对segaboot的深入研究

SegaBoot通常是ALLS HX2/UX2 的 核心启动/管理程序,从逆向的视角带你看看他们的作用。

这些文件提取于 UX2/HX2 中位于 c:/Windows/Sega/System 中,可能会带来侵权/DMCA

如果你认为我侵犯了这些软件的版权,请您联系我删除!

SegaBoot 的组成

System/
├── resources/
│   ├── download_icon.png
│   ├── license_system_en.txt
│   ├── license_system_ja.txt
│   ├── license_system_zh.txt
│   ├── logo.png
│   └── logomode_load.gif
├── installed/
│   ├── 0001_StandardCommon_058/
│   ├── 0002_Uwf_001/
│   ├── 0007_ACA4.2_003/
│   ├── 0105_SDFT_007/
│   ├── 1003_bugfix_002/
│   └── 1004_bugfix_001/
├── mainstorage/
│   ├── apply/
│   └── deploy/
├── recovery/
│   ├── apply/
│   └── deploy/
├── substorage/
│   ├── apply/
│   └── deploy/
├── reinstall/
│   ├── dpinst.exe
│   ├── dpinst.xml
│   └── reinstall.bat
├── Microsoft.Expression.Interactions.dll
├── System.Windows.Interactivity.dll
├── UwfServicingMasterScript.cmd
├── VWF_Tool.exe
├── amDeviceManagerWrapperMD.dll
├── devcon.exe
├── hsol_vwf.dll
├── pxSecureAtaCmd.exe
├── sgSystemServicingCommon.ps1
├── sgSystemServicingMasterScript.ps1
├── sgimagemount.exe
├── sglaunch.exe
├── sglaunch.json
├── sgmanageio.exe
├── sgmanageio.InstallLog
├── sgmanageio.InstallState
├── sgosupdate.exe
├── sgsetdisplaysetting.exe
├── sgsetresolution.exe
├── sgsystemservicingshell.exe
├── sgxdownloadui.exe
├── sgxmaster.exe
├── sgxprestartup.exe
├── sgxsegaboot.exe
├── sgxsystemdaemon.exe
├── sgxsystemservicing.exe
├── speaker_test_managerMD.dll
├── system_appuser.txt
├── system_key.txt
├── system_share_data_wrapperMD.dll
├── system_updateuser.txt
├── util_downloadui.dll
├── util_sgxsegaboot.dll
└── wrp_systemdaemon_cli_wrapperMD.dll

他们每个都负责着不同的工作,经过我的研究发现,大部分的程序都是属于sgxsystemdaemon.exe的子程序,也就是通过某种方式调用其他程序,通常通过 System Test Mode 页面拉取。

system-test-mode-page
system-test-mode-page

因为手边没有世嘉基板,我通过还原sgxsegaboot.exe提取的页面,实际应该为这样的。

system-test-mode-page-real
system-test-mode-page-real

歪了是因为基板里面没装游戏程序,判断不出来方向

接下来我将对这些程序按照重要顺序依次介绍排序。

部分程序带有早期老版本的crackproof的壳子,你可以通过网上的资源轻松裂开,世嘉有很长时间没有维护这个启动程序了,准确来说我没有渠道拿到比较新的东西,我的资源通常来源于线下提取的。

部分程序我已经处理过壳子,所以可能造成内存位置不正确,几乎都是通过 IDA Pro 或者 DnspyEx 来逆向的。

因为我没有那个钱去买基板和加密狗来实操运行,只能通过静态或半动态的方式逆向,所以准确度可能不是太高,仅供参考。

SgxSegaBoot

sgxsegaboot 本身是一个纯粹的 前端显示器 + 命令转发器,不直接执行任何硬件检测或系统操作。

他可以通过 DnspyEx 拖入直接看到代码,所以我们可以对他进行还原成可构造的状态,甚至可以整一点花活。

0721
0721

这个也可以用来改成 SegaBoot Simulator 虽然没什么用,只是为了装逼

通过还原,它主要的功能有以下这些。

启动与监控:
  - 启动 sgxsystemdaemon.exe(测试模式时传 -s test)
  - 16ms 周期轮询共享数据区
  - 监听 daemon 退出事件

启动画面与错误显示:
  - 读取共享数据中的 stepNumber / errorNumber
  - 显示 STEP 01-31 对应的步骤消息
  - 错误时显示 ERROR XXXX + 错误消息文本
  - STEP 30/31 时发送 exit 命令并关闭窗口
  - daemon 非正常退出时强制显示 ERROR 0010

输入处理:
  - Test → Enter 键
  - Service → Tab 键
  - 方向键 → Arrow Up/Down/Left/Right
  - USB IO 输入事件 → WPF 键盘导航

命令转发 (通过 util.wrp.Client 发送给 daemon):
  - 退出 daemon
  - 设置时钟
  - 卸载游戏
  - 修改网络配置 (IP/DHCP/Gateway/DNS)
  - 修改投币/credit 配置
  - 启动/清除网络测试
  - 格式化主/副存储
  - 修改 DIP 开关设置
  - 清 credit
  - 保存/清除触摸屏校准

系统测试菜单:
  - 根据共享数据中的 SystemTestMenu 启用/禁用菜单项
  - 菜单状态: Enable / Loading / Disable / Hidden
  - System Info, Storage, USB IO, Speaker, Coin, Clock, Network, DIP, Touch 等

可以看出他类似于一个前端,所有的功能都是通过sgxsegaboot.exe来操作。

sgxsegaboot.exe 是纯粹的前端:
  ┌──────────────────────────────────────────────┐
  │  C# WPF 界面                                 │
  │  - 显示 boot 步骤 / 错误 / 进度              │
  │  - 系统测试菜单 UI                           │
  │  - 输入 → WPF 键盘导航                       │
  │  - 命令 → util.wrp.Client → daemon           │
  └──────────────────────────────────────────────┘
         │ 共享内存 (ShareData)

sgxsystemdaemon.exe 是后端引擎:
  ┌──────────────────────────────────────────────┐
  │  C++ 原生                                    │
  │  - 主板检测, keychip 验证, USB IO            │
  │  - 存储格式化 (PowerShell 脚本)              │
  │  - 网络配置, 防火墙, 系统配置                │
  │  - 所有底层操作                              │
  └──────────────────────────────────────────────┘

例如发生了错误ERROR 0021主板故障,它的交互逻辑是这样的。

sgxsystemdaemon 侧:
  GetInfoTask@mainboard
    → amPlatformGetDevSet() 失败
    → sequencer.errorNumber = 21

sgxsegaboot 侧:
  onSequenceChange(sequencer)
    → sequencer.m_errorNumber = 21
    → errorSequence(21u)
      → getErrorMessageOrWorkaround(21)
        → TextResource.ERROR_0021_MESSAGE
          → "Main board Malfunctioning" (en)
          → "メインボードに異常があります" (jp)
          → "主板故障" (zh)

而上文提到的页面图片,则是通过sgxsystemdaemon.exe进行操作,使用指令util.wrp.Client.SendCommand()传递给sgxsystemdaemon 侧,由Worker@wrp@segaboot执行。

系统信息的读取,也是如此

System Info 页面显示:
  Keychip ID         ← 共享数据 (daemon 从 NVRAM 读取)
  Game ID            ← 共享数据 (daemon 从 keychip 读取)
  Region             ← 共享数据
  主板 ID            ← 共享数据
  OS 版本             ← 共享数据 (daemon 读取)
  内存容量            ← 共享数据 (daemon 调用 GlobalMemoryStatusEx)
  CPU 温度            ← 共享数据 (daemon Task@temperature@cpu@monitor)
  主/副存储容量       ← 共享数据
  显卡 vendor/device  ← 共享数据
  BIOS 版本           ← 共享数据

system-info
system-info

图示情况是过了基板验证,但是没有读取到系统信息。

所以他就是一个纯粹的前端,无任何功能,怪不得世嘉不给他加壳。。。

架构层次

sgxsegaboot.exe (C# WPF 前端)
  └── util_sgxsegaboot.dll           ← 输入处理/共享数据/喇叭测试
        └── system_share_data_wrapperMD.dll  ← 共享数据结构 C++/CLI 桥接
              └── wrp_systemdaemon_cli_wrapperMD.dll  ← daemon Worker 命令 CLI 包装器
                    └── sgxsystemdaemon.exe (C++ 原生守护进程)
              └── amDeviceManagerWrapperMD.dll  ← 设备管理器包装器

Sgxsystemdaemon

这个程序是整个系统的核心,所以这篇文章将会用大量的文字来描述这个程序,世嘉给他加了crackproof壳子,但是依赖前人的工具我们可以很轻松裂开,看到内部的内存地址与位置。

通过查看二进制,我们发现这个程序记录了一些版本号。

sgxsystemdaemon Ver.a75ebcfc
  Build: Jan 9 2024 14:40:03           ← 主程序 (2024)

SEGA AM Library Ver.5b8f8eee
  Build: Jan 9 2024 14:39:48           ← 底层框架库 (2024)

altr Ver.1.00.00
  Build: Nov 1 2013 18:04:04            ← 网络中继模块 (2013,遗留代码)

新代码 (2024) 用 C++ 重写了大部分逻辑,但核心的 ALTR 网络协议模块还保留着 2013 年的原始 socket 实现。

混淆手法

二进制经过控制流混淆,反汇编中有大量垃圾指令:

sub_1400B2F20:
    pop rsi
    mov ebx, esp
    sub rsp, 68h
    mov rax, [rdx]
    mov qword ptr [r11-28h], 0
    mov [r11-10h], rax
    mov eax, [rdx+14h]
    mov [rsp+60h+var_10], eax
    mov rsp, 8B48D04389491842h      ; ← 混淆立即数
    test [rax+rdx*4], ah             ; ← 混淆指令

混淆没有覆盖数据段和 RTTI (运行时类型信息),所以我们可以通过 C++ 类名反推整个架构。

架构图如下

┌──────────────────────────────────────────────────────────────────────┐
│                     sgxsystemdaemon.exe                               │
│                     Ver.a75ebcfc (Jan 2024)                          │
│                       SEGA AM Library v5b8f8eee                      │
│                                                                      │
│  ┌──── IPC ────┐  ┌── Boot Seq ──┐  ┌── ALTR Protocol ────┐        │
│  │ ShareData*  │  │ Step 1-31    │  │  Server Main         │        │
│  │ CreateEvent │  │ MergeLicense │  │  SendPacket          │        │
│  │ CommandQueue│  │ ModeSelect   │  │  IcmpProbe           │        │
│  └─────────────┘  └──────────────┘  └──────────────────────┘        │
│                                                                      │
│  ┌── USB IO ────┐  ┌── Keychip ──┐  ┌── Dev Mode ────────┐         │
│  │ Manager      │  │ SearchTask  │  │ Mount Dev Storage  │         │
│  │ setupNode    │  │ ValidateTask│  │ Disable Firewall   │         │
│  │ parseBoardType│  │ DummyKeychip│  │ ChannelEncryptOff  │         │
│  └──────────────┘  └─────────────┘  └────────────────────┘         │
│                                                                      │
│  ┌── Storage ───┐  ┌── Firewall ─┐  ┌── AppImage ────────┐         │
│  │ CheckDisk    │  │ Enable/     │  │ Install/Deploy     │         │
│  │ Format       │  │ Disable     │  │ Beacon/DVD/USB    │         │
│  │ BitLocker    │  │ Exception   │  │ Gcatcher          │         │
│  └──────────────┘  └─────────────┘  └────────────────────┘         │
│                                                                      │
│  ┌── Config ────┐  ┌── SystemTest ─┐  ┌── Utility ───────┐         │
│  │ JSON Parser  │  │ Network      │  │ Process/Thread   │         │
│  │ SystemProperty│  │ Credit/Touch │  │ File/BinaryFile  │         │
│  │ ModuleBase   │  │ EDID/Sound   │  │ Input Monitor    │         │
│  └──────────────┘  └──────────────┘  └──────────────────┘         │
│                                                                      │
│  ┌── Licensing ──┐  ┌── Logging ──┐  ┌── Time/NVRAM ────┐         │
│  │ MergeLicense  │  │ EventLog    │  │ SetLocaltime     │         │
│  │ amBackup*     │  │ IcfLog      │  │ NvMemory         │         │
│  │ BCrypt AES    │  │ SystemRecord│  │ Volume           │         │
│  └──────────────┘  └─────────────┘  └──────────────────┘         │
└──────────────────────────────────────────────────────────────────────┘

下面逐一展开核心子系统。

通信模型

sgxsystemdaemon.exe (后台)            sgxsegaboot.exe (WPF 界面)
      │                                     │
      │  CreateFileMappingW()                │
      │  MapViewOfFile()                     │
      │    └── ShareData* pData              │ OpenFileMappingW()
      │                                     │ MapViewOfFile()
      │                                     │   └── ShareData* pData
      │                                     │
      │  pData->sequencer = {               │
      │    stepNumber: 4,                   │
      │    errorNumber: 21,   ← "主板故障"   │
      │    bDevelopSequence: false           │
      │  }                                   │
      │  SetEvent(hDataReady) ────────────→ │ WaitForSingleObject(hDataReady)
      │                                     │ read(pData->sequencer)
      │                                     │ → "ERROR 0021"
      │                                     │
      │  Wait...Object(hCommand) ←───────── │ pData->command = CMD_EXIT
      │  process(CMD_EXIT)                  │ SetEvent(hCommand)
      │  SetEvent(hCommandDone) ──────────→ │ Wait...Object(hCommandDone)

从 C# 侧和 daemon 侧共同还原出的结构:

struct ShareData {
    Sequencer   sequencer;        // 启动序列状态
    SystemInfo  systemInfo;       // 系统信息
    uint8_t     command[1024];    // 命令缓冲区
};

struct Sequencer {
    uint32_t stepNumber;          // 当前步骤 (1-31)
    uint32_t errorNumber;         // 错误码 (0=正常)
    bool     bDevelopSequence;    // 开发模式标志
};

struct SystemInfo {
    wchar_t wszPlatformName[256]; // 平台名称
};

enum CommandType {
    CMD_NETWORK_SETTING,          // 设置网卡
    CMD_NETWORK_TEST_START,       // 网络测试开始
    CMD_NETWORK_TEST_CLEAR,       // 清除网络测试
    CMD_MAIN_STORAGE_FORMAT,      // 格式化主存储 (C:)
    CMD_SUB_STORAGE_FORMAT,       // 格式化副存储 (D:)
    CMD_CREDIT_CLEAR,             // 清除投币数据
    CMD_CREDIT_SETTING,           // 设置投币参数
    CMD_CLOCK_SETTING,            // 设置时钟
    CMD_SYSTEM_CONFIGURATION,     // 系统配置
    CMD_CLEAR_TOUCH_CALIB,        // 清除触屏校准
    CMD_SAVE_TOUCH_CALIB,         // 保存触屏校准
    CMD_EXIT,                     // 退出 daemon
    CMD_UNINSTALL,                // 卸载
};

命令由 WPF 侧通过 SetEvent(hCommand) 发送,daemon 的 Worker@wrp@segaboot 执行。

对他有一个内部的开发者环境,可以绕过基板验证和物理加密狗(以下简称物理狗/虚拟狗)验证,生成一个虚假狗。

启动序列

Step 1:  许可证合并开始
Step 4:  主板验证             → 失败 → ERROR 0021 "主板故障"
Step 10: 磁盘检测
Step 11: 加载计数读取
Step 12: 系统信息收集
Step 13: 安装介质检测
Step 21: 模式选择
           ├── Normal → Keychip 启动
           ├── Developer → 开发模式
           ├── Recovery → 磁盘恢复
           └── Update → 系统更新
Step 30-31: 完成/退出(稍后启动游戏程序)

开发者模式则是通过amsSystemPropertyIsDevelop() 函数。这是 SEGA AM Library 提供的一个系统属性查询函数,其错误信息直接出现在 DummyKeychip 模块的二进制中:

Error: amsSystemPropertyIsDevelop error = 0x%08\n

这个函数从 AM System Property 机制读取开发模式标志。这个标志的来源可能是:

  • 注册表 HKLM\System\SEGA\SystemProperty\IsDevelop
  • 特定文件的属性
  • 内核驱动曝露的属性

判断逻辑:

if (amsSystemPropertyIsDevelop() == TRUE) {
    → 进入 Developer Mode
    → Keychip 搜索失败时不报错,改走 ApplyDummyTask
} else {
    → 进入 Normal Mode
    → Keychip 搜索失败时 → ERROR 0001 "找不到加密锁"
}

实际上我在注册表里面什么都读不到,包括下文提到的会在注册表里面存储物理狗信息。

主板验证

主板验证对应 GetInfoTask@mainboard@startup 任务,在 Step 4 执行。它调用 5 个 amPlatform* 函数:

amPlatformGetMemorySize — 内存检测

二进制字符串:

"GlobalMemoryStatusEx Error!! (%d)"

伪代码还原:

uint64_t amPlatformGetMemorySize() {
    MEMORYSTATUSEX memInfo = { .dwLength = sizeof(MEMORYSTATUSEX) };
    if (!GlobalMemoryStatusEx(&memInfo)) {
        log("GlobalMemoryStatusEx Error!! (%d)", GetLastError());
        return 0;  // ← 触发 ERROR 0021
    }
    if (memInfo.ullTotalPhys == 0)
        return 0;  // ← 物理内存为 0 → ERROR 0021
    return memInfo.ullTotalPhys;
}

amPlatformGetGraphicInfo — 显卡检测

二进制字符串:

"amPlatformGetGraphicInfo"
"\\.\DISPLAY"
"SetupDiGetDeviceInfoListDetail Error!! Error Code is %ld!!\n"

遍历 EnumDisplayDevicesW() 查找活动的显示设备;同时打开 \\.\DISPLAY 设备通过 DeviceIoControl 查询 EDID。

amPlatformGetUsbIdList — USB 设备 ID 列表

这是最关键的部分。二进制中直接存放了 USB 设备 ID 的 5 段格式:

内存      0x14013dac8: "amPlatformGetUsbIdList"
之后紧跟着 5 个 4 字节的对齐字符串:

地址         字符串     说明
0x14013dae0: "VEN_"    ← USB 供应商 ID (Vendor)
0x14013dae8: "VID_"    ← 产品/设备 ID (Device)
0x14013daf0: "DEV_"    ← 设备类别 (Class)
0x14013daf8: "SUBSYS_" ← 子系统 ID (Subsystem) ★最关键
0x14013db00: "PID_"    ← 产品 ID (Product)

构造出的 USB 设备标识格式:

VEN_XXXX_VID_XXXX_DEV_XXXX_PID_XXXX_SUBSYS_XXXX

验证代码执行:

SetupDiGetClassDevsW(NULL, L"USB", NULL,
    DIGCF_ALLCLASSES | DIGCF_PRESENT);
for (i = 0; SetupDiEnumDeviceInfo(hDevInfo, i, &devInfo); i++) {
    CHAR deviceId[MAX_DEVICE_ID_LEN];
    CM_Get_Device_IDA(devInfo.DevInst, deviceId, MAX_DEVICE_ID_LEN, 0);

    // deviceId 格式:
    // "USB\VID_XXXX&PID_XXXX\SUBSYS_XXXX&REV_XXXX\..."

    // 解析出 VEN/VID/DEV/PID/SUBSYS 构造 ID
    snprintf(formattedId,
        "VEN_%04X_VID_%04X_DEV_%04X_PID_%04X_SUBSYS_%04X",
        vendorId, deviceId, devClass, productId, subsysId);

    // 与已知 SEGA 硬件白名单匹配
    if (MatchKnownSEGAHardware(formattedId, &hwType)) {
        switch (hwType) {
            case HW_MAINBOARD: ...
            case HW_IOBOARD: ...
            case HW_NETWORK: ...
            case HW_STORAGE: ...
        }
    }
}

SUBSYS_ 是基板匹配的关键字段。 每个 SEGA 基板在 PCI/USB 配置空间中有一个唯一的子系统 ID,daemon 通过这个 ID 确定插入的是什么型号的基板。

amPlatformGetDevSet — 设备集合验证

地址 0x14013db88: "amPlatformGetDevSet"

汇总前 3 项检测结果,检查基板配置完整性:

BOOL amPlatformGetDevSet(DevSet* devSet) {
    UsbIdList usbList;
    amPlatformGetUsbIdList(&usbList);

    BOOL hasMainboard = FALSE;
    BOOL hasIoBoard = FALSE;
    BOOL hasNetwork = FALSE;
    BOOL hasStorage = FALSE;

    for (each id in usbList) {
        MatchKnownSEGAHardware(id, &hwType);
        switch (hwType) {
            case HW_MAINBOARD:  hasMainboard = TRUE; break;
            case HW_IOBOARD:    hasIoBoard = TRUE; break;
            case HW_NETWORK:    hasNetwork = TRUE; break;
            case HW_STORAGE:    hasStorage = TRUE; break;
        }
    }

    // 以下任何一项缺失 → ERROR 0021
    if (!hasMainboard || !hasIoBoard ||
        !hasNetwork || !hasStorage)
        return FALSE;

    devSet->mainboardPresent = hasMainboard;
    devSet->ioBoardPresent = hasIoBoard;
    devSet->networkPresent = hasNetwork;
    devSet->storagePresent = hasStorage;
    return TRUE;
}

这 5 道关卡:

Step 4: GetInfoTask@mainboard

     ├── amPlatformGetMemorySize()
     │   └── "GlobalMemoryStatusEx Error!! (%d)" → 内存为 0 → ERROR 21

     ├── amPlatformGetGraphicInfo()
     │   └── "SetupDiGetDeviceInfoListDetail Error!!..." → 无显示设备
     │   └── "\\.\DISPLAY" 打开失败 → 可能继续 (无头运行)

     ├── amPlatformGetUsbIdList()
     │   └── "SetupDiGetClassDevs Error!!..." → USB 枚举失败 → ERROR 21
     │   └── 构造 "VEN_/VID_/DEV_/PID_/SUBSYS_" → 与白名单匹配

     ├── amPlatformGetDevSet()
     │   └── 必要硬件 (主板/IO板/网卡/存储) 是否全部存在?
     │   └── 缺任何一个 → ERROR 21

     ├── amLibGetMainSerialId()
     │   └── 通过 WMI 查询 Win32_BaseBoard.SerialNumber
     │   └── 序列号为空 → ERROR 21

     └── 全部通过 → Step 10

Keychip

Keychip 也就是我们俗话所有的狗,是一个 USB HID 设备 (硬件加密狗/U盘状),内部包含加密 NVRAM,存储游戏授权信息。

daemon 中相关的 RTTI 类:

Manager@keychip@impl           ← Keychip 管理器
KeychipInfo@impl               ← Keychip 配置数据
KeychipNvramAccess@impl        ← NVRAM 读写访问
DummyKeychip                   ← 软件模拟实现 (Rev 3406)

它的运行逻辑是这样的,

SearchTask (USB HID 扫描)

    ├── 找到物理 keychip
    │   → ReadInfoTask
    │     → amyDongleOpen()          ← 打开 USB 加密狗
    │     → KeychipNvramAccess::setup()
    │     → 读取 6 个 NVRAM 参数
    │     → ValidateTask
    │       → BCrypt 解密 NVRAM 数据
    │       → HMAC-SHA256 验证签名
    │       ┌── 通过 → 使用物理 keychip
    │       └── 失败 → dev mode? → ApplyDummyTask
    │                        → normal → 报错

    └── 没找到
        ┌── dev mode? → ApplyDummyTask
        └── normal → ERROR 0001 "找不到加密锁"

在内存地址0x140150dd0 中,我们可以找到 NVRAM 数据表,它存储的信息包括。

地址          字符串          类型      说明
─────────────────────────────────────────────────
0x140150dd0  System\Sega\     路径     注册表备份路径
             SystemProperty\
             keychip

0x140150e18  gameId           REG_SZ   游戏ID (如 "SDGB")
0x140150e28  region           REG_SZ   地区 ("JPN"/"CHN")
0x140150e38  modelType        REG_SZ   机型号 ("TYPE-5")
0x140150e50  systemFlag       DWORD    系统功能标志
0x140150e68  serverIpIpv4     REG_SZ   ALL.NET 服务器 IPv4
0x140150e88  serverIpIpv6     REG_SZ   ALL.NET 服务器 IPv6

对,它是会备份数据到注册表,但是实际情况我找不到这个注册表,不知道为何。

它的读写流程为

// KeychipNvramAccess 的 NVRAM 访问流程
KeychipNvramAccess::setup() {
    amyDongleOpen();                     // USB HID 打开加密狗
    recvFromInterruptPipe(hDevice);      // 建立中断管道
    recvFromControlPipe(hDevice);        // 建立控制管道
}

KeychipNvramAccess::read(L"gameId") {
    // 1. HID 协议发送读取请求
    // 2. amUsbioRecvAcknowledge() 等待回复
    // 3. BCryptDecrypt() 解密加密数据
    //    算法: AES-CBC, 密钥在 daemon 代码段中
    // 4. HMAC-SHA256 签名验证
    // 5. 写入注册表缓存 (如果是首次读取)
    // 6. 返回解密后的值
}

// NVRAM 加密条目结构 (验证签名用)
struct NvramEntry {
    wchar_t paramName[32];      // "gameId", 等
    uint32_t dataSize;
    uint8_t encryptedData[256];  // BCrypt AES-CBC 加密
    uint8_t hmac[32];            // HMAC-SHA256 签名
};

然后验证为

AMS_STATUS ValidateTask::execute() {
    // 1. 打开 AES 算法
    BCryptOpenAlgorithmProvider(&hAlg, L"AES", NULL, 0);
    BCryptSetProperty(hAlg, L"ChainingMode", L"CBC", ...);

    // 2. 导入解密密钥
    BCryptGenerateSymmetricKey(hAlg, &hKey, key, keyLen);

    // 3. 解密
    BCryptDecrypt(hKey, nvramData.encrypted, nvramData.size,
                  NULL, iv, sizeof(iv),
                  decrypted, sizeof(decrypted), &resultLen,
                  BCRYPT_BLOCK_PADDING);

    // 4. HMAC 验证
    BCryptCreateHash(hAlg, &hHash, NULL, 0, macKey, macKeyLen, 0);
    BCryptHashData(hHash, decrypted, resultLen);
    BCryptFinishHash(hHash, computedHmac, 32);

    // 5. 签名比较
    if (memcmp(computedHmac, nvramData.hmac, 32) == 0)
        return OK;     // ← 物理 keychip 验证通过
    else
        return FAIL;   // ← 签名不匹配
}

根据逆向,它存储到注册表的位置为

HKEY_LOCAL_MACHINE
  └── System\SEGA\SystemProperty\keychip\     ← 路径来自 :0x140150dd0
        ├── gameId          REG_SZ            ← 字符串值
        ├── region          REG_SZ
        ├── modelType       REG_SZ
        ├── systemFlag      REG_DWORD         ← 通过 amyRegistrySetEntryDataInteger
        ├── serverIpIpv4    REG_SZ
        └── serverIpIpv6    REG_SZ

代码调用链:

amyDongleOpen() → 读取 NVRAM
  → 验证通过后
    → amyRegistryOpen("System\\Sega\\SystemProperty\\keychip")
    → amyRegistrySetEntryDataInteger(hKey, L"systemFlag", 0x00000027)
    → amyRegistryClose(hKey)

DummyKeychip

这里面竟然内置了一套虚拟狗程序,可以模拟keychip,似乎是在开发环境中调用的。

DummyKeychip Build:Jan  9 2024 14:39:55 ($Rev: 3406 $)

对比物理 keychip 相关模块:

KeychipInfo  Build:Jan  9 2024 14:39:55 ($Rev: 2356 $)   ← Rev 2356
NvramAccess  Build:Jan  9 2024 14:39:55 ($Rev: 2356 $)   ← Rev 2356
DummyKeychip Build:Jan  9 2024 14:39:55 ($Rev: 3406 $)   ← Rev 3406

触发条件为读取Developer Mode的布朗值。

modeselect@startup

    ├── amsSystemPropertyIsDevelop() = TRUE
    │   → 全局开发者模式
    │   → SearchTask 搜索物理 keychip
    │     ├── 找到 → 正常读取+验证
    │     └── 没找到 → ApplyDummyTask → DummyKeychip

    └── amsSystemPropertyIsDevelop() = FALSE
        → 正常模式
        → SearchTask 搜索物理 keychip
          ├── 找到 → 正常读取+验证
          └── 没找到 → ERROR 0001 "找不到加密锁"

开发者模式开关正是 amsSystemPropertyIsDevelop() 函数,错误信息紧邻 DummyKeychip 构建字符串的后方:

0x140150c80: "Error: amsSystemPropertyIsDevelop error = 0x%08\n"

虚拟狗的模拟实现是这样的,

class DummyKeychip : public impl::Base {
public:
    // 1. 跳过硬件打开
    AMS_STATUS open()  { return STATUS_OK; }  // 不开 USB
    AMS_STATUS close() { return STATUS_OK; }

    // 2. 生成虚拟 KeychipInfo
    AMS_STATUS getInfo(KeychipInfo* info) {
        // 硬编码默认值:
        info->gameId      = L"SDEZ";           // 默认游戏ID
        info->region      = L"JPN";            // 默认地区
        info->modelType   = L"";               // 不限制机型
        info->systemFlag  = 0x00000000;         // 默认标志
        info->serverIpv4  = L"127.0.0.1";       // 回环地址
        info->serverIpv6  = L"::1";             // IPv6 回环
        return STATUS_OK;
    }

    // 3. NVRAM 用内存替代,跳过真实读写
    AMS_STATUS readNvram(const wchar_t* name, void* data, size_t* size) {
        // 不调用 amyDongleOpen
        // 不从 HID 中断管道接收
        // 不从 HID 控制管道接收
        // 不从 BCrypt 解密
        // 不从 HMAC 校验
        // → 直接从内存映射返回硬编码值
        return STATUS_OK;
    }

    // 4. 验证直接返回"通过"
    AMS_STATUS validate() {
        return STATUS_OK;  // 跳过所有加密验证
    }

    // 5. 生成虚拟序列号
    uint64_t getSerialNumber() {
        return 0xDEADBEEF00000001ULL;  // 固定调试序列号
    }
};

可以看到几乎都是返回的值,我去这不是废话吗

DummyKeychip 没有自己的 vtable(二进制中搜索不到 .?AVDummyKeychip 之外的任何引用)。这意味着它是通过工厂模式构造的:

ApplyDummyTask::execute() {
    auto* dummy = new DummyKeychip();         // 工厂构造
    Manager@keychip::setKeychip(dummy);       // 注入管理器
    return STATUS_OK;
}

USB IO 基板验证

首先发现枚举所有的USB设备,然后解析产品中的字符串。

io::usb::Manager::setup()
  → HidD_GetHidGuid()
  → SetupDiGetClassDevsW(&hidGuid)
  → 遍历设备:
      SetupDiEnumDeviceInterfaces()
      → amUsbioSetupDevice(devicePath)
setupNode(hDevice)
  → HidD_GetManufacturerString()
    → 失败: "Error: HidD_GetManufacturerString(). ErrCode %u\n"
  → HidD_GetProductString()
    → 失败: "Error: HidD_GetProductString(). ErrCode %u\n"
  → amUsbiopParseNodeInfo(mfr, product, &info)

SEGA IO 板的产品字符串中嵌入机器可读的参数:

// 产品字符串示例:
"BTIDa_1000_BoardNo_A01_FW_1.0.3_FUNC_SWITCH"

                  ↓ splitProduct 按 '_' 拆分

parseBoardType(parts, &boardType)       → BTIDa = 1000
parseBoardNo(parts, &boardNo)           → BoardNo = A01
parseFirmwareRevision(parts, &fwRev)    → FW = 1.0.3
parseFirmwareSum(parts, &fwSum)         → 固件校验和
parseCustomChipNo(parts, &customChip)   → 自定义芯片编号
parseMode(parts, &mode)                 → 工作模式

BTIDa / BTIDA 是基板类型标识。 parseBoardType 提取这个值来确定基板类型。

ALTR 协议 TCP中继

这个程序为2013年的TCP中继,通过研究世嘉的后端代码,发现的确是这么老,他们的后端几乎都是老掉牙的php和springboot。

(ALLNET) BEACON,1,SDVX-JPN-A12-2024,       ← 信标广播
(ALLNET) STATUS,128,SDVX,JPN,TYPE-5,OK,     ← 状态上报
(ALLNET) PING,55,1703123456,                 ← 心跳探测
(ALLNET) CONFIG,12,192.168.1.100,            ← 配置同步
(ALLNET) ERROR,0xEE0002,Packet Too Long,     ← 错误响应

协议堆栈

altrInitProbe()
  → altrIcmpInitialize()        → IcmpCreateFile()
  → altrIcmpSendEcho()          → IcmpSendEcho2()
  → altrIcmpWaitReply()         → 等回复
  → altrIcmpGetReplyStatus()    → 检查状态
  → altrCheckProbeTimeout()     → 超时检查

altrServerMain()
  → socket → bind → listen → accept
  → 每次连接:
    1. WSARecv()
    2. altrDecodePacket() → 解析 "(ALLNET) %s,%d,%s,"
    3. 处理命令
    4. altrSendPacket() → WSASend()

altrSendPacket():
    // 格式化为 "(ALLNET) %s,%d,%s,"
    // → 错误码 0xEE0002 = "too long packet"

你也可以通过bsnk的文章来详细了解ALLNET,这里不做过多阐述。

开发者模式

检测是否启用了开发者模式

amsSystemPropertyIsDevelop() 读取 AM System Property,可能来源:

  • 注册表 System\SEGA\SystemProperty\IsDevelop
  • DIP 开关状态 (通过 USB IO 板读取)
  • 特定 USB 设备的插入
  • 启动时的按键组合

这里没有做详细的研究只是进行了大致的猜测。

那么开发者模式到底有什么东西呢?

发现如果你启用了开发者模式,会执行这些东西

Sequencer@develop@execution@@:
  ① MountTask@devstorage        → 挂载开发者存储 (U盘/SD/网络)
  ② MountTask@optionimage       → 挂载选项镜像
  ③ ExecuteTask@startup         → 执行启动脚本 (autorun.sh/bat)
  ④ DisableTask@keyboardfilter  → 开放键盘输入 (USB 键盘直接操控系统)
  ⑤ DisableTask@firewall        → 关闭防火墙 (所有端口开放)
  ⑥ AddFolderTask@sharedfolder  → 创建 SMB 网络共享

  ⑦ ChannelEncryptOffTask       → 关闭 ALL.NET 通信加密
  ⑧ AssignTask@privilege        → 权限提升

这样就是为什么你在基板上插鼠标和键盘没有任何反应的原因,他们通过注册表限制了这些,其次是会打开segaboot下的按钮操作,你可以通过键盘的Enter进入系统测试菜单。

// sgxsegaboot ViewModel.cs
onSequenceChange(sequencer) {
    m_bEnableEnterTest = sequencer.m_bDevelopSequence;
    // → 开发模式下 "Enter Test" 按钮变为可用
    // → 点击后进入系统测试菜单
}

存储初始化

这也是我为什么想要逆向segaboot的原因,因为我在尝试修复我那台新加坡机器时所遇到的问题,因为那里的gs1盘被人偷走了,所以我请了🐍妈帮我写了盘,插上机后,我按照流程初始化了副盘,然后开机,等到稍后启动游戏程序,然后就黑屏重启,继续初始化主盘,注意这里我没有主动初始化,但是他自己却主动初始化了,接下来就是反复的重启与初始化主盘,令我很头疼。

sgxsegaboot 系统测试菜单中有「初始化主盘」和「初始化副盘」两个功能。对应 daemon 中的两个 Worker 命令和两个系统测试任务。

从二进制中提取的存储设备路径字符串:

地址          字符串                 含义
──────────────────────────────────────────────────
0x140151d08   \\.\Q:\               ← 副盘 (Q:) 设备路径
0x140151d78   \\.\Q:                ← 副盘设备路径 (无尾斜杠)
0x140151d73   Q:                    ← 副盘盘符 (用于挂载点)
0x140151d18   \\.\%C:\              ← 主盘设备路径模板 (%C → C)
0x140151cf8   \device\CDRom%d       ← CD/DVD 驱动器路径模板

涉及的类 (从 RTTI 提取)

存储管理:
  Volume@impl@@                        ← 卷管理实现
  Manager@mount@appimage@impl@@        ← 挂载管理器
  TYPE@mount@appimage@impl@@           ← 挂载类型枚举

格式化命令 (Worker 命令, 由 sgxsegaboot 触发):
  MainStorageFormatCommand@inner@worker@wrp@segaboot
  SubStorageFormatCommand@inner@worker@wrp@segaboot

格式化任务 (系统测试菜单调用):
  FormatMainStorageTask@appimage@systemtest@
  FormatSubStorageTask@appimage@systemtest@

磁盘发现与初始化:
  FindTask@substorage@initialboot@     ← 发现副盘设备
  Sequencer@disk@startup@              ← 磁盘启动序列
  GetSizeTask@Sequencer@disk@startup   ← 获取磁盘大小
  SetAmfsPathTask@Sequencer@disk@startup ← 设置 AMFS 路径

磁盘验证 (initialboot 时执行):
  CheckSequencer@volume@checkdisk@initialboot
  CheckBitLockerTask                    ← 检查 BitLocker 加密状态
  ValidateVolumeTask                    ← 验证卷完整性
  DirtyCheckTask                        ← 检查脏状态

恢复:
  RecoveryTask@disk@modeselect@execution ← 磁盘恢复
  RecoveryTask@amfs@common@execution    ← AMFS 文件系统恢复

挂载/卸载:
  MountTask@mount@appimage@release@execution
  UnmountTask@prepare@appimage@release@execution

主盘 (Main Storage) 初始化流程

物理路径: \\.\C:\ (通过 \\.\%C:\ 模板,%CC)

触发途径:

  • 系统测试菜单 → 选择”初始化主盘”
  • sgxsegaboot 发送 MainStorageFormatCommand → daemon 的 Worker 执行
  • 或直接从 systemtest 调用 FormatMainStorageTask

执行逻辑 (从 API 调用链还原):

AMS_STATUS MainStorageFormatCommand::execute() {
    // ===== 第 1 步: 准备卷 =====
    // 1a. 获取主盘卷 GUID
    wchar_t volumeGuid[MAX_PATH];
    GetVolumeNameForVolumeMountPointW(L"C:\\", volumeGuid, MAX_PATH);

    // 1b. 锁定卷 (阻止其他进程写入)
    HANDLE hVolume = CreateFileW(L"\\\\.\\C:\\",
        GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL, OPEN_EXISTING, 0, NULL);

    DWORD bytesReturned;
    DeviceIoControl(hVolume, FSCTL_LOCK_VOLUME,
        NULL, 0, NULL, 0, &bytesReturned, NULL);

    // 1c. 卸载卷 (移除文件系统挂载)
    DeviceIoControl(hVolume, FSCTL_DISMOUNT_VOLUME,
        NULL, 0, NULL, 0, &bytesReturned, NULL);
    // → 失败时: "Error : Cannot Unmount C: (LastError = %d).\n"

    // 1d. 删除现有挂载点
    DeleteVolumeMountPointW(L"C:\\");

    // ===== 第 2 步: 格式化 =====
    // 格式化方式: 标准 NTFS 快速格式化
    // 使用 DeviceIoControl(FSCTL_FORMAT) 或 SHFormatDrive
    // 或自己写 MBR + NTFS 引导扇区
    DWORD maxCount = 100;  // 重试计数器
    while (maxCount-- > 0) {
        // 通过 WriteFile 写入文件来测试可写性
        // 或用 DeviceIoControl 发格式化命令
    }

    // ===== 第 3 步: 重建挂载 =====
    SetVolumeMountPointW(L"C:\\", volumeGuid);

    // ===== 第 4 步: 创建 AMFS 目录结构 =====
    // AMFS = AM File System (SEGA 自定义游戏数据文件系统)
    CreateDirectoryW(L"C:\\AMFS", NULL);
    // 创建 AMFS 子目录结构
    amfsPath = SetAmfsPathTask(L"C:\\AMFS");

    // ===== 第 5 步: 验证 =====
    CheckSequencer@volume@checkdisk
        → CheckBitLockerTask      // BitLocker 是否关闭?
        → ValidateVolumeTask      // 卷是否可读?
        → DirtyCheckTask          // 是否有脏数据?

    // ===== 第 6 步: 设置卷标 =====
    SetVolumeLabelW(L"C:\\", L"SEGA_MAIN");

    CloseHandle(hVolume);
    return AMS_STATUS_OK;
}

副盘 (Sub Storage) 初始化流程

物理路径: \\.\Q:\\\.\Q:

Q: 盘是什么? Q: 不是 Windows 标准盘符。它是 SEGA 系统专有的游戏数据分区/卷。可能是:

  • 物理硬盘上划出的独立分区
  • 挂载的 VHD (虚拟硬盘文件)
  • RAM Disk (内存盘,用于游戏缓存)
  • AMFS 格式化的专用数据卷

触发途径:

  • 系统测试菜单 → 选择”初始化副盘”
  • sgxsegaboot 发送 SubStorageFormatCommand → daemon 的 Worker 执行
  • 或直接从 systemtest 调用 FormatSubStorageTask
  • 在 initialboot 时由 FindTask@substorage 自动发现

执行逻辑:

AMS_STATUS SubStorageFormatCommand::execute() {
    // ===== 第 1 步: 发现副盘设备 =====
    // 方法 1: QueryDosDeviceA 查询 Q: 对应的物理设备
    CHAR targetPath[MAX_PATH];
    QueryDosDeviceA("Q:", targetPath, MAX_PATH);
    // → 返回值: "\Device\HarddiskVolumeN"

    // 方法 2: FindTask@substorage@initialboot
    // 遍历所有卷,寻找符合条件的副盘
    HANDLE hFind = FindFirstVolumeW(volumeGuid, MAX_PATH);
    while (hFind) {
        GetVolumePathNamesForVolumeNameW(guid, paths, ...);
        // 匹配特征: 特定分区大小/标签/类型
        if (IsSubStorageCandidate(guid)) {
            found = guid;
            break;
        }
        FindNextVolumeW(hFind, volumeGuid, MAX_PATH);
    }

    // ===== 第 2 步: 打开副盘设备 =====
    // 使用 CreateFileA (ANSI 版本) 打开
    HANDLE hDevice = CreateFileA(
        "\\\\.\\Q:",                // 或 "\\.\\Q:\\"
        GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL, OPEN_EXISTING, 0, NULL);
    // → 失败: "Error: CreateFileA %08x"

    // ===== 第 3 步: 锁定并卸载 =====
    DeviceIoControl(hDevice, FSCTL_LOCK_VOLUME, ...);
    DeviceIoControl(hDevice, FSCTL_DISMOUNT_VOLUME, ...);
    // → "Error : Cannot Unmount Q: (LastError = %d).\n"

    DeleteVolumeMountPointA("Q:\\");

    // ===== 第 4 步: 格式化 =====
    // 方式: 通过 DeviceIoControl + WriteFile 创建 AMFS 格式
    // 或用标准 Windows 格式化接口
    FormatSubStorageTask::execute() {
        // 1. 计算副盘大小
        GetDiskFreeSpaceExW(L"Q:\\", ...);

        // 2. 清理旧数据
        SetFilePointerEx(hDevice, 0, NULL, FILE_BEGIN);
        SetEndOfFile(hDevice);

        // 3. 写 AMFS 文件系统元数据
        // → AMFS_FILE_HEADER
        // → AMFS_ROOT_DIRECTORY
        // → AMFS_ALLOCATION_TABLE
        // 所有这些通过 WriteFile 直接写入设备

        // 4. 创建根目录结构
        CreateDirectoryW(L"Q:\\AMFS", NULL);
        CreateDirectoryW(L"Q:\\APPDATA", NULL);
        CreateDirectoryW(L"Q:\\GAME", NULL);
        CreateDirectoryW(L"Q:\\BACKUP", NULL);
    }

    // ===== 第 5 步: 重建挂载 =====
    SetVolumeMountPointA("Q:\\", volumeGuid);

    // ===== 第 6 步: 验证 =====
    GetVolumeInformationW(L"Q:\\", ...);
    // 验证卷可读、空间正确

    CloseHandle(hDevice);
    return AMS_STATUS_OK;
}

初始化执行流程

首次启动 (initialboot):
  Sequencer@disk@startup
    ├── GetSizeTask          → 获取主盘和副盘大小
    ├── SetAmfsPathTask      → 设置 AMFS 路径
    └── FindTask@substorage  → 发现副盘

  CheckSequencer@volume@checkdisk
    ├── CheckBitLockerTask   → 检查 BitLocker (必须关闭)
    ├── ValidateVolumeTask   → 验证卷完整性
    └── DirtyCheckTask       → 检查脏状态

用户主动操作 (系统测试菜单):
  "初始化主盘" → MainStorageFormatCommand
    → 锁定 C: → 卸载 C: → 格式化为 NTFS → 重建 AMFS 目录

  "初始化副盘" → SubStorageFormatCommand
    → 锁定 Q: → 卸载 Q: → 格式化为 AMFS → 重建游戏数据目录

接下来就是关于为什么我反复重启的原因分析,我的猜测可能是这样的。

现象:

正常启动 → STEP30 → 突然关机重启

再次开机 → 触发初始化主盘 → 格式化

格式化完成 → 自动重启

正常启动 → 游戏运行 → 突然关机重启

        ⋮  (无限循环)

CheckSequencer@volume@checkdisk@initialboot 的 RTTI 和 API 调用链还原的磁盘检查逻辑:

// 伪代码还原 — 初装引导时的磁盘检查
CheckSequencer::execute(Volume* volume) {
    bool triggerFormat = false;

    // ===== 第一步: 检查 BitLocker =====
    if (CheckBitLockerTask::execute(volume) == LOCKED) {
        // BitLocker 加密开启 → 无法读取
        // 街机系统不需要 BitLocker
        triggerFormat = true;
        log("BitLocker detected on %S, format required", volume->path);
    }

    // ===== 第二步: 检查脏位 =====
    if (DirtyCheckTask::execute(volume) == DIRTY) {
        // 卷的脏位被设置 → 上次是非正常卸载
        // ★ 注意: 不运行 chkdsk, 不尝试修复
        // ★ 直接触发格式化
        triggerFormat = true;
        log("Dirty bit set on %S, format required", volume->path);
    }

    // ===== 第三步: 验证卷完整性 =====
    if (ValidateVolumeTask::execute(volume) == INVALID) {
        // 文件系统元数据损坏 / 不一致
        triggerFormat = true;
        log("Volume %S is invalid, format required", volume->path);
    }

    // ===== 决策 =====
    if (triggerFormat) {
        // 不是修复,是直接重建
        MainStorageFormatCommand::execute();
        // 格式化后重启
        ExitCommand::execute(1);  // 带重启参数退出
    }
}

// 脏位检测的实现 (DirtyCheckTask)
DirtyCheckTask::execute(Volume* volume) {
    // 方式一: 通过 GetVolumeInformation 检查卷状态
    DWORD flags = 0;
    GetVolumeInformationW(volume->path, NULL, 0,
        NULL, NULL, &flags, NULL, 0);

    // 检查 VOLUME_IS_DIRTY 标志
    if (flags & FILE_VOLUME_IS_DIRTY) {
        return DIRTY;  // → 触发格式化
    }

    // 方式二: 直接写测试文件测读写
    HANDLE hTest = CreateFileW(
        L"C:\\AMFS\\_chk.tmp",
        GENERIC_READ | GENERIC_WRITE, 0,
        NULL, CREATE_ALWAYS, 0, NULL);
    if (hTest == INVALID_HANDLE_VALUE) {
        // 无法创建测试文件 → 卷有问题
        return DIRTY;
    }
    // 写入并验证
    WriteFile(hTest, testData, ...);
    FlushFileBuffers(hTest);
    // 验证写入成功
    CloseHandle(hTest);
    DeleteFileW(L"C:\\AMFS\\_chk.tmp");

    return CLEAN;  // 卷正常
}
CheckSequencer (initialboot)

    ├── Step 1: CheckBitLockerTask
    │   → 检查卷是否有 BitLocker 加密
    │   → 如果加密 → 触发格式化

    ├── Step 2: DirtyCheckTask     ← ★ 关键
    │   → 检查卷是否是"脏的"(非正常卸载)
    │   → 如果脏 → 直接触发格式化 (不修复)

    ├── Step 3: ValidateVolumeTask
    │   → 验证文件系统完整性
    │   → 如果不一致 → 触发格式化

    └── 任意一步触发 → MainStorageFormatCommand → 重启

其实也有可能是这样造成的无限重启

循环链完整还原:

                     ┌──────────────────┐
                     │  游戏/系统异常退出  │
                     │  (原因待动态确定)   │
                     └────────┬─────────┘


                    Windows 非正常关机


               ntfs.sys 设置卷脏位
               (标准 Windows 行为)


               DirtyCheckTask 读到脏位
               → CHECK_DIRTY


               CheckSequencer → 触发格式化


               MainStorageFormatCommand
               → 格式化 C: → 重启

                              └──→ 回到开头 (循环)

存储格式化的 PowerShell 脚本分析

SEGA ALLS 系统的「初始化主盘」和「初始化副盘」不是由 C++ daemon 直接完成的,而是通过 daemon 触发 PowerShell 脚本链来实现。整个过程分 Deploy(部署)Apply(应用) 两个阶段,中间通过一次系统服务模式重启来隔离。

也就是开头提到的sega目录有一些apply那些东西。

流程如下

sgxsegaboot → 选择格式化

Worker 命令 → sgxsystemdaemon

deploy\deploy.bat → deploy\*.ps1     ← 第 1 阶段: 复制脚本到 N: 分区

EnterSystemServicingMode → 重启        ← 进入系统服务模式

N:\apply.bat → N:\apply.ps1          ← 第 2 阶段: 实际执行格式化

退出服务模式 → 正常重启

主盘初始化流程

deploy\sgMainstorageFormatDeploy.ps1
#
# Copyright(C)SEGA
#
$version = "0.02"

# 主存储格式化的触发处理
$commonPath = "C:\windows\sega\System\"

pushd $commonPath

# 汎用处理读取
. .\sgSystemServicingCommon.ps1

# N 驱动器マウント
$bRet = UnlockBitLocker "N" "$commonPath\system_key.txt"
if (!$bRet)
{
    exit 1
}

# N 驱动器ファイル全删除
Remove-Item N:\* -Recurse

# ファイル复制
Copy-Item ".\mainstorage\apply\*" "N:\" -Recurse
if (Test-Path D:\mainstorage.json) {
	Move-Item "D:\mainstorage.json" "N:\"
}

# システムサービシングモード遷移 + 重启
$bRet = EnterSystemServicingMode "mainstorageformat"
if (!$bRet)
{
    exit 1
}

exit 0

关键操作:

  1. UnlockBitLocker("N") — 用 system_key.txt 解锁 N: 分区的 BitLocker
  2. Remove-Item N:\* — 清空 N: 分区
  3. Copy-Item .\mainstorage\apply\* N:\ — 把 apply 脚本拷贝到 N:
  4. Move-Item D:\mainstorage.json N:\ — 把配置 JSON 移到 N:
  5. EnterSystemServicingMode("mainstorageformat") — 注册服务模式并重启
apply\sgMainstorageFormatApply.ps1
#
# Copyright(C)SEGA
#
$version = "1.00"

# 主存储格式化本处理
# 主存储のAMFS/AppDataを格式化する
# 副存储が接続されているときは初期处理は実行しない
# 之后可以更改此容量设置

# 针对目标驱动器的 BitLocker 设置
function EnableBitLocker([string]$letter) 
{
    $bRet = EnableAutoUnlockBitLocker $letter
    if (!$bRet) {
        return $false
    }
    return $bRet
}

$commonPath = "C:\windows\sega\System\"
pushd $commonPath

. .\sgSystemServicingCommon.ps1

# 获取主存储编号
$main = GetDiskNumber "C"
if ($main -eq -1){
    # 主存储が見つからず
    # 正常启动
    LeaveSystemServicingModeNextBoot | out-null
    # ERROR 0085 を显示
    exit 85
}

# 副存储搜索 (容量 256GB 以上)
$subs = GetHddDiskNumber 256

# 主存储を除外
$subs = $subs | ? {$_ -ne $main}
if ($subs -ne $null) 
{
    # 有副存储时不执行任何操作
    exit 0
}

# JSON 设置读取
$ExpectedMinimumStorageSizeGB = 64
$AmfsSizeGB = 45
if (Test-Path N:\mainstorage.json) {
    $mainStorageSettings = get-content N:\mainstorage.json | ConvertFrom-Json
    if ($mainStorageSettings -eq $null) {
        LeaveSystemServicingModeNextBoot | out-null
        exit 85
    }
    if ($mainStorageSettings.ExpectedMinimumStorageSizeGB -ne $null) {
        $ExpectedMinimumStorageSizeGB = $mainStorageSettings.ExpectedMinimumStorageSizeGB
    }
    if ($mainStorageSettings.AmfsSizeGB -ne $null) {
        $AmfsSizeGB = $mainStorageSettings.AmfsSizeGB
    }
}

# 异常值对策
if ($ExpectedMinimumStorageSizeGB -eq 0) {
    $ExpectedMinimumStorageSizeGB = 64
    $AmfsSizeGB = 45
}
if (($AmfsSizeGB -eq 0) -or ($ExpectedMinimumStorageSizeGB -lt $AmfsSizeGB)) {
    LeaveSystemServicingModeNextBoot | out-null
    exit 85
}

# 分区の设置のパス
$path = "HKLM:\SYSTEM\SEGA\SYSTEMPROPERTY\STATIC"
$generation = Get-ItemProperty $path -name "PlatformIdGeneration"

if ($generation.PlatformIdGeneration -ge 2)
{
    # === ALLS 第 2 代及以后: 动态分区 ===
    $cnt_file = "C:\cnt.txt"
    :MAIN_LABEL do
    {
        $bRet = AdjustmentMainStorage 0 ($AmfsSizeGB * 1024) "D" "Y"
        if (!$bRet) { break }

        $bRet = EnableAutoUnlockBitLocker "Y"
        if (!$bRet) { break }

        # 确认盘符
        $LetterList = @("D", "N", "P", "Y")
        foreach ($Letter in $LetterList) {
            $AssignRet = Get-Partition -diskNumber $main |
                Where-Object{$_.DriveLetter -eq $Letter}
            if ($AssignRet -eq $null) {
                $bRet = $false
                break MAIN_LABEL
            }
        }
    } while ($false)

    if ($bRet) {
        . chkntfs /x C: D: N: Y: P:
        Remove-Item $cnt_file
    } else {
        if (isRetry $cnt_file 5) {
            IncrementRetryCount $cnt_file
            exit -1          # 重试
        } else {
            Remove-Item $cnt_file
            exit 84          # ERROR 0084
        }
    }
}
else
{
    # === ALLS 第 1 代: 固定格式化 ===
    # 快速格式化 D
    $AmfsLetter = (get-volume | ? {$_.DriveLetter -eq "D"} ).DriveLetter
    Format-Volume $AmfsLetter -FileSystem NTFS -NewFileSystem AMFS -Force

    # 快速格式化 Y
    $AppdataLetter = (get-volume | ? {$_.DriveLetter -eq "Y"} ).DriveLetter
    Format-Volume $AppdataLetter -FileSystem NTFS -NewFileSystem AppData -Force

    # 启用 BitLocker
    $bRet = EnableBitLocker "Y"
    if (!$bRet) {
        LeaveSystemServicingModeNextBoot | out-null
        exit 84
    }

    . chkntfs /x C: D: N: Y:
}

exit 0

关键逻辑分析:

行范围逻辑说明
38-45GetDiskNumber("C")找到系统盘的主磁盘号
48-56检查 ≥256GB 副盘是否存在如果有副盘,主盘格式化跳过! 这是设计约束
58-82读取 N:\mainstorage.json动态配置: 磁盘最小容量, AMFS 分区大小
89-95检查 PlatformIdGeneration第 1 代 vs 第 2 代分区布局不同
97-122第 2 代: 动态分区调用 AdjustmentMainStorage 调整 D:/Y:/N:/P:
130-143第 1 代: 固定格式化快速格式化 D:→AMFS, Y:→AppData, 启用 BitLocker
125, 141chkntfs /x C: D: N: Y: P:关闭所有系统盘的 chkdsk

错误码映射:

Error场景
0084主盘格式化失败 (重试 5 次后)
0085主盘未找到 / JSON 配置无效 / AMFS 大小异常
exit -1重试中 (重启后继续)

副盘初始化流程

deploy\sgSubstorageFormatDeploy.ps1
$version = "0.02"

# 副存储格式化的触发处理
$commonPath = "C:\windows\sega\System\"
pushd $commonPath
. .\sgSystemServicingCommon.ps1

$bRet = UnlockBitLocker "N" "$commonPath\system_key.txt"
if (!$bRet) { exit 1 }

Remove-Item N:\* -Recurse

Copy-Item ".\substorage\apply\*" "N:\" -Recurse
if (Test-Path D:\substorage.json) {
    Move-Item "D:\substorage.json" "N:\"
}

# T:驱动器に配置したシステムファイルをD:驱动器に复制する
copyTDriveSystemFileToDDrive

$bRet = EnterSystemServicingMode "subStorageFormat"
if (!$bRet) { exit 1 }

exit 0
apply\sgSubstorageFormatApply.ps1
$version = "0.02"

$commonPath = "C:\windows\sega\System\"
pushd $commonPath
. .\sgSystemServicingCommon.ps1

$main = GetDiskNumber "C"
if ($main -eq -1) {
    LeaveSystemServicingModeNextBoot | out-null
    exit 86
}

# JSON 设置
$ExpectedMinimumStorageSizeGB = 256
$AmfsSizeGB = 128
if (Test-Path N:\substorage.json) {
    $subStorageSettings = get-content N:\substorage.json | ConvertFrom-Json
    if ($subStorageSettings -eq $null) {
        LeaveSystemServicingModeNextBoot | out-null
        exit 86
    }
    if ($subStorageSettings.ExpectedMinimumStorageSizeGB -ne $null) {
        $ExpectedMinimumStorageSizeGB = $subStorageSettings.ExpectedMinimumStorageSizeGB
    }
    if ($subStorageSettings.AmfsSizeGB -ne $null) {
        $AmfsSizeGB = $subStorageSettings.AmfsSizeGB
    }
}

# 异常值对策
if ($ExpectedMinimumStorageSizeGB -eq 0) {
    $ExpectedMinimumStorageSizeGB = 256
    $AmfsSizeGB = 128
}
if (($AmfsSizeGB -eq 0) -or ($ExpectedMinimumStorageSizeGB -lt $AmfsSizeGB)) {
    LeaveSystemServicingModeNextBoot | out-null
    exit 86
}

# 副存储を搜索 (容量 256GB 以上)
$subs = GetHddDiskNumber $ExpectedMinimumStorageSizeGB

# 主存储を除外
$subs = $subs | ? {$_ -ne $main}
if ($subs -eq $null) {
    LeaveSystemServicingModeNextBoot | out-null
    exit 86
}

# 将最先找到的作为副存储
$sub = $subs[0]

$path = "HKLM:\SYSTEM\SEGA\SYSTEMPROPERTY\STATIC"
$generation = Get-ItemProperty $path -name "PlatformIdGeneration"

# 执行格式化
$bRet = FormatSubStorage $sub ($AmfsSizeGB * 1024) "T" "Y"
if (!$bRet) {
    RemoveDriveLetter "Y" | out-null
    if ($generation.PlatformIdGeneration -ge 2) {
        AssignLetter $main 3 "D" | out-null
        AssignLetter $main 4 "Y" | out-null
    } else {
        AssignLetter $main 3 "D" | out-null
        AssignLetter $main 5 "Y" | out-null
    }
    LeaveSystemServicingModeNextBoot | out-null
    exit 86
}

# 应用 BitLocker
$bRet = EnableAutoUnlockBitLocker "Y"
if (!$bRet) {
    RemoveDriveLetter "Y" | out-null
    if ($generation.PlatformIdGeneration -ge 2) {
        AssignLetter $main 3 "D" | out-null
        AssignLetter $main 4 "Y" | out-null
    } else {
        AssignLetter $main 3 "D" | out-null
        AssignLetter $main 5 "Y" | out-null
    }
    LeaveSystemServicingModeNextBoot | out-null
    exit 86
}

# 确认盘符
if ($generation.PlatformIdGeneration -ge 2) {
    $LetterList =@(
        @{"Letter" = "D"; "Disk" = $main},
        @{"Letter" = "N"; "Disk" = $main},
        @{"Letter" = "P"; "Disk" = $main},
        @{"Letter" = "T"; "Disk" = $sub},
        @{"Letter" = "Y"; "Disk" = $sub}
    )
} else {
    $LetterList =@(
        @{"Letter" = "D"; "Disk" = $main},
        @{"Letter" = "N"; "Disk" = $main},
        @{"Letter" = "T"; "Disk" = $sub},
        @{"Letter" = "Y"; "Disk" = $sub}
    )
}

$cnt_file = "C:\cnt.txt"
# 重試機制 (5 次)
foreach ($Letter in $LetterList) {
    $AssignRet = Get-Partition -diskNumber $Letter.Disk |
        Where-Object{$_.DriveLetter -eq $Letter.Letter}
    if ($AssignRet -eq $null) {
        if (isRetry $cnt_file 5) {
            IncrementRetryCount $cnt_file
            exit -1
        } else {
            # 错误後处理...
            exit 86
        }
    }
}
Remove-Item $cnt_file

if ($generation.PlatformIdGeneration -ge 2) {
    . chkntfs /x C: D: T: N: Y: P:
} else {
    . chkntfs /x C: D: T: N: Y:
}

# 复制系统文件
Copy-Item D:\sysfile.dat T:\sysfile.dat -Force

# 電子マネーフォルダ复制
if (Test-Path D:\emoney\) {
    Copy-Item D:\emoney\ T:\emoney\ -Recurse -Force
}

exit 0

关键逻辑分析:

行范围逻辑说明
14-16GetDiskNumber("C")找到主盘 (排除主盘就是副盘)
19-41读取 N:\substorage.json副盘最小≥256GB, AMFS 分区 128GB
44-49GetHddDiskNumber(256)扫描系统上所有 ≥256GB 的硬盘
52-56排除主盘第一个非主盘的就是副盘
64FormatSubStorage($sub, 128*1024, "T", "Y")分区副盘为 T:+Y:
101-121$LetterList第 2 代有 P: 分区, 第 1 代没有
124-137重试机制5 次重试, 超过则 ERROR 0086
145-150文件复制sysfile.dat + emoney\ 从 D: 到 T:

错误码映射:

Error场景
0086副盘未找到 / 容量不足 / 格式化失败 / BitLocker 失败

恢复流程 (Recovery)

# deploy\deploy.ps1
$version = "0.01"
$commonPath = "C:\windows\sega\System\"
pushd $commonPath
. .\sgSystemServicingCommon.ps1

# SSD のライトプロテクトをアンロック
UnlockWriteProtection

# N 驱动器マウント
$bRet = UnlockBitLocker "N" "$commonPath\system_key.txt"
if (!$bRet) { exit 1 }

Remove-Item N:\* -Recurse

# recovery\apply を N: に复制
Copy-Item "C:\Windows\SEGA\System\recovery\apply\*" "N:\" -Recurse

# レジストリキーをコミット (UWF 保護下のレジストリ変更を確定)
$reg_key = "HKLM:\SYSTEM\SEGA\SystemProperty\SystemServicing\recovery"
$reg_names = @("dirty", "assign", "BitLocker", "bFindSubStorage")
foreach($name in $reg_names) {
    $bRet = UwfCommitRegistry $reg_key $name
    if (!$bRet) { exit 1 }
}

$bRet = EnterSystemServicingMode "mainStorageFormat"
if (!$bRet) { exit 1 }
exit 0

恢复流程关键操作:

  1. UnlockWriteProtection — 解锁 SSD 写保护 (UWF)
  2. 提交 4 个注册表键 UwfCommitRegistry — 将 dirty/assign/BitLocker/bFindSubStorage 状态写入持久化注册表
  3. 进入服务模式 → 格式化主盘 → 从副盘 T: 恢复数据

devcon

PDB 路径指向 Microsoft WDK 标准编译产物,非 SEGA 自定义开发,似乎使用来安装驱动程序的东西。

ALLS 系统中的具体调用场景

devcon install sgdongle.inf                ← SG 加密狗驱动(USB 硬件识别)
devcon install sgkeychip.inf               ← Keychip 驱动(USB 加密狗)
devcon install sgfscrypt.inf               ← 文件系统加密驱动
devcon install sghwmonitor.inf             ← 硬件监控驱动(温度/电压)
devcon install sghwreset.inf               ← 硬件复位驱动
devcon install sgprotect.inf               ← 保护驱动
devcon install hdd.inf / ssd.inf           ← 存储驱动

关键 API

API功能
CM_Reenumerate_DevNode_Ex硬件重新扫描
SetupDiCallClassInstaller触发 DIF 安装/移除动作
SetupCopyOEMInfW复制驱动包到驱动存储
SetupUninstallOEMInfW从驱动存储移除驱动包
UpdateDriverForPlugAndPlayDevicesW更新 PnP 设备驱动
CM_Locate_DevNode_ExW按硬件 ID 查找设备
CM_Get_DevNode_Status_Ex查询设备状态
CM_Connect_MachineW远程管理设备

pxSecureAtaCmd

用来存储设备写保护管理的小玩意,核心功能就是给驱动器写保护,然后解锁。

调用链为:

recovery/deploy/deploy.ps1:
    UnlockWriteProtection              ← PowerShell 函数
      → 调用 pxSecureAtaCmd.exe
        → tdkgs1::unlockWriteProtect  ← 实质操作
          → CreateFileW(\.\...)
          → DeviceIoControl(IOCTL_...)
          → CryptEncrypt / CryptImportKey

系统恢复流程的第一行就是 UnlockWriteProtection——先解锁硬件写保护,才能进行后续的格式化/重装操作。

与 sgmanageio 驱动的关系

sgmanageio.exe 安装的驱动层提供 IOCTL 接口,pxSecureAtaCmd.exe 通过 CreateFileW + DeviceIoControl 与这个驱动通信,实现对存储硬件的写保护控制。

写这么多保护不如神人灵机一动

sgimagemount

用于挂载虚拟磁盘镜像的东西,似乎使用来处理类似于app,opt这种游戏文件的程序?

支持的命令

MOUNT <target> [OPTION=value] [VHDPARAM=value]   ← 挂载镜像
UNMOUNT <GUID> [OPTION=value]                     ← 卸载镜像

三种挂载方式

① VhdMounter (标准 VHD):
   OpenVirtualDisk → AttachVirtualDisk → GetVirtualDiskPhysicalPath
   → 挂载 VHD/VHDX 文件为虚拟磁盘 → 分配盘符

② FscryptMounter (加密镜像):
   BCryptOpenAlgorithmProvider(L"AES")
   BCryptGenerateSymmetricKey → 解密镜像数据
   CreateFile → DeviceIoControl → 挂载解密后的文件系统

③ ImageMounter (通用):
   → 根据文件扩展名自动选择挂载方式
   → 管理挂载点和盘符

通信方式

父进程 (daemon / sglaunch):
  │ 创建 mailslot
  │ 发送 MOUNT/UNMOUNT 命令

sgimagemount.exe:
  ├── CreateMailslotW → 等待命令
  ├── 执行操作
  └── SetEvent → 通知父进程完成
      └── ResultSender → 通过 mailslot 返回结果

在 ALLS 系统中的位置

daemon 的 MountTask@mount:
  → sgimagemount.exe MOUNT <game_image.vhd>
    → VhdMounter → OpenVirtualDisk → AttachVirtualDisk
    → 分配盘符 (通过 DefineDosDeviceW + SetVolumeMountPointW)
    → 通知 daemon 完成

daemon 的 UnmountTask:
  → sgimagemount.exe UNMOUNT <guid>
    → DetachVirtualDisk → 释放盘符

sglaunch

用来启动/监控游戏的程序,没啥好说的。

sgxsegaboot 完成 Step 30-31 (启动完成)

sglaunch.exe
  ├── 1. 解析 sglaunch.json
  │   ├── 游戏可执行文件路径
  │   ├── 启动参数
  │   ├── 工作目录
  │   ├── 运行用户 (可能通过 CreateProcessWithLogonW)
  │   └── 退出后行为 (关机/重启/返回系统测试)

  ├── 2. 创建互斥体 (单实例)

  ├── 3. CreateProcessW 启动游戏进程
  │   └── 可选: CREATE_SUSPENDED → 准备完成后 ResumeThread

  ├── 4. WaitForSingleObject(hProcess, INFINITE)
  │   ├── 正常退出 → GetExitCodeProcess → 处理退出码
  │   └── 异常退出 → 记录错误到 SEGA_AM_LOG 事件

  └── 5. 退出后决策
      ├── 退出码 = 0 → 正常关机
      ├── 退出码 = 1 → 重启系统
      └── 退出码 = 其他 → 可能触发错误处理

sgmanageio

似乎是一个安装驱动程序的玩意.

安装的驱动列表

驱动名INF 文件SYS 文件功能
SG Donglesgdongle.infsgdongle.sysUSB 加密狗设备驱动
SG Keychipsgkeychip.infsgkeychip.sysKeychip USB HID 通信
SG File System Cryptosgfscrypt.infsgfscrypt.sys文件系统加解密过滤驱动
SG Hardware Monitorsghwmonitor.infsghwmonitor.sys主板温度/电压/风扇监控
SG Hardware Resetsghwreset.infsghwreset.sys硬件复位控制
SG Protectionsgprotect.infsgprotect.sys系统进程/文件保护
存储hdd.inf / ssd.infhdd.sys / ssd.sysHDD/SSD 设备驱动

调用时机

系统初始化期间由 0001_StandardCommon_058 包的 install.batdriver.bat 调用一次,安装完成后不再参与系统运行。

sgosupdate

用来安装系统更新的工具,当且仅当进入 Update Mode 时被调用。

在 ALLS 系统中的位置

daemon 的 Sequencer@modeselect@startup
  → Update Mode 被选中
    → UpdateTask@os@modeselect@execution
      → sgosupdate.exe
        → 安装 OS 更新包 (驱动、系统组件、安全补丁)
        → 完成后重启

sgxsystemservicing

似乎是用来管理程序的窗口和显示工具的小玩意,用于配置显示器分辨率和多屏。

不做过多阐述。

sgSystemServicingMasterScript.ps1

系统进入服务模式后第一个执行的脚本。它决定下一步执行哪个服务操作。

执行流程

sgSystemServicingMasterScript.ps1

  ├── 1. 清除旧的错误计数:
  │     HKLM\...\SystemServicing\error_number = 0
  │     HKLM\...\SystemServicing\total_pack = 0

  ├── 2. 设置显示分辨率:
  │     sgsetresolution.exe

  ├── 3. 启动服务界面:
  │     sgxsystemservicing.exe

  ├── 4. 检查退出标志:
  │     if (IsLeaveSystemServicingMode) → LeaveSystemServicingMode; exit 0

  ├── 5. 解锁 N: 分区:
  │     UnlockBitLocker("N", "system_key.txt")
  │     失败 → ErrorFlow(500)

  ├── 6. 执行服务脚本:
  │     N:\apply.bat
  │     │
  │     ├── exit 0  → LeaveSystemServicingMode (正常完成)
  │     ├── exit -1 → RestartSystemServicingMode (重启重试)
  │     └── default → ErrorFlow(exit code) (显示错误)

  └── exit 0

sgSystemServicingCommon.ps1

被所有格式化/恢复脚本引用的公共函数库。

函数列表

BitLocker 管理

UnlockBitLocker(letter, path)
  → manage-bde -unlock ${letter}: -RecoveryKey ${path}
  → exit code ≠ 0 → return false

EnableAutoUnlockBitLocker(letter)
  → manage-bde -autounlock -enable ${letter}:   ← 启用自动解锁
  → manage-bde -on ${letter}: -used -sync       ← 开启 BitLocker
  → exit code -2144272353 → 已应用,视为成功

服务模式切换

EnterSystemServicingMode(mode)
  → UnlockWriteProtection             ← pxSecureAtaCmd -u 0
  → DisableWriteProtection             ← pxSecureAtaCmd -d 0
  → 更改 Winlogon DefaultUser = "UpdateUser"  ← 切换到更新用户
  → UwfCommitRegistry(winlogon key)    ← 提交到 UWF
  → UwfCommitRegistry(mode)            ← 保存 mode
  → UWF_Volume.Unprotect("C:")         ← 关闭 C: 保护
  → RestartSystemServicingMode         ← 重启

LeaveSystemServicingMode()
  → RemoveUsbCOM                        ← 移除 USB-COM 端口
  → RemoveTaskTrayInformation           ← 清理任务栏
  → UWF_Volume.Protect("C:")           ← 重新启用 C: 保护
  → RestartSystemServicingMode

RestartSystemServicingMode()
  → UWF_Filter.RestartSystem()

IsLeaveSystemServicingMode()
  → 检查 C:\...\LeaveSystemServicingMode.txt 是否存在

LeaveSystemServicingModeNextBoot()
  → 创建标志文件 → 下次启动时退出服务模式

ChangeDefaultUserIfLeaveSystemServicingModeDone()
  → Winlogon DefaultUser = "AppUser"
  → pxSecureAtaCmd -e 0 (启用写保护)

磁盘操作

GetDiskNumber(letter)
  → WMI: Win32_LogicalDiskToPartition → 解析 Disk #N → 返回磁盘号

GetHddDiskNumber(size)
  → WMI: Win32_DiskDrive MediaType=Fixed + Size ≥ size×GB
  → 返回所有符合条件的磁盘号列表

AssignLetter(diskNumber, partitionNumber, letter)
  → Get-Partition → Remove-PartitionAccessPath → Add-PartitionAccessPath

RemoveDriveLetter(letter)
  → Get-Partition → Remove-PartitionAccessPath

ChangeDriveLetter(srcLetter, dstLetter)
  → WMI: Win32_Volume → 修改 DriveLetter

FormatSubStorage(diskNumber, amfsSizeMB, amfsLetter, appDataLetter)
  → Clear-Disk                       ← 清空磁盘
  → Initialize-Disk -GPT             ← GPT 分区表
  → New-Partition -Size amfsSizeMB -DriveLetter $amfsLetter
    → Format-Volume NTFS "AMFS"       ← 副存储分区
  → New-Partition -UseMaximumSize -DriveLetter $appDataLetter
    → Format-Volume NTFS "AppData"    ← 应用数据分区
  → return true/false

AdjustmentMainStorage(diskNumber, amfsSizeMB, amfsLetter, appDataLetter)
  → Remove-Partition AMFS + AppData   ← 删除旧分区
  → New-Partition AMFS               ← 重建 AMFS 分区
    → Format-Volume NTFS "AMFS"
  → New-Partition AppData            ← 重建 AppData 分区
    → Format-Volume NTFS "AppData"
  → return true/false

写保护控制 (pxSecureAtaCmd 包装)

EnableWriteProtection()
  → pxSecureAtaCmd -e 0              ← 启用 TDK GS1 写保护

DisableWriteProtection()
  → pxSecureAtaCmd -d 0              ← 禁用 TDK GS1 写保护

UnlockWriteProtection()
  → pxSecureAtaCmd -u 0              ← 解锁 TDK GS1 写保护

UWF 注册表提交

UwfCommitRegistry(key, name)
  → WMI: UWF_RegistryFilter.CommitRegistry(key, name)
  → 将注册表更改强制写入 (绕过 UWF 写过滤)
  → 用于持久化 Winlogon、mode、error 等配置

USB-COM 端口清理

RemoveUsbCOM()
  → devcon findall =ports              ← 列出所有 COM 端口
  → 筛选 COM20-COM99 (USB-COM 设备)
  → devcon remove @COMxx               ← 删除每个 USB-COM 端口
  → 重试 3 次直到 FTDIBUS 枚举清除

错误处理

ErrorFlow(num)
  → HKLM\...\SystemServicing\error_number = num
  → while(1) { Sleep 3600 }    ← 无限等待

重试机制

isRetry(cnt_file, max_cnt)
  → 读取 C:\cnt.txt → 比较计数 ≤ max_cnt

IncrementRetryCount(cnt_file)
  → 计数 +1 → 写回文件

函数调用关系图 (格式化场景)

sgSystemServicingMasterScript.ps1
  → sgSystemServicingCommon.ps1 (通用函数)
  → sgsetresolution.exe (设置分辨率)
  → sgxsystemservicing.exe (服务界面)
  → BitLocker 解锁
  → N:\apply.bat
    → sgMainstorageFormatApply.ps1
      → AdjustmentMainStorage → Format-Volume AMFS + AppData
      → EnableAutoUnlockBitLocker
      → chkntfs /x C: D: N: Y: P:

sgsystemservicingshell

用来以不同用户权限执行服务脚本的小程序,感觉没啥用?

其次是监控了脚本执行状态。

与 sgxsystemservicing.exe 的分工

sgxsystemservicing.exe (服务引擎)
  ├── 创建窗口 + 消息循环
  ├── 管理显示器分辨率
  └── 提供 UI 界面

sgxsystemservicingshell.exe (服务 Shell)
  ├── 以指定用户身份启动服务脚本
  ├── 监控脚本执行状态
  └── 管理安全上下文

两者在系统服务模式启动时由 sgSystemServicingMasterScript.ps1 协同调用:

# 服务主脚本调用:
. start C:\windows\sega\system\sgxsystemservicing.exe    # 启动 UI
# N:\apply.bat 内部调用:
N:\apply.batsgsystemservicingshell.exe 形式

sgxdownloadui

更新下载ui?

主要依赖于util_downlaodui.dll

sgxmaster

系统的进程管理器,类似于/sbin/init的身份?

进程生命周期管理

Windows 启动 → 自动登录 AppUser

sgxmaster.exe (主进程管理器)
  ├── CreateMutexW (单例保护)

  ├── ① 启动 sgxprestartup.exe ? (预启动检查)

  ├── ② 启动 sgxsegaboot.exe + sgxsystemdaemon.exe
  │   │                        (启动 UI)           (后端引擎)
  │   └── WaitForSingleObject (等待完成)

  ├── ③ 正常模式: 启动 sglaunch.exe (游戏启动器)
  │   │   └── WaitForSingleObject (监控游戏)
  │   └── 退出后 → InitiateShutdownW / 重启

  └── 服务模式: 启动 sgsystemservicingshell.exe (服务 Shell)
      └── 格式化/恢复/更新完成后重启

sgxprestartup

sgxmaster 同一套代码框架?

Windows 启动 → 自动登录 AppUser

sgxprestartup.exe      ← ★ 预启动检查
  │  - 检查系统状态
  │  - 决定进入正常启动还是服务模式
  │  - ExitProcess

sgxmaster.exe           ← 进程管理器
  ├── sgxsegaboot.exe   ← 启动 UI
  ├── sgxsystemdaemon.exe ← 后端引擎
  ├── sglaunch.exe      ← 游戏启动器
  └── sgsystemservicingshell.exe ← 服务模式 Shell

先于sgxmaster 启动

sgsetresolution

用于管理分辨率的工具

没啥好介绍的。

sgsetdisplaysetting

同上 一个是设置,一个是显示参数,没啥好介绍的。

util_sgxsegaboot.dll

命名空间用途
util.io.ManagerIO 输入管理器,7 个委托事件
util.io.ManagerButtonType按钮类型枚举 (Test/Service/Up/Down/Left/Right/Sw1)
util.io.ManagerShareDataInputBit共享数据输入位枚举
util.io.ManagerPlayerSwitchAssign玩家开关分配枚举
util.io.ManagerSystemSwitchAssign系统开关分配枚举
util.SpeakerTest喇叭测试工具类 (Play/Stop)
util.share.Manager共享数据管理器,5 个回调事件

system_share_data_wrapperMD.dll

C++/CLI 混合模式程序集,桥接 C# 前端与 C++ 原生 daemon 之间的共享内存数据结构。

说明
FactoryWrapper工厂包装器:CreateCreditChuteType() / CreateCreditServiceType()
CreditChuteType / TYPE投币通道类型枚举 (COMMON=0, INDIVIDUAL_1=1, INDIVIDUAL_2=2)
CreditServiceType / TYPE投币服务类型枚举 (COMMON=0, INDIVIDUAL_1=1, INDIVIDUAL_2=2)
CreditOperation / OPERATION投币操作枚举
LanguageSetting / SETTING语言设置枚举 (CHINESE=0, ENGLISH=1, JAPANESE=2)
Region / REGION_BIT地区区域设置枚举
StorageInfoStatus / STATUS存储信息状态枚举
NetworkTestProgress / PROGRESS网络测试进度枚举
WlanRouterModem / STATUS无线路由器调制解调器状态枚举 (DISABLED, OFFLINE, SEARCHING, CONNECTING, ONLINE)
WlanRouterPPP / STATUS无线路由器 PPP 状态枚举

wrp_systemdaemon_cli_wrapperMD.dll

C++/CLI 混合模式程序集,封装了向 sgxsystemdaemon 发送的命令。

命名空间说明
segaboot.wrpClientWrapper命令行客户端包装器
segaboot.wrpCOMMAND_SET命令集合枚举
segaboot.wrp.cli.networkSettingParam网络设置参数
segaboot.wrp.cli.creditCreditChuteType / TYPE投币通道 CLI 包装
segaboot.wrp.cli.creditCreditServiceType / TYPE投币服务 CLI 包装
segaboot.wrp.cli.creditCreditOperation / OPERATION投币操作 CLI 包装
segaboot.wrp.cli.creditSettingParam投币配置参数
segaboot.wrp.cli.dipswSettingParamDIP 开关配置参数
AM_CREDIT_STATUS投币状态枚举
AM_CREDIT_PLAYER玩家投币枚举

版权声明:本文作者为 nuym,首发于 www.nuym.cn

遵循 CC BY-NC-SA 4.0 许可协议。转载请注明出处!

评论