Files
moegrid_sega-mount/README.md
T
2026-07-31 15:39:45 +08:00

156 lines
5.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# sega-mount
SEGA游戏容器挂载工具。解析 ICF 配置文件,通过 fscrypt 驱动解密挂载 APP/OPT 容器,并创建 overlay 差分盘实现可写层,最后通过重解析点将卷设备链接到指定目录。
## 功能
- 解析 ICF 文件,筛选 Active 状态的 APP/OPT 条目(同 part 自动取最新版本)
- 通过 `sgfscrypt` 驱动解密挂载 APP/OPT 容器(AES-128-CBC)
- 创建 overlay.vhd 差分盘作为可写层,挂载到指定盘符
- 递归拷贝 overlay 目录文件到差分盘(首次创建时)
- 通过 NTFS 重解析点(Mount Point)将卷设备链接到目录
- 支持 config.ini 覆盖密钥,空则用内置密钥表
## 构建
### 环境要求
- C++20(MSVC 编译器)
- CMake 3.18+
- Windows SDK 10.0
- CLion(推荐)或命令行构建
### 步骤
```bash
mkdir build && cd build
cmake .. -G "Visual Studio 17 2022"
cmake --build . --config Release
```
CLion 用户直接打开项目即可,CMakeLists.txt 已配置好 `/utf-8` 和 SDK 版本宏。
### 生成密钥头文件
内置密钥表由 `res/gen_keys.py` 从 `res/keys.json` 生成:
```bash
python res/gen_keys.py
```
输出 `src/utils/game_keys.h`,包含 76 条游戏的 AES 密钥(编译期 `constexpr` 数组,无需运行时解析)。`keys.json` 更新后重新运行此脚本即可。
## 使用
### 命令
```
sega_mount -m [config.ini] 挂载
sega_mount -u [config.ini] 卸载
```
不指定 config.ini 路径时,默认读取 exe 所在目录的 `config.ini`。
> 需要管理员权限运行(驱动服务安装需要)。
### config.ini 格式
```ini
[CONFIG]
ICF_PATH = D:\SDGB_1.55.01\amfs\ICF1
IMAGE_DIR = D:\SDGB_1.55.01
OVERLAY_DIR = D:\SDGB_1.55.01\overlay
MOUNT_LETTER = X
APP_LINK = C:\Mount\App
OPT_LINK = C:\Mount\Option
[KEYS]
APP_KEY = 7CA4E6B6F3D6E8B26472973887D7FA3A
APP_IV = 53FE7135762DE3F97E7FE76B0FEF3F27
OPT_KEY = 5C84A9E726EAA5DD351F2B0750C23697
OPT_IV = C063BF6F562D084D7963C987F5281761
```
| 字段 | 说明 |
|------|------|
| `ICF_PATH` | ICF 文件路径(必填) |
| `IMAGE_DIR` | APP/OPT 镜像文件所在目录,空则用 exe 所在目录 |
| `OVERLAY_DIR` | overlay 源目录,空或不存在则跳过拷贝 |
| `MOUNT_LETTER` | overlay 卷盘符,默认 X |
| `APP_LINK` | APP 链接基础路径,空则不创建链接 |
| `OPT_LINK` | OPT 链接基础路径,空则不创建链接 |
| `APP_KEY` / `APP_IV` | APP 容器密钥覆盖(hex),空则用内置密钥表 |
| `OPT_KEY` / `OPT_IV` | OPT 容器密钥覆盖(hex),空则用内置密钥表 |
### LINK 输出
挂载成功后,链接目录结构:
```
C:\Mount\App\0\ → FscryptDisk_APP_0 (part 0)
C:\Mount\App\1\ → FscryptDisk_APP_1 (part 1)
C:\Mount\Option\A005\ → FscryptDisk_OPT_A005
C:\Mount\Option\A011\ → FscryptDisk_OPT_A011
```
## 挂载/卸载流程
### 挂载 (-m)
1. 读取并解析 ICF 文件(AES 解密 → 结构体解析)
2. 筛选 Active APP 条目,按 part_index 排序,验证 base version 依赖链
3. 依次挂载 APP 容器(向 fscrypt 驱动发送 MOUNT IOCTL)
4. 创建/复用 overlay.vhd 差分盘,挂载到指定盘符
- overlay.vhd 不存在 → 创建差分盘 + 拷贝 overlay 目录文件
- overlay.vhd 已存在 → 直接挂载(沿用数据)
5. 挂载 OPT 容器
6. 创建 APP/OPT 链接(NTFS 重解析点)
### 卸载 (-u)
1. 移除 APP/OPT 链接(RemoveDirectory 移除 junction)
2. 移除 overlay 盘符 + detach overlay.vhd(保留文件)
3. 倒序卸载 APP 容器(向驱动发送 UNMOUNT IOCTL)
4. 卸载 OPT 容器
## 项目结构
```
sega-mount/
├── CMakeLists.txt
├── config.ini # 配置文件示例
├── res/
│ ├── keys.json # 游戏密钥表(源数据)
│ └── gen_keys.py # 密钥表头文件生成脚本
├── src/
│ ├── main.cpp # 入口:INI 解析 + 命令分发
│ ├── config.h # Config 结构体定义
│ ├── driver_service.cpp # sgfscrypt 驱动服务管理
│ ├── fscrypt.cpp # 容器挂载/卸载/链接(fscrypt IOCTL)
│ ├── icf_parser.cpp # ICF 文件解析(解密 + 结构体)
│ ├── icf_mount.cpp # ICF 挂载编排(APP + overlay + OPT + LINK)
│ ├── vhd_mount.cpp # VHD 创建/挂载/卸载(virtdisk API)
│ └── utils/
│ ├── aes.cpp # AES-128-CBC 加解密(BCrypt)
│ ├── bcrypt_util.cpp # BCrypt 辅助函数
│ ├── crc32.cpp # CRC32 校验
│ ├── files.cpp # 文件读取/目录拷贝/exe路径
│ ├── log.cpp # 日志库(彩色输出 + 等级)
│ ├── strings.cpp # 字符串/路径转换工具
│ ├── crypto_constants.h # BootId/ICF 通用密钥常量
│ └── game_keys.h # 自动生成的游戏密钥表
```
## 技术细节
- **ICF 解析**:ICF 文件经 AES-128-CBC 解密后,按 0x40 字节 section 依次解析 Header 和 Entry,支持 Pack/App/Opt 三种类型
- **容器挂载**:读取容器 BootId(0x60 字节)→ AES 解密 → 解析容器元数据 → 构造 0x280 字节 IOCTL 输入缓冲 → 发送 MOUNT IOCTL
- **overlay 差分盘**:使用 `CreateVirtualDisk` 创建差分 VHD,父盘指向最后一个 APP 容器内的 `internal_N.vhd`;差分盘保留不删除,仅首次创建时拷贝 overlay 目录文件
- **密钥查找优先级**:config.ini `[KEYS]` section 覆盖 → 内置 `game_keys::ENTRIES` 密钥表按 GameID 查找
- **日志格式**:`[LEVEL][MODULE] message`,彩色输出,不打印敏感信息(密钥/IV)
## 依赖
- `sgfscrypt.sys` 驱动(需放在 exe 同目录)
- Windows API:`virtdisk.h`(VHD)、`winioctl.h`(重解析点)、`bcrypt.h`(AES)
- 无第三方 C++ 库依赖