首次提交

This commit is contained in:
MoeGrid
2026-07-31 15:39:45 +08:00
commit feef2db6ac
32 changed files with 3447 additions and 0 deletions
+60
View File
@@ -0,0 +1,60 @@
# ===== IDE / 编辑器 =====
.idea/
.vscode/
.vs/
*.user
*.suo
*.sln.docstates
# ===== CMake 构建 =====
cmake-build-*/
build/
out/
# CMake 生成的文件(防止在源码根目录直接运行cmake时污染)
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
CMakeUserPresets.json
compile_commands.json
# ===== MSVC 编译产物 =====
*.obj
*.pdb
*.ilk
*.idb
*.exp
*.lib
*.dll
*.exe
*.manifest
*.res
*.i
*.ddi
*.modmap
*.dd
# ===== Ninja =====
.ninja_deps
.ninja_log
build.ninja
# ===== 项目运行时产物 =====
# overlay差分盘(程序运行时在exe目录生成)
overlay.vhd
# sgfscrypt驱动文件(构建时拷贝到输出目录)
sgfscrypt.sys
sgfscrypt.inf
# ===== 项目私有目录/文件 =====
driver/
refer/
overlay/
check/
note.txt
# ===== Windows 系统文件 =====
Thumbs.db
ehthumbs.db
desktop.ini
$RECYCLE.BIN/
+33
View File
@@ -0,0 +1,33 @@
cmake_minimum_required(VERSION 3.18)
project(sega_mount)
set(CMAKE_CXX_STANDARD 20)
add_executable(sega_mount
src/main.cpp
src/driver_service.cpp
src/fscrypt.cpp
src/icf_parser.cpp
src/icf_mount.cpp
src/vhd_mount.cpp
src/utils/aes.cpp
src/utils/bcrypt_util.cpp
src/utils/crc32.cpp
src/utils/files.cpp
src/utils/log.cpp
src/utils/strings.cpp
)
target_include_directories(sega_mount PRIVATE src)
if (WIN32)
target_link_libraries(sega_mount PRIVATE bcrypt virtdisk)
# 源文件含中文注释,指定UTF-8编码避免MSVC解析错误
target_compile_options(sega_mount PRIVATE /utf-8)
# 指定Windows SDK最低版本为Win10,启用virtdisk.h等结构体定义
target_compile_definitions(sega_mount PRIVATE
WINVER=0x0A00
_WIN32_WINNT=0x0A00
NTDDI_VERSION=0x0A000000
)
endif ()
+156
View File
@@ -0,0 +1,156 @@
# 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++ 库依赖
+13
View File
@@ -0,0 +1,13 @@
[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
+72
View File
@@ -0,0 +1,72 @@
#!/usr/bin/env python3
"""
将 res/keys.json 生成为 src/utils/game_keys.h 头文件
KEY/IV 直接生成为字节数组,C++ 端无需 hex_to_bytes 转换
用法(在项目根目录运行): python res/gen_keys.py
"""
import json
import sys
from pathlib import Path
JSON_PATH = Path("./res/keys.json")
OUTPUT_PATH = Path("./src/utils/game_keys.h")
def hex_to_c_array(hex_str):
"""将hex字符串转为C字节数组初始化列表,如 {0xA5, 0x37, ...}"""
return "{" + ", ".join(
"0x" + hex_str[i:i + 2].upper() for i in range(0, len(hex_str), 2)
) + "}"
def main():
if not JSON_PATH.exists():
print("Error: " + str(JSON_PATH) + " not found", file=sys.stderr)
sys.exit(1)
with open(JSON_PATH, "r", encoding="utf-8") as f:
entries = json.load(f)
out = []
out.append("#pragma once")
out.append("")
out.append("#include <cstdint>")
out.append("")
out.append("// 此文件由 gen_keys.py 从 res/keys.json 自动生成,请勿手动编辑")
out.append("// 源文件: res/keys.json (" + str(len(entries)) + " 条记录)")
out.append("")
out.append("namespace game_keys {")
out.append(" // 单个游戏的密钥条目(KEY/IV为16字节原始数组,无需运行时hex转换)")
out.append(" struct Entry {")
out.append(" const char *game_id;")
out.append(" const uint8_t app_key[16];")
out.append(" const uint8_t app_iv[16];")
out.append(" const uint8_t opt_key[16];")
out.append(" const uint8_t opt_iv[16];")
out.append(" };")
out.append("")
out.append(" // 所有游戏的密钥表(编译期常量,无需运行时解析)")
out.append(" inline constexpr Entry ENTRIES[] = {")
for e in entries:
line = ' {"' + e["GameID"] + '", ' + \
hex_to_c_array(e["AppKey"]) + ", " + \
hex_to_c_array(e["AppIV"]) + ", " + \
hex_to_c_array(e["OptKey"]) + ", " + \
hex_to_c_array(e["OptIV"]) + "},"
out.append(line)
out.append(" };")
out.append("}")
out.append("")
content = "\n".join(out)
OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True)
with open(OUTPUT_PATH, "w", encoding="utf-8") as f:
f.write(content)
print("Generated " + str(OUTPUT_PATH) + " (" + str(len(entries)) + " entries)")
if __name__ == "__main__":
main()
+686
View File
@@ -0,0 +1,686 @@
[
{
"GameID": "SBZS",
"GameKey": "A53780999F0DF49CBD8075C86D150566",
"GameIV": "E2048E0E673CD0C8F3A79E72FEAF4A62",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "2ECBCFF65CE0ABECC10547F8AC8351D8",
"AppIV": "F2AC6C2817D0574BBA113D497E319F3E"
},
{
"GameID": "SBZT",
"GameKey": "C613529289708843B858AE80396DE7AB",
"GameIV": "76C111FD52F654CA61A3FB0758FA8309",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "9AB9CE55ED9C194A715A73A7699F795B",
"AppIV": "8552DE88FEDDA6E859369FB000F44D5B"
},
{
"GameID": "SBZU",
"GameKey": "E6EF248B72D31DEAB42FE73906C4C9F0",
"GameIV": "0A7D94EC3DAFD9CCCF9F599CB245BDB1",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "EB1228254CDD3077EB3E441C0227BF40",
"AppIV": "3F9B4676118CEE129FE2F1CB2747BCA5"
},
{
"GameID": "SBZV",
"GameKey": "1EB26F806696CD872DEA14262F8C7B4C",
"GameIV": "EB0A261B4DB7ED9D89FE5EB24AF7054D",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "3274A399594D84779625940B69C02D3F",
"AppIV": "675BA66D29C87923F5F154C406AFEE42"
},
{
"GameID": "SDAP",
"GameKey": "A8FEEA8AF2950EA933392A7E7A515AC2",
"GameIV": "6C7E0E3B88CE678FB033E456E5343D72",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "41B5027C5E99D94AA9335D6D71838ECF",
"AppIV": "D195D7AD2814207115DB3531200AE3BA"
},
{
"GameID": "SDAQ",
"GameKey": "C5DEF48491A17ABFAF14F70ACC068C4D",
"GameIV": "3E416A45DC0E442A13FC058F36DEBE89",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "C28F22BC1B339AE64180739886DC83D6",
"AppIV": "0A29FD145D72BF8DEDD436025DF0A9FC"
},
{
"GameID": "SDAV",
"GameKey": "6F239C60B73FB2259A40ECFDC1FF541A",
"GameIV": "A5E047B8A29F27014E4D512E08954FF0",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "EED95513266A499A55E265B049169C44",
"AppIV": "84C0E5931D91A6A477D62C271546056E"
},
{
"GameID": "SDBE",
"GameKey": "F88B7A295516361C4720B11B39C0BB89",
"GameIV": "9303368A79D66F0AB626AE02F3450BF6",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "7053FB944572E5B631A665CEF4B5BCDD",
"AppIV": "AE4D7E884002C79EB35711554D613057"
},
{
"GameID": "SDBN",
"GameKey": "2F35C2E8024539D1A19ECE74D2FD1A97",
"GameIV": "917F9466B2B7571DC9FE2608470AADAD",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "C1F14AE2E85B095E313C8BAEC125805E",
"AppIV": "3C538EEA66251ACD5404B93F8976A7F7"
},
{
"GameID": "SDBT",
"GameKey": "F65A75BEC7F0DD4D08A690F219AB14EF",
"GameIV": "CAE2CEE2CC88188F661893E0696BBE2A",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "A6A870671FD432EC637ADF7A822F97DA",
"AppIV": "2C277F31CD550CFA2C993B4DD56B85AE"
},
{
"GameID": "SDBX",
"GameKey": "7CC784A2B8D4577B77FFE000C7AA4BBD",
"GameIV": "DCCC018210E7D0FE283A468FD9375D59",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "3DC19C2D0C20AC199D5FA46E7F6335A6",
"AppIV": "D8F029EC90FE55BE67584F742C55EF8B"
},
{
"GameID": "SDBZ",
"GameKey": "A09F1E9AED8E14B3F2D285E50FA4DCBC",
"GameIV": "3282AB559319FB67A168E0803B235091",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "521BDE4460F4184EDD879136ADEEA5EE",
"AppIV": "1B8324032DB69D7B0954794AA229FE68"
},
{
"GameID": "SDCA",
"GameKey": "48FF08937104B2A83CCB282468A5C702",
"GameIV": "D7782A8594B74BC91491EA9DBC693395",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "1649490A03D6C2AEC1C496982CB0405C",
"AppIV": "4680711C7E67A26F9230D5AF74B5DCFB"
},
{
"GameID": "SDCD",
"GameKey": "9DA25C8104D3CD5A3161229DE221ACE9",
"GameIV": "8BC818BEF7FF3C3431BA11EDA5C5FB48",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "43B38502D8F6D3C7B02B95FC28DB5308",
"AppIV": "6DFCB94BF74F152B55F3E0C7F35B44B5"
},
{
"GameID": "SDCF",
"GameKey": "E45390726D4747CA28086C667214CFE7",
"GameIV": "36336C65FC649205231745CE6A9CE0B8",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "DF986883DA837538E37B959A3E4117CD",
"AppIV": "DABF539738852F17714811AF70435A83"
},
{
"GameID": "SDCH",
"GameKey": "260B3364400E701920B6DED70BC39271",
"GameIV": "5EAB7342D2D79B09FE0E00F81E325307",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "E2DA769E94F1D3ACA1930CDBE0708C9F",
"AppIV": "C7DCCE203C84AB0477236D697570DADC"
},
{
"GameID": "SDCR",
"GameKey": "70A02D1FAC466632771C843886CE13DC",
"GameIV": "E8FFB97A988D6C845317E324A7BD4A22",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "4961A51FD36F14E72664F52373052160",
"AppIV": "25D7D1341A282C5E0A34C64562C023EC"
},
{
"GameID": "SDCT",
"GameKey": "B751601015BAE0A26EC2CE0115C136DA",
"GameIV": "946A0D219DF2C355447517056C952F93",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "D6AE51F10EC76DA93C981800FC3AD3CB",
"AppIV": "FB8E43E280D330D06581732F2E11A6DC"
},
{
"GameID": "SDCX",
"GameKey": "20DE66FA6791EEB961405183710B3C1D",
"GameIV": "D391E4B68D109D28A3CDA97B8BB74DAA",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "79504CCC509B67D1F7A3F593E6F9D9D6",
"AppIV": "1551EA8926F2AEE233EEC309DE3E5F3C"
},
{
"GameID": "SDDB",
"GameKey": "1AE3DCE135807FD020D3820FCAE65B90",
"GameIV": "6ADE2B3A5A7E388F29EC9D8CECB840D0",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "875679B2CD1637962B0DB25C51FB21A6",
"AppIV": "8EF44722A0566E8F572356245687FBE5"
},
{
"GameID": "SDDD",
"GameKey": "619410D29DF4F940177ACCD859D97E8E",
"GameIV": "16497FE160E28E601A49D16DB18F2541",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "564E967873DE6CBCD22EFECA6952E9DC",
"AppIV": "4E3DD465CF09CD82B259F7BED5FC2D6D"
},
{
"GameID": "SDDD",
"GameKey": "6591D8D2E84B21D197753804D47C2E48",
"GameIV": "D842A5C6F75B35C7257C0EC9BA30DED1",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "75713E3A6340416598CFA9E38FBA68E4",
"AppIV": "59A14E3E13A6F8B867561EF734E1A5FF"
},
{
"GameID": "SDDF",
"GameKey": "A6497BC4BB114A1F8F24AA756D2AF2D2",
"GameIV": "00BBACA3CDCE3DCA0174C9F26DC65120",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "65058573A0CB81749E694AE164C61B04",
"AppIV": "981C4F45E3C6958F054E5D00916BDF2B"
},
{
"GameID": "SDDJ",
"GameKey": "32AE1BA642A3166FFD75D2DB116E8814",
"GameIV": "95972D0C401E4306D9FBF459EED36170",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "630FE52276537BD7FB267ADF175F4E99",
"AppIV": "DC5755BE57DED2CDB34433BBBA2204FF"
},
{
"GameID": "SDDL",
"GameKey": "5A837D9EC1B3FB37F844E3ECD40ACACD",
"GameIV": "AD46FDC45AC915D55C5DCBA55A600D39",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "992458295FD06D6A8AF0DFB3F6854C19",
"AppIV": "8484906D4CD5FD225E032843ED37495D"
},
{
"GameID": "SDDM",
"GameKey": "7A5F4E97AB1790DFF41C1CA4A061AC12",
"GameIV": "410280B2458399D7CA59283AB4AB46E1",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "0127958210F6AE9BDEB8975018B5AF24",
"AppIV": "181716BADCCFF4BC2B1E29AE02A1BBBB"
},
{
"GameID": "SDDN",
"GameKey": "9B3B2090947A2486F0F3555C6DB98E56",
"GameIV": "D5BE04A1303C1DD9385486CF0DF68088",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "41DD8E66290117AC67D311A2F0A6416E",
"AppIV": "73E18E8418F6CEEFB11E2767FDEA190C"
},
{
"GameID": "SDDP",
"GameKey": "E6E91C814845C68667950B52774E610F",
"GameIV": "43227E2DCD1A1A1134E4F60BDB6E1D89",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "CF6D64427EECA47674E17BCD46D1EA8C",
"AppIV": "CE5174093D26CA2A31B58541E85AC276"
},
{
"GameID": "SDDS",
"GameKey": "5272EA6AE573FD2ED90FFBFF4E3B170C",
"GameIV": "45437CA754B19C4B9F70C4DD0331F432",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "161BEC6D90989D0E26D791170607A440",
"AppIV": "81DC26A27028E2092332038AA1BFFC47"
},
{
"GameID": "SDDT",
"GameKey": "764B8462192DBA6655E2A0E49635A90B",
"GameIV": "9BF8267AD7E3C7B3179F5ECD661DE76A",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "3F7658728B9517D3314E684FA2E2A045",
"AppIV": "41578833C547AAFF04DB597A6E9EB784"
},
{
"GameID": "SDDT",
"GameKey": "665ED0587A16A9AFDD46D59596663ACA",
"GameIV": "90A6F613EF5F7A3C9C932D6B7BB8BC5B",
"OptKey": "813A1158C52B82089D710AEC3B5FCC91",
"OptIV": "A12F44F9F1BE52E6994AC6E052C15965",
"AppKey": "2169343646A3248619755B68E6EF2C2C",
"AppIV": "6982406E69C9EC2574EDC5253FC6A324"
},
{
"GameID": "SDDU",
"GameKey": "8D31FD5E238CD55BD2C69418F2A57A21",
"GameIV": "E9C935BBFB3257816402064FA484F55F",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "649AE9982625F90C55AF86713C55D3FD",
"AppIV": "187116FC4647A7D3B6F2303A34F0A2FE"
},
{
"GameID": "SDDW",
"GameKey": "C1F88053CCF85D67CB8556D9A0CA7CC3",
"GameIV": "085CA00674C1C4EB142FCF0A3495856B",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "118565D344F3E14CA69299EEAC049BB9",
"AppIV": "9D6D392EC35ED94EF9FE0A5BE0573981"
},
{
"GameID": "SDDX",
"GameKey": "E2D4514CB65BF10EC75C8F916D215E08",
"GameIV": "9C1923F45F7A48ED812A2C9F8DE0BE13",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "428BFF0F9E7AAFC169A7A75751FFDA98",
"AppIV": "F8250594F425332C6D349D7EA0E86669"
},
{
"GameID": "SDEA",
"GameKey": "BBFEF63A99E6ED7E8B188765F9A59B36",
"GameIV": "9FA9E78A402E5E52994DC31B95967791",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "9F9CF148AC3C50AAF925AF1DFB27F58B",
"AppIV": "4D8EBBD971896B8A4A3DD84A23B329FC"
},
{
"GameID": "SDEB",
"GameKey": "DFD69032CEA0AAB606EB2C4A409F2D35",
"GameIV": "F55F915DC3608ABB127C5D0CF7826AC9",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "D511ED690415F6359843A134FD47836A",
"AppIV": "AC139B382ACDD112E31564EA7F38186C"
},
{
"GameID": "SDEC",
"GameKey": "02AF292B025A67EE82BED12E8898BF34",
"GameIV": "4B143B304693B5238BAAF8FC5A6E5C01",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "F272E5016863AF2BA0337F50DE686F6E",
"AppIV": "5327E132631E7F71B61BE7CC0DF382CE"
},
{
"GameID": "SDED",
"GameKey": "2687C32336142426FD927613D0925133",
"GameIV": "A1CAE504C8C5E08C04D992EDBC5A4E3A",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "21FCEC779A16769F5277A36FB542992C",
"AppIV": "22B50239F1B40CCC3E55A2D69C69B160"
},
{
"GameID": "SDED",
"GameKey": "D92F3AEE4C1B486D8A9F321C56B8FA10",
"GameIV": "5B7E9C20A1F6432EB3C6FF9EA1143D98",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "DE78B3F3D3358F40D552CBF58921A499",
"AppIV": "20F333CC757462842656B430C7823434"
},
{
"GameID": "SDEE",
"GameKey": "3A72741FF61D178AFB79FF1BB160728F",
"GameIV": "2DA1CD60559BC9F3450AFD13F220A49E",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "191EB7440672DAB08DDBB7195EFB356F",
"AppIV": "C278B5386DC38BD76D71DBCD826954CF"
},
{
"GameID": "SDEG",
"GameKey": "8F14C90D89EC333DF010F9942CDC5676",
"GameIV": "E1F1BC99B8E3BA5F63322363DB7C6D51",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "721853DBE2D30BAFE24F0EDBD210DEEB",
"AppIV": "4DFB0BCEC86159AAB297166BCD509E6F"
},
{
"GameID": "SDEJ",
"GameKey": "F4A506F8906C19C4639110EA0D84ABFE",
"GameIV": "5E1F6C4A1188EECBB758787DF1FDD2D9",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "9DE1EA6AE38D9011F55D8EE864395D24",
"AppIV": "F60CDE21982876D12D17662A48D90836"
},
{
"GameID": "SDEM",
"GameKey": "464B92E7D9E50CE6D92C9E370D5FDF2B",
"GameIV": "5076349FDD5839D0C94E6271D1B7E2FB",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "700617F293696C07FB9F356D3B99240D",
"AppIV": "667D026D6CDF329FF351DBAF7098E81D"
},
{
"GameID": "SDEP",
"GameKey": "ABDCD0D2E165F26C4CAEB58CEE0734B4",
"GameIV": "CEA3E44F35FD6C3D1D74B78BE7384782",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "FA2B7CA53A823C152D940972CBF532F5",
"AppIV": "F4AF35120C48617704BB5B8471797A62"
},
{
"GameID": "SDER",
"GameKey": "F0903BC3FF83434CC45893290257A7F7",
"GameIV": "B8151112A3E81CA7049EAE11A36F7362",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "7D73367EBB218EC82930D58DC6D7950B",
"AppIV": "9788C3ECA2DB6BA92BAC4F6F7B706308"
},
{
"GameID": "SDET",
"GameKey": "37416FB467F7BDBCBBFFDDF2914BCBF5",
"GameIV": "648065B8A84D7278F5FCE2F2684657D2",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "4643E7B2C3006E0264163EDC8545FB72",
"AppIV": "612BCA81EA2958FFBAC36F780F1ED688"
},
{
"GameID": "SDEU",
"GameKey": "5B1909AD9CB17AF336D282D7D9445DF4",
"GameIV": "BA360F8C2B7F9DE16E2A7CE2CB324A0A",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "23B3E9BB47E3AC9998F6E6C1ADC4AE33",
"AppIV": "A964714CEA60688407BF554BD1C27EC2"
},
{
"GameID": "SDEV",
"GameKey": "7FF2A3A5D06B372BB2A527BB213EEFF3",
"GameIV": "10EBB95FADB2C84AE75916D32D1E3C43",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "3C1F018D88926D98163B07A1563A4818",
"AppIV": "CA7373C9C7DFEBAC0FC24254C030E4AD"
},
{
"GameID": "SDEZ",
"GameKey": "EB7B708E6D996ED3241F1769F82BA4F0",
"GameIV": "110BB7D93549498351E4E4A455E113EB",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "D136EBA05D40E82682E6AAD8D9E8688C",
"AppIV": "C484DEEAA0249EF46695F63694B7372F"
},
{
"GameID": "SDFA",
"GameKey": "79F8198C9D5AC8437138594879190E66",
"GameIV": "A638FFE37A1F63B16C770BE48E1F2F69",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "8E816B4362DB24A230877885864D206D",
"AppIV": "8E5A0BA6A0A1150D47D12BDB64DEBBA7"
},
{
"GameID": "SDFE",
"GameKey": "0261F06FD9946B01608D1582A2BAF4EE",
"GameIV": "7A1B5A6755F6C18639CCFAEF078B8728",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "F61719C371E5BCA6788C139A53091617",
"AppIV": "67D43173E343813FA2097FD32992A8E2"
},
{
"GameID": "SDFE",
"GameKey": "FA8BFA200F2D472E3A700C2BF8548DED",
"GameIV": "F4C72A1B9BEAFECCB479FDF99420020B",
"OptKey": "813A1158C52B82089D710AEC3B5FCC91",
"OptIV": "A12F44F9F1BE52E6994AC6E052C15965",
"AppKey": "24F963D0A7CF3D65F3670977FAA5DABA",
"AppIV": "37A175838F1E5C6D105F4FB0599F607B"
},
{
"GameID": "SDFG",
"GameKey": "5703455E6C6387B456240FFB1D37D9D5",
"GameIV": "2E6B48A1B83FB3F157F4203EF0E74FDB",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "3398FB86BFE630A14979411879861AC7",
"AppIV": "A794C49C2C7639CD80571807C17246FF"
},
{
"GameID": "SDFL",
"GameKey": "E06C1C41A89E29724479CB3546D9BF5C",
"GameIV": "014FA325941612C624490F496854A79A",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "2449B48067B9176A6E0F9563481E97F4",
"AppIV": "616F8710454632EB4FB1D89D8C19C19A"
},
{
"GameID": "SDFN",
"GameKey": "281D50321112A3E23B1F15FED5CCE35A",
"GameIV": "ADBAF6CC997B689715A6432A2D2B8C0B",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "29F62E22C6A9FD8BE327631C68546405",
"AppIV": "2A860976E6D98513825F291E56CFB5EE"
},
{
"GameID": "SDFP",
"GameKey": "7AC4DC21598A9704B1BAA44BD5A61787",
"GameIV": "9F10BE20644AB29C279C2D1E0DE69B2D",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "570B87263A7CA0AA4C1388E204EE6D4B",
"AppIV": "7640886011A2300A91FAD9F36A8C4775"
},
{
"GameID": "SDFT",
"GameKey": "F23FCA0721136FEF222343A9971C8D6C",
"GameIV": "A915572D4AC5CF70BDF3ED5F592D5619",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "92A25F388C50737E39C3C2F006645F31",
"AppIV": "A97E72F990417488CB4C67F8F0C3FB25"
},
{
"GameID": "SDFV",
"GameKey": "41E88EF71E353A8118C265CA1C5411DE",
"GameIV": "D973F89D7E1B730FC5B69AF73047ADAB",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "FE82DB9A60295D829B95F03C2276018B",
"AppIV": "34D82772AE18174F0A181DC53399EA9C"
},
{
"GameID": "SDGA",
"GameKey": "48E025DB167E6DDBD644470761B8F13A",
"GameIV": "77A5DAB4A96DA910769F8177B5CC8314",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "0A6610A62EF670C65B7E7B1750FFB7A1",
"AppIV": "17A2A22915F81C5896EDBBA4C412585E"
},
{
"GameID": "SDGB",
"GameKey": "65BF2FD5B58BD9F152201493B26D22C4",
"GameIV": "496836BEFDAD86ACD968A2B00576042A",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "7CA4E6B6F3D6E8B26472973887D7FA3A",
"AppIV": "53FE7135762DE3F97E7FE76B0FEF3F27"
},
{
"GameID": "SDGH",
"GameKey": "36D93AA95A3AF61F381B1A966B022D4A",
"GameIV": "C9B6E4E9DDE93B52981AC59C43BA405A",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "B3E30E7EABAC3767ADE13C69C9B2F22B",
"AppIV": "03DEAEA3742D69675B36CDDC8B15AC91"
},
{
"GameID": "SDGK",
"GameKey": "9B6A779462BADCA6AB9D31EB4CAA82D3",
"GameIV": "47E4949A368E6FBEEC411AB6593BA6E1",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "9DC4A17FC39FCA5A8A358984801CAAA7",
"AppIV": "E0445B11DCFA0DAE56C85E8787E11D9B"
},
{
"GameID": "SDGP",
"GameKey": "28CF1676E84CA7F51AEE5951EFEE1915",
"GameIV": "DCC11502A8DF74FAC4C8451DDA49B631",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "C87AB31247E7B6FF95FDD79FB91F9F37",
"AppIV": "2467AB3C031E3DC0568B7077EFD27C36"
},
{
"GameID": "SDGQ",
"GameKey": "4BA8B06F1D06642D95C2FE3637E8AA14",
"GameIV": "3276BFD62B11A0623CF7E00D3D35A969",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "C5356DAE7B066BCE88984AEC36DEB62D",
"AppIV": "4E9F2982460E2FD907BDE15709EDFBA7"
},
{
"GameID": "SDGS",
"GameKey": "9359E460857ADE9D8C6848FFC6DBCE12",
"GameIV": "DEE1137D3075F6342E5414EE020C8DDA",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "A5150CC5065D2C59EE2F8F332CBD29D5",
"AppIV": "84014D26696F290AD7EAD70C7549BD81"
},
{
"GameID": "SDGT",
"GameKey": "B7317E58BA349BD5083BEDE40ED46011",
"GameIV": "3496BD50B3A8219CA683AEDF65F88012",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "9D0BBA20D1E84F2459399F5383BEEE72",
"AppIV": "5D340013FDFB2464D253093602FE4B64"
},
{
"GameID": "SDGV",
"GameKey": "FFE2B24922A81545FFE237AD9DC8830F",
"GameIV": "E00111F7B80C776D98E0E2C02ACF6582",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "573F8CC44F10F31EC749B695EBE886A8",
"AppIV": "6BFCA86F9D208A7944CDFC25EA3CD220"
},
{
"GameID": "SDGY",
"GameKey": "4A90AE39D573B7467584EDA2A75D57C7",
"GameIV": "4E658B8254EA74A5947052FDF8480283",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "C04B663A59055ACBDFEBC6D3DF0E6A04",
"AppIV": "76FC5F1D88605107947D0C1FF347022D"
},
{
"GameID": "SDGZ",
"GameKey": "7B5A682F348716C1EF474336DAE0A9AE",
"GameIV": "AC00D0E835003AA83969DE5C7EB8D9FE",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "9AD74EFB208D6EE4FE5EE770331712CF",
"AppIV": "45F6E53F0EB8FAE6665B45444A61E266"
},
{
"GameID": "SDHD",
"GameKey": "5B7C9D1CADC06352B2FB13625CA84751",
"GameIV": "3283E14746A69FDA67F2EF8F97B0049E",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "3ABD00D7A820CE862EAF474BF6C8F33E",
"AppIV": "0F1E7EEA78DA7E037E0552C2843E1B6A"
},
{
"GameID": "SDHD",
"GameKey": "93D37F8554C2710EA50466C8A45DF3FB",
"GameIV": "3C58D00419186C3B435B767A8602437B",
"OptKey": "813A1158C52B82089D710AEC3B5FCC91",
"OptIV": "A12F44F9F1BE52E6994AC6E052C15965",
"AppKey": "81B158CBB88370A1264C99800C5DDE4D",
"AppIV": "6FD501ED31EBE11826DBAC3946E24D57"
},
{
"GameID": "SDHH",
"GameKey": "F4D35DFC5EADD0E41F3CEC79EB769F7B",
"GameIV": "D045FD4217DA3FB06B450C653D42F831",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "FC6F887F3717C5D6713113B92FA3FB27",
"AppIV": "AD76606460DBE1E91E41BEF7AB0C1535"
},
{
"GameID": "SDHJ",
"GameKey": "3988C8ED7CCA22C396E6CA1600C712BF",
"GameIV": "BAB62A0484C6EF1B516F04EAF9792411",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "985EA66ECB5B1F208C90E2B898F0B073",
"AppIV": "164A65422E7F01B7F1B0849FC7737CDB"
},
{
"GameID": "SDHK",
"GameKey": "5D6062E6B084DFFB12BA6FFA47C0A4BE",
"GameIV": "106CD4D807F81A83CA9E9EDB5B641649",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "BC92D63C2A099CA2315A483C3041FDD7",
"AppIV": "B14D8449B6D4325D83A2774B13DD21FF"
},
{
"GameID": "SDHN",
"GameKey": "A51196D719F8596B0960B9C3D7B3C8BC",
"GameIV": "BCD7287F0C5D7055BBFBD2BC213CFBBA",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "892123A26D7C03D49EDD12A80EE0C58F",
"AppIV": "76AA15A6868B8DBDF7207906354D5169"
},
{
"GameID": "SDHR",
"GameKey": "2E7A6DBA5533FB29F8B575FDFF55AE44",
"GameIV": "8FBA8203E734CF2A884FC1C699A95379",
"OptKey": "5C84A9E726EAA5DD351F2B0750C23697",
"OptIV": "C063BF6F562D084D7963C987F5281761",
"AppKey": "1FB897CAB97C8170A6AC0A21685C58D9",
"AppIV": "F9B60F65B01E8E836A4BC20F7D39FAF5"
}
]
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include <string>
// 配置信息(从config.ini读取)
struct Config {
std::wstring icf_path; // ICF文件路径
std::wstring image_dir; // APP/OPT镜像文件所在目录
std::wstring overlay_dir; // overlay源目录(空或不存在则跳过拷贝)
wchar_t mount_letter = L'X'; // overlay卷盘符
std::wstring app_link; // APP链接基础路径(空则不LINK)
std::wstring opt_link; // OPT链接基础路径(空则不LINK)
// [KEYS] section:hex字符串,空表示用内置密钥表
std::string app_key; // APP密钥hex
std::string app_iv; // APP IV hex
std::string opt_key; // OPT密钥hex
std::string opt_iv; // OPT IV hex
};
+101
View File
@@ -0,0 +1,101 @@
#define LOG_MODULE "DRIVER"
#include "driver_service.h"
#include "utils/files.h"
#include "utils/strings.h"
#include "utils/log.h"
#include <windows.h>
#include <string>
namespace driver {
bool ensure_running(std::string *error) {
SC_HANDLE hScm = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_ALL_ACCESS);
if (!hScm) {
if (error) *error = "OpenSCManager failed: " + std::to_string(GetLastError());
return false;
}
// Try to open existing service
SC_HANDLE hSvc = OpenServiceW(hScm, SERVICE_NAME,
SERVICE_ALL_ACCESS);
if (!hSvc) {
DWORD last = GetLastError();
if (last != ERROR_SERVICE_DOES_NOT_EXIST) {
if (error) *error = "OpenService failed: " + std::to_string(last);
CloseServiceHandle(hScm);
return false;
}
// Service does not exist - create it
std::wstring driver_path = utils::get_exe_dir() + L"\\" + DRIVER_FILENAME;
hSvc = CreateServiceW(
hScm, SERVICE_NAME, SERVICE_NAME,
SERVICE_ALL_ACCESS,
SERVICE_KERNEL_DRIVER,
SERVICE_DEMAND_START,
SERVICE_ERROR_NORMAL,
driver_path.c_str(),
nullptr, nullptr, nullptr, nullptr, nullptr);
if (!hSvc) {
if (error) *error = "CreateService failed: " + std::to_string(GetLastError());
CloseServiceHandle(hScm);
return false;
}
LOG_INFO("Service installed: {}", utils::wstr_to_str(driver_path));
}
// Query current state
SERVICE_STATUS status{};
if (!QueryServiceStatus(hSvc, &status)) {
if (error) *error = "QueryServiceStatus failed: " + std::to_string(GetLastError());
CloseServiceHandle(hSvc);
CloseServiceHandle(hScm);
return false;
}
if (status.dwCurrentState == SERVICE_RUNNING) {
CloseServiceHandle(hSvc);
CloseServiceHandle(hScm);
return true;
}
// Start the service
if (!StartServiceW(hSvc, 0, nullptr)) {
DWORD last = GetLastError();
if (last != ERROR_SERVICE_ALREADY_RUNNING) {
if (error) *error = "StartService failed: " + std::to_string(last);
CloseServiceHandle(hSvc);
CloseServiceHandle(hScm);
return false;
}
}
// Wait for it to actually enter RUNNING state
int tries = 30;
while (tries-- > 0) {
if (!QueryServiceStatus(hSvc, &status)) break;
if (status.dwCurrentState == SERVICE_RUNNING) {
LOG_INFO("Service started");
CloseServiceHandle(hSvc);
CloseServiceHandle(hScm);
return true;
}
if (status.dwCurrentState == SERVICE_STOPPED) break;
Sleep(100);
}
if (status.dwCurrentState != SERVICE_RUNNING) {
if (error) *error = "Service failed to reach RUNNING state";
CloseServiceHandle(hSvc);
CloseServiceHandle(hScm);
return false;
}
LOG_INFO("Service started");
CloseServiceHandle(hSvc);
CloseServiceHandle(hScm);
return true;
}
}
+16
View File
@@ -0,0 +1,16 @@
#pragma once
#include <string>
namespace driver {
constexpr auto SERVICE_NAME = L"sgfscrypt";
constexpr auto DRIVER_FILENAME = L"sgfscrypt.sys";
// Ensure the sgfscrypt service is installed and running.
// - If service does not exist: create it (driver path = same dir as exe),
// then start it.
// - If service exists but stopped: start it.
// - If already running: no-op.
// Returns true on success. On failure, fills `error`.
bool ensure_running(std::string *error = nullptr);
}
+412
View File
@@ -0,0 +1,412 @@
#define LOG_MODULE "FSCRYPT"
#include "fscrypt.h"
#include "utils/strings.h"
#include "utils/aes.h"
#include "utils/crypto_constants.h"
#include "utils/game_keys.h"
#include "utils/log.h"
#include <cstdio>
#include <format>
#include <filesystem>
namespace fscrypt {
// 解析解密后的BootId,提取容器类型、数据偏移、扇区大小等信息
bool parse_boot_id(const uint8_t *decrypted_bootid, size_t size, BootIdInfo *info, std::string *error) {
if (size < 0x38) {
if (error) *error = "Decrypted BootId is too small";
return false;
}
info->type = static_cast<ContainerType>(decrypted_bootid[13]);
auto read_u64_le = [](const uint8_t *p) -> uint64_t {
uint64_t v = 0;
for (int i = 0; i < 8; i++) v |= static_cast<uint64_t>(p[i]) << (8 * i);
return v;
};
info->data_offset = read_u64_le(decrypted_bootid + 32);
info->data_size = read_u64_le(decrypted_bootid + 40);
info->sector_size = read_u64_le(decrypted_bootid + 48);
info->total_sectors = info->sector_size * info->data_size;
info->encrypted_offset = (info->data_size * (info->data_offset - info->sector_size)) << 8;
return true;
}
// 根据BootId容器类型识别容器名称和镜像类型(OS/APP/OPTION)
bool recognize_container(ContainerType type, const char **container_name, const char **image_type,
std::string *error) {
switch (static_cast<uint8_t>(type)) {
case 0:
*container_name = "NTFS Container";
*image_type = "OS";
return true;
case 1:
*container_name = "NTFS Container";
*image_type = "APP";
return true;
case 2:
*container_name = "EXFAT Container";
*image_type = "OPTION";
return true;
default:
if (error) *error = std::format("Unsupported BootId container type: {}", static_cast<int>(type));
return false;
}
}
// 查找AES密钥和IV:优先用config.ini override,为空则查内置密钥表
// 规则:取文件名前4字符作为GameID,.app用AppKey/AppIV,.opt用OptKey/OptIV
bool lookup_key_iv(const std::wstring &container_path, uint8_t out_key[16], uint8_t out_iv[16],
std::string *error, const KeyOverride *override_keys) {
// 提取文件名
std::wstring filename = container_path;
size_t slash = filename.find_last_of(L"\\/");
if (slash != std::wstring::npos) filename = filename.substr(slash + 1);
// GameID = 文件名前4个字符
if (filename.size() < 4) {
if (error) *error = "Filename too short for GameID";
return false;
}
std::string game_id;
game_id.reserve(4);
for (int i = 0; i < 4; i++) {
game_id.push_back(static_cast<char>(filename[i]));
}
// 判断扩展名:.app 或 .opt
bool is_app = filename.size() >= 4 &&
_wcsicmp(filename.substr(filename.size() - 4).c_str(), L".app") == 0;
bool is_opt = filename.size() >= 4 &&
_wcsicmp(filename.substr(filename.size() - 4).c_str(), L".opt") == 0;
if (!is_app && !is_opt) {
if (error) *error = "Filename must end with .app or .opt";
return false;
}
// 优先使用config.ini指定的密钥(override)
if (override_keys) {
const std::string &key_hex = is_app ? override_keys->app_key_hex : override_keys->opt_key_hex;
const std::string &iv_hex = is_app ? override_keys->app_iv_hex : override_keys->opt_iv_hex;
if (!key_hex.empty() && !iv_hex.empty()) {
if (!utils::hex_to_bytes(key_hex, out_key, 16) ||
!utils::hex_to_bytes(iv_hex, out_iv, 16)) {
if (error) *error = std::format("Invalid hex in config override: {}/{}", key_hex, iv_hex);
return false;
}
return true;
}
}
// 从编译期密钥表查找GameID,直接拷贝字节数组
for (const auto &entry: game_keys::ENTRIES) {
if (_stricmp(entry.game_id, game_id.c_str()) != 0) continue;
const uint8_t *key = is_app ? entry.app_key : entry.opt_key;
const uint8_t *iv = is_app ? entry.app_iv : entry.opt_iv;
memcpy(out_key, key, 16);
memcpy(out_iv, iv, 16);
return true;
}
if (error) *error = std::format("GameID {} not found in keys table", game_id);
return false;
}
// 挂载容器:读取BootId、解密、识别类型,向驱动发送MOUNT IOCTL
MountResult mount_container(const std::wstring &container_path, const std::wstring &tag,
const KeyOverride *override_keys) {
MountResult result = {};
// Open Fscrypt driver
HANDLE hDriver = CreateFileW(L"\\\\.\\Fscrypt", GENERIC_READ | GENERIC_WRITE,
0, nullptr, OPEN_EXISTING, 0x80, nullptr);
if (hDriver == INVALID_HANDLE_VALUE) {
result.error = std::format("Open Fscrypt Driver Failed: {}", GetLastError());
return result;
}
// Look up AES key and IV from embedded keys.json based on filename
uint8_t key[16];
uint8_t app_iv[16];
std::string err;
if (!lookup_key_iv(container_path, key, app_iv, &err, override_keys)) {
result.error = std::format("Lookup key/IV failed: {}", err);
CloseHandle(hDriver);
return result;
}
// Read container BootId (0x60 bytes)
FILE *container_file = nullptr;
if (_wfopen_s(&container_file, container_path.c_str(), L"rb") != 0 || !container_file) {
result.error = "Open container file failed";
CloseHandle(hDriver);
return result;
}
uint8_t bootid_encrypted[BOOTID_SIZE] = {};
size_t bootid_read = fread(bootid_encrypted, 1, BOOTID_SIZE, container_file);
if (bootid_read != BOOTID_SIZE) {
result.error = "Read BootId failed";
fclose(container_file);
CloseHandle(hDriver);
return result;
}
// Decrypt BootId with hardcoded key (CBC)
err.clear();
auto bootid_decrypted = aes::cbc_decrypt(crypto::BOOT_KEY, crypto::BOOT_IV,
bootid_encrypted, BOOTID_SIZE, &err);
if (bootid_decrypted.empty()) {
result.error = std::format("Decrypt BootId failed: {}", err);
fclose(container_file);
CloseHandle(hDriver);
return result;
}
// Parse BootId
BootIdInfo info{};
if (!parse_boot_id(bootid_decrypted.data(), bootid_decrypted.size(), &info, &err)) {
result.error = std::format("Parse BootId failed: {}", err);
fclose(container_file);
CloseHandle(hDriver);
return result;
}
// Recognize container type
const char *container_name = nullptr;
const char *image_type = nullptr;
if (!recognize_container(info.type, &container_name, &image_type, &err)) {
result.error = std::format("Recognize BootId container failed: {}", err);
fclose(container_file);
CloseHandle(hDriver);
return result;
}
LOG_INFO("{} ({})", container_name, image_type);
fclose(container_file);
// Prepare mount IOCTL input buffer (total 0x280 = 640 bytes)
// Layout from fscrypt_link.exe sub_140005620 (Buffer is wchar_t[], offsets in bytes):
// 0x000: int32 code = 640
// 0x004: wchar_t path[258] (NT path: \??\...)
// 0x208: uint64_t unk_208 = 0x200000
// 0x210: uint64_t unk_210 = 0
// 0x218: uint64_t unk_218 = 1
// 0x220: uint8_t key[16] (AES key from key.bin / JSON)
// 0x230: uint8_t iv[16] (AES IV, calculated or from JSON)
// 0x240: wchar_t tag[32]
#pragma pack(push, 1)
struct MountInBuffer {
int code;
wchar_t path[258];
uint64_t unk_208;
uint64_t unk_210;
uint64_t unk_218;
uint8_t key[16];
uint8_t iv[16];
wchar_t tag[32];
};
#pragma pack(pop)
static_assert(sizeof(MountInBuffer) == 0x280, "MountInBuffer must be 0x280");
MountInBuffer in_buf{};
memset(&in_buf, 0, sizeof(in_buf));
in_buf.code = 640;
// Build NT path: \??\<normalized_path>
std::wstring nt_path = std::format(L"\\??\\{}", utils::normalize_path(container_path));
wcsncpy_s(in_buf.path, nt_path.c_str(), 257);
in_buf.unk_208 = 0x200000;
in_buf.unk_210 = 0;
in_buf.unk_218 = 1;
// Copy AES key and IV from JSON lookup (key at 0x220, iv at 0x230)
memcpy(in_buf.key, key, 16);
memcpy(in_buf.iv, app_iv, 16);
// Copy tag
wcsncpy_s(in_buf.tag, tag.c_str(), 31);
// Send mount IOCTL
uint8_t out_buf[1024] = {};
DWORD bytes_returned = 0;
if (!DeviceIoControl(hDriver, IOCTL_FSCRYPT_MOUNT,
&in_buf, sizeof(in_buf),
out_buf, sizeof(out_buf),
&bytes_returned, nullptr)) {
result.error = std::format("DeviceIoControl failed, Error = {}", GetLastError());
CloseHandle(hDriver);
return result;
}
// Query symlink to verify mount
std::wstring device_path = std::format(L"\\\\.\\FscryptDisk_{}", tag);
HANDLE hCheck = CreateFileW(device_path.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE,
nullptr, OPEN_EXISTING, 0, nullptr);
if (hCheck != INVALID_HANDLE_VALUE) {
CloseHandle(hCheck);
result.success = true;
} else {
LOG_ERROR("Query Symbol Link Failed");
result.success = false;
}
CloseHandle(hDriver);
return result;
}
// 卸载容器:向驱动发送UNMOUNT IOCTL
bool unmount_container(const std::wstring &tag) {
HANDLE hDriver = CreateFileW(L"\\\\.\\Fscrypt", GENERIC_READ | GENERIC_WRITE,
0, nullptr, OPEN_EXISTING, 0x80, nullptr);
if (hDriver == INVALID_HANDLE_VALUE) {
LOG_ERROR("Open Fscrypt Driver Failed: {}", GetLastError());
return false;
}
struct UnmountInBuffer {
int code;
wchar_t path[258];
};
UnmountInBuffer in_buf{};
memset(&in_buf, 0, sizeof(in_buf));
in_buf.code = 516; // 0x204
// Build path: \??\FscryptDisk_<tag> (no trailing backslash in comment)
std::wstring nt_path = std::format(L"\\??\\FscryptDisk_{}\\", tag);
wcsncpy_s(in_buf.path, nt_path.c_str(), 257);
uint8_t out_buf[1024] = {};
DWORD bytes_returned = 0;
if (DeviceIoControl(hDriver, IOCTL_FSCRYPT_UNMOUNT,
&in_buf, sizeof(in_buf),
out_buf, sizeof(out_buf),
&bytes_returned, nullptr)) {
CloseHandle(hDriver);
return true;
}
LOG_ERROR("UNMOUNT failed: Error = {}", GetLastError());
CloseHandle(hDriver);
return false;
}
// 链接容器:为已挂载的卷设备创建重解析点(目录链接)
bool link_container(const std::wstring &tag, const std::wstring &link_path) {
// Verify device exists: \\.\FscryptDisk_<tag>
std::wstring device_path = std::format(L"\\\\.\\FscryptDisk_{}", tag);
HANDLE hCheck = CreateFileW(device_path.c_str(), 0,
FILE_SHARE_READ | FILE_SHARE_WRITE,
nullptr, OPEN_EXISTING, 0, nullptr);
if (hCheck == INVALID_HANDLE_VALUE) {
LOG_ERROR("LINK failed: Non-existent device");
return false;
}
CloseHandle(hCheck);
// Build source: NT path format \??\FscryptDisk_<tag> (with trailing backslash)
std::wstring source = std::format(L"\\??\\FscryptDisk_{}\\", tag);
// Normalize link_path: replace / with \, strip trailing backslashes
std::wstring target = link_path;
for (auto &c: target) if (c == L'/') c = L'\\';
while (target.size() > 3 && target.back() == L'\\') target.pop_back();
// 创建重解析点
// REPARSE_DATA_BUFFER结构(共16字节头部):
// 0x00: ReparseTag (4字节)
// 0x04: ReparseDataLength (2字节)
// 0x06: Reserved (2字节)
// 0x08: SubstituteNameOffset (2字节)
// 0x0A: SubstituteNameLength (2字节)
// 0x0C: PrintNameOffset (2字节)
// 0x0E: PrintNameLength (2字节)
// 0x10: PathBuffer (变长)
size_t sub_len = source.size();
size_t print_len = target.size();
size_t buf_size = 16 + (sub_len + 1) * 2 + (print_len + 1) * 2;
std::vector<uint8_t> reparse_buf(buf_size, 0);
// ReparseTag
*reinterpret_cast<DWORD *>(reparse_buf.data()) = IO_REPARSE_TAG_MOUNT_POINT;
// ReparseDataLength
*reinterpret_cast<WORD *>(reparse_buf.data() + 4) = static_cast<WORD>(buf_size - 8);
// Reserved
*reinterpret_cast<WORD *>(reparse_buf.data() + 6) = 0;
// SubstituteNameOffset
*reinterpret_cast<WORD *>(reparse_buf.data() + 8) = 0;
// SubstituteNameLength
*reinterpret_cast<WORD *>(reparse_buf.data() + 10) = static_cast<WORD>(sub_len * 2);
// PrintNameOffset
*reinterpret_cast<WORD *>(reparse_buf.data() + 12) = static_cast<WORD>((sub_len + 1) * 2);
// PrintNameLength
*reinterpret_cast<WORD *>(reparse_buf.data() + 14) = static_cast<WORD>(print_len * 2);
// Copy substitute name (source)
memcpy(reparse_buf.data() + 16, source.c_str(), sub_len * 2);
// null terminator
*reinterpret_cast<WCHAR *>(reparse_buf.data() + 16 + sub_len * 2) = 0;
// Copy print name (target)
memcpy(reparse_buf.data() + 16 + (sub_len + 1) * 2, target.c_str(), print_len * 2);
// null terminator
*reinterpret_cast<WCHAR *>(reparse_buf.data() + 16 + (sub_len + 1) * 2 + print_len * 2) = 0;
// 递归创建目录及其父目录(CreateDirectoryW无法递归创建父目录)
namespace fs = std::filesystem;
std::error_code mk_ec;
fs::create_directories(link_path, mk_ec);
if (mk_ec && !fs::exists(link_path)) {
LOG_ERROR("CreateDirectory failed for {}: {} {}", utils::wstr_to_str(link_path), mk_ec.value(),
mk_ec.message());
return false;
}
// Open the directory
HANDLE hDir = CreateFileW(link_path.c_str(), GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
nullptr, OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, nullptr);
if (hDir == INVALID_HANDLE_VALUE) {
LOG_ERROR("CreateFileW failed for {}: {}", utils::wstr_to_str(link_path), GetLastError());
return false;
}
DWORD bytes_returned = 0;
bool ok = DeviceIoControl(hDir, FSCTL_SET_REPARSE_POINT,
reparse_buf.data(), static_cast<DWORD>(buf_size),
nullptr, 0, &bytes_returned, nullptr);
if (!ok) {
LOG_ERROR("FSCTL_SET_REPARSE_POINT failed: {}", GetLastError());
}
CloseHandle(hDir);
if (ok) {
LOG_INFO("Linked {} -> {}", utils::wstr_to_str(target), utils::wstr_to_str(source));
}
return ok;
}
// 取消链接:移除重解析点目录(junction),不影响目标卷设备
bool unlink_container(const std::wstring &link_path) {
// 规范化路径
std::wstring path = link_path;
for (auto &c: path) if (c == L'/') c = L'\\';
while (path.size() > 3 && path.back() == L'\\') path.pop_back();
// 不存在则视为已移除
if (GetFileAttributesW(path.c_str()) == INVALID_FILE_ATTRIBUTES) {
return true;
}
// mount point重解析点可直接用RemoveDirectoryW移除(会删除junction而非目标内容)
if (!RemoveDirectoryW(path.c_str())) {
LOG_ERROR("RemoveDirectory failed for {}: {}", utils::wstr_to_str(path), GetLastError());
return false;
}
LOG_INFO("Removed link {}", utils::wstr_to_str(path));
return true;
}
}
+82
View File
@@ -0,0 +1,82 @@
#pragma once
#include <windows.h>
#include <cstdint>
#include <string>
namespace fscrypt {
constexpr size_t BOOTID_SIZE = 0x60;
// AES密钥和IV已统一至 utils/crypto_constants.h 的 crypto::BOOT_KEY / BOOT_IV
// IOCTL码(FSCTL_SET_REPARSE_POINT和IO_REPARSE_TAG_MOUNT_POINT
// 已在winioctl.h/winnt.h中定义)
constexpr DWORD IOCTL_FSCRYPT_MOUNT = 0x22E008;
constexpr DWORD IOCTL_FSCRYPT_UNMOUNT = 0x22E00C;
// 容器类型
enum class ContainerType : uint8_t {
Ntfs = 0, // NTFS容器
NtfsApp = 1, // NTFS APP容器
Exfat = 2 // EXFAT容器
};
// BootId解析结果
struct BootIdInfo {
ContainerType type; // 容器类型
uint64_t data_offset; // 数据偏移
uint64_t data_size; // 数据大小
uint64_t sector_size; // 扇区大小
uint64_t total_sectors; // 总扇区数
uint64_t encrypted_offset; // 加密偏移
const char *container_name; // 容器名称
const char *image_type; // 镜像类型
};
// 挂载结果
struct MountResult {
bool success;
std::wstring nt_target;
std::string error;
};
// 解析解密后的BootId
bool parse_boot_id(const uint8_t *decrypted_bootid, size_t size,
BootIdInfo *info, std::string *error = nullptr);
// 识别容器类型,返回容器名称和镜像类型
bool recognize_container(ContainerType type,
const BootIdInfo &info,
const char **container_name,
const char **image_type,
std::string *error = nullptr);
// 密钥覆盖配置(从config.ini读取,hex字符串,空表示用内置密钥表)
struct KeyOverride {
std::string app_key_hex;
std::string app_iv_hex;
std::string opt_key_hex;
std::string opt_iv_hex;
};
// 查找AES密钥和IV:优先用override,为空则查内置密钥表
// 规则:取文件名前4字符作为GameID,.app用AppKey/AppIV,.opt用OptKey/OptIV
bool lookup_key_iv(const std::wstring &container_path,
uint8_t out_key[16], uint8_t out_iv[16],
std::string *error = nullptr,
const KeyOverride *override_keys = nullptr);
// 挂载容器:读取BootId、解密、识别类型,向驱动发送MOUNT IOCTL
MountResult mount_container(const std::wstring &container_path,
const std::wstring &tag,
const KeyOverride *override_keys = nullptr);
// 卸载容器:向驱动发送UNMOUNT IOCTL
bool unmount_container(const std::wstring &tag);
// 链接容器:为已挂载的卷设备创建重解析点(目录链接)
bool link_container(const std::wstring &tag, const std::wstring &link_path);
// 取消链接:移除重解析点(目录链接),不影响已挂载的卷设备
bool unlink_container(const std::wstring &link_path);
}
+400
View File
@@ -0,0 +1,400 @@
#define LOG_MODULE "ICF"
#include "icf_mount.h"
#include "icf_parser.h"
#include "fscrypt.h"
#include "vhd_mount.h"
#include "utils/files.h"
#include "utils/strings.h"
#include "utils/log.h"
#include <map>
#include <format>
#include <ranges>
#include <windows.h>
namespace icf_mount {
// 前向声明
static std::vector<const icf::DecodedEntry *> get_active_apps(const icf::IcfFile &icf_file);
static std::vector<const icf::DecodedEntry *> get_active_opts(const icf::IcfFile &icf_file);
// 解析ICF文件并挂载所有Active状态的APP和OPT容器
// 流程:挂载APP → 创建/挂载overlay → 挂载OPT → 创建LINK
IcfMountResult mount_icf_apps(const Config &cfg) {
IcfMountResult result{};
result.success = false;
// 读取ICF文件
auto icf_data = utils::read_file(cfg.icf_path);
if (icf_data.empty()) {
result.error = "Failed to read ICF file";
return result;
}
// 解密并解析ICF(parse_file内部会处理解密)
std::string parse_err;
auto icf_file = icf::parse_file(icf_data, &parse_err);
if (!icf_file) {
result.error = std::format("Parse ICF failed: {}", parse_err);
return result;
}
// 筛选Active状态的APP条目,按part_index排序
auto app_entries = get_active_apps(*icf_file);
if (app_entries.empty()) {
result.error = "No active APP entries found in ICF";
return result;
}
// 验证base version依赖关系
// part 0 没有base version
// part n (n>0) 的 base_version 应该等于 part n-1 的 version
for (size_t i = 1; i < app_entries.size(); i++) {
auto *cur = app_entries[i];
auto *prev = app_entries[i - 1];
if (cur->part_index != prev->part_index + 1) {
result.error = std::format("Part index not consecutive: {} -> {}", prev->part_index, cur->part_index);
return result;
}
if (cur->base_version != prev->version) {
result.error = std::format("Base version mismatch for part {}: {} != {}", cur->part_index,
cur->base_version, prev->version);
return result;
}
}
// APP/OPT文件所在目录:优先使用image_dir,否则使用exe所在目录
std::wstring dir = cfg.image_dir.empty() ? utils::get_exe_dir() : cfg.image_dir;
// 构造密钥override(config.ini [KEYS] section,空则用内置密钥表)
fscrypt::KeyOverride key_override;
key_override.app_key_hex = cfg.app_key;
key_override.app_iv_hex = cfg.app_iv;
key_override.opt_key_hex = cfg.opt_key;
key_override.opt_iv_hex = cfg.opt_iv;
// 依次挂载每个APP容器
bool all_ok = true;
for (auto entry: app_entries) {
MountEntry mount_entry{};
mount_entry.filename = entry->filename;
mount_entry.version = entry->version;
mount_entry.part_index = entry->part_index;
mount_entry.tag = std::format("APP_{}", entry->part_index);
mount_entry.vhd_success = false;
std::wstring app_path = std::format(L"{}\\{}", dir, utils::to_wstring(entry->filename));
std::wstring tag = std::format(L"APP_{}", entry->part_index);
LOG_INFO("Mount part {}: {}", entry->part_index, entry->filename);
auto mount_res = fscrypt::mount_container(app_path, tag, &key_override);
mount_entry.success = mount_res.success;
mount_entry.error = mount_res.error;
if (!mount_res.success) {
LOG_ERROR("Mount part {} failed: {}", entry->part_index, mount_res.error);
all_ok = false;
}
result.apps.push_back(mount_entry);
}
// 所有APP容器挂载完成后,处理overlay写层:
// overlay.vhd不存在 → 创建差分盘 + 拷贝overlay目录文件
// overlay.vhd已存在 → 直接挂载(数据已在里面,无需重新拷贝)
if (all_ok && !app_entries.empty()) {
auto *last = app_entries.back();
std::wstring exe_dir = utils::get_exe_dir();
std::wstring overlay_vhd = std::format(L"{}\\overlay.vhd", exe_dir);
std::wstring last_tag = std::format(L"APP_{}", last->part_index);
std::wstring parent_for_create = std::format(L"\\\\?\\FscryptDisk_{}\\internal_{}.vhd", last_tag,
last->part_index);
bool vhd_exists = GetFileAttributesW(overlay_vhd.c_str()) != INVALID_FILE_ATTRIBUTES;
if (!vhd_exists) {
// 创建overlay.vhd差分盘
LOG_INFO("Creating overlay.vhd (parent: internal_{}.vhd)", last->part_index);
std::string ov_err;
if (!vhd_mount::create_diff_vhd(overlay_vhd, parent_for_create, &ov_err)) {
LOG_ERROR("Overlay create failed: {}", ov_err);
all_ok = false;
}
} else {
LOG_INFO("Reusing existing overlay.vhd");
// 先尝试detach,处理上次未正常卸载的情况
vhd_mount::detach_vhd(overlay_vhd, nullptr);
}
if (all_ok) {
// 挂载overlay.vhd
vhd_mount::MountedVhdInfo vhd_info{};
std::string attach_err;
if (!vhd_mount::attach_vhd(overlay_vhd, vhd_info, &attach_err)) {
LOG_ERROR("Overlay attach failed: {}", attach_err);
all_ok = false;
} else {
result.apps.back().vhd_success = true;
// 分配盘符到overlay卷
char drive = static_cast<char>(cfg.mount_letter);
std::wstring mount_point = std::format(L"{}:\\", cfg.mount_letter);
DeleteVolumeMountPointW(mount_point.c_str()); // 清理可能残留的旧挂载点
if (!SetVolumeMountPointW(mount_point.c_str(), vhd_info.volume_guid.c_str())) {
LOG_ERROR("Assign drive {} failed: Error = {}", drive, GetLastError());
all_ok = false;
} else {
LOG_INFO("Overlay mounted to {}:", drive);
// 仅新建overlay.vhd时才拷贝overlay目录文件
// OVERLAY_DIR为空或不存在时跳过拷贝
if (!vhd_exists) {
if (!cfg.overlay_dir.empty()) {
DWORD attr = GetFileAttributesW(cfg.overlay_dir.c_str());
bool dir_exists = attr != INVALID_FILE_ATTRIBUTES &&
attr & FILE_ATTRIBUTE_DIRECTORY;
if (dir_exists) {
std::string cp_err;
size_t copied = 0;
if (!utils::copy_dir_overwrite(cfg.overlay_dir, mount_point,
&cp_err, &copied)) {
LOG_ERROR("Overlay copy failed: {}", cp_err);
all_ok = false;
} else {
LOG_INFO("Copied {} files", copied);
}
} else {
LOG_INFO("OVERLAY_DIR not found, skip copy");
}
} else {
LOG_INFO("OVERLAY_DIR empty, skip copy");
}
}
}
}
}
}
// 挂载OPT容器(独立于APP/overlay,不受其结果影响)
auto opt_entries = get_active_opts(*icf_file);
for (auto entry: opt_entries) {
MountEntry mount_entry{};
mount_entry.filename = entry->filename;
mount_entry.version = entry->version;
mount_entry.part_index = entry->part_index;
mount_entry.tag = std::format("OPT_{}", entry->version);
mount_entry.vhd_success = false;
std::wstring opt_path = std::format(L"{}\\{}", dir, utils::to_wstring(entry->filename));
std::wstring opt_tag = std::format(L"OPT_{}", utils::to_wstring(entry->version));
LOG_INFO("Mount OPT {}: {}", entry->version, entry->filename);
auto mount_res = fscrypt::mount_container(opt_path, opt_tag, &key_override);
mount_entry.success = mount_res.success;
mount_entry.error = mount_res.error;
if (!mount_res.success) {
LOG_ERROR("Mount OPT {} failed: {}", entry->version, mount_res.error);
}
result.opts.push_back(mount_entry);
}
// 创建APP LINK(仅对挂载成功的APP)
if (!cfg.app_link.empty()) {
for (const auto &app: result.apps) {
if (!app.success) continue;
std::wstring tag = std::format(L"APP_{}", app.part_index);
std::wstring link_path = std::format(L"{}\\{}", cfg.app_link, app.part_index);
if (!fscrypt::link_container(tag, link_path)) {
LOG_ERROR("Link APP {} failed", app.part_index);
}
}
}
// 创建OPT LINK(仅对挂载成功的OPT)
if (!cfg.opt_link.empty()) {
for (const auto &opt: result.opts) {
if (!opt.success) continue;
std::wstring tag = std::format(L"OPT_{}", utils::to_wstring(opt.version));
std::wstring link_path = std::format(L"{}\\{}", cfg.opt_link, utils::to_wstring(opt.version));
if (!fscrypt::link_container(tag, link_path)) {
LOG_ERROR("Link OPT {} failed", opt.version);
}
}
}
result.success = all_ok;
if (!all_ok) {
result.error = "Some APPs failed to mount";
}
return result;
}
// 从ICF中获取所有Active状态的APP条目,同一part_index只保留timestamp最新的,按part_index升序
static std::vector<const icf::DecodedEntry *> get_active_apps(const icf::IcfFile &icf_file) {
// 按 part_index 分组,每组保留 timestamp 最大的条目(timestamp格式为 YYYYMMDDhhmmss,定长数字串可直接比较)
std::map<uint8_t, const icf::DecodedEntry *> latest_by_part;
for (const auto &entry: icf_file.entries) {
if (entry.type == icf::EntryType::App &&
static_cast<uint16_t>(entry.status) == 0x0102) {
auto it = latest_by_part.find(entry.part_index);
if (it == latest_by_part.end() || entry.timestamp > it->second->timestamp) {
latest_by_part[entry.part_index] = &entry;
}
}
}
// map 按 key 升序,直接收集 values 即可
std::vector<const icf::DecodedEntry *> app_entries;
app_entries.reserve(latest_by_part.size());
for (const auto *entry: latest_by_part | std::views::values) {
app_entries.push_back(entry);
}
return app_entries;
}
// 从ICF中获取所有Active状态的OPT条目,同一version只保留timestamp最新的
static std::vector<const icf::DecodedEntry *> get_active_opts(const icf::IcfFile &icf_file) {
// OPT的version字段为4字符名称(如A001),按此分组去重
std::map<std::string, const icf::DecodedEntry *> latest_by_name;
for (const auto &entry: icf_file.entries) {
if (entry.type == icf::EntryType::Opt &&
static_cast<uint16_t>(entry.status) == 0x0102) {
auto it = latest_by_name.find(entry.version);
if (it == latest_by_name.end() || entry.timestamp > it->second->timestamp) {
latest_by_name[entry.version] = &entry;
}
}
}
std::vector<const icf::DecodedEntry *> opt_entries;
opt_entries.reserve(latest_by_name.size());
for (const auto *entry: latest_by_name | std::views::values) {
opt_entries.push_back(entry);
}
return opt_entries;
}
// 解析ICF文件并卸载所有容器
// 流程:移除LINK → 卸载overlay → 倒序卸载APP → 卸载OPT
IcfMountResult unmount_icf_apps(const Config &cfg) {
IcfMountResult result{};
result.success = false;
// 读取并解析ICF
auto icf_data = utils::read_file(cfg.icf_path);
if (icf_data.empty()) {
result.error = "Failed to read ICF file";
return result;
}
std::string parse_err;
auto icf_file = icf::parse_file(icf_data, &parse_err);
if (!icf_file) {
result.error = std::format("Parse ICF failed: {}", parse_err);
return result;
}
auto app_entries = get_active_apps(*icf_file);
auto opt_entries = get_active_opts(*icf_file);
bool all_ok = true;
// 1) 移除APP LINK
if (!cfg.app_link.empty()) {
for (auto *entry: app_entries) {
std::wstring link_path = std::format(L"{}\\{}", cfg.app_link, entry->part_index);
fscrypt::unlink_container(link_path);
}
}
// 2) 移除OPT LINK
if (!cfg.opt_link.empty()) {
for (auto *entry: opt_entries) {
std::wstring link_path = std::format(L"{}\\{}", cfg.opt_link,
utils::to_wstring(entry->version));
fscrypt::unlink_container(link_path);
}
}
// 3) overlay.vhd:移除盘符 + detach(保留文件供下次沿用)
std::wstring exe_dir = utils::get_exe_dir();
std::wstring overlay_vhd = std::format(L"{}\\overlay.vhd", exe_dir);
{
char drive = static_cast<char>(cfg.mount_letter);
std::wstring mount_point = std::format(L"{}:\\", cfg.mount_letter);
if (DeleteVolumeMountPointW(mount_point.c_str())) {
LOG_INFO("Removed drive letter {}", drive);
}
WIN32_FILE_ATTRIBUTE_DATA fa;
if (GetFileAttributesExW(overlay_vhd.c_str(), GetFileExInfoStandard, &fa)) {
LOG_INFO("Detaching overlay.vhd...");
std::string vhd_err;
if (!vhd_mount::detach_vhd(overlay_vhd, &vhd_err)) {
LOG_ERROR("Overlay detach failed: {}", vhd_err);
all_ok = false;
} else {
LOG_INFO("Detached overlay.vhd");
}
} else {
LOG_INFO("overlay.vhd not found, skip detach");
}
}
// 4) 倒序卸载APP容器
for (auto it = app_entries.rbegin(); it != app_entries.rend(); ++it) {
auto *entry = *it;
MountEntry mount_entry{};
mount_entry.filename = entry->filename;
mount_entry.version = entry->version;
mount_entry.part_index = entry->part_index;
mount_entry.tag = std::format("APP_{}", entry->part_index);
mount_entry.vhd_success = false;
std::wstring tag = std::format(L"APP_{}", entry->part_index);
LOG_INFO("Unmount part {}: {}", entry->part_index, entry->filename);
bool ok = fscrypt::unmount_container(tag);
mount_entry.success = ok;
if (!ok) {
mount_entry.error = "Unmount failed";
LOG_ERROR("Unmount part {} failed", entry->part_index);
all_ok = false;
}
result.apps.push_back(mount_entry);
}
// 5) 卸载OPT容器
for (auto *entry: opt_entries) {
MountEntry mount_entry{};
mount_entry.filename = entry->filename;
mount_entry.version = entry->version;
mount_entry.part_index = entry->part_index;
mount_entry.tag = std::format("OPT_{}", entry->version);
mount_entry.vhd_success = false;
std::wstring opt_tag = std::format(L"OPT_{}", utils::to_wstring(entry->version));
LOG_INFO("Unmount OPT {}: {}", entry->version, entry->filename);
bool ok = fscrypt::unmount_container(opt_tag);
mount_entry.success = ok;
if (!ok) {
mount_entry.error = "Unmount failed";
LOG_ERROR("Unmount OPT {} failed", entry->version);
all_ok = false;
}
result.opts.push_back(mount_entry);
}
result.success = all_ok;
if (!all_ok) {
result.error = "Some containers failed to unmount";
}
return result;
}
}
+34
View File
@@ -0,0 +1,34 @@
#pragma once
#include "config.h"
#include <string>
#include <vector>
namespace icf_mount {
// 单个容器挂载结果(APP和OPT通用)
struct MountEntry {
std::string filename; // 容器文件名
std::string version; // 版本号(OPT为4字符名称)
uint8_t part_index; // part编号
std::string tag; // 挂载标签 (APP_0, OPT_A001, ...)
bool success; // 容器是否挂载成功
std::string error; // 错误信息
bool vhd_success; // VHD是否挂载成功(仅APP有overlay)
};
// ICF挂载结果
struct IcfMountResult {
bool success;
std::string error;
std::vector<MountEntry> apps;
std::vector<MountEntry> opts;
};
// 解析ICF文件并挂载其中所有Active状态的APP和OPT容器
// 流程:挂载APP → 创建/挂载overlay → 挂载OPT → 创建LINK
IcfMountResult mount_icf_apps(const Config &cfg);
// 解析ICF文件并卸载所有容器
// 流程:移除LINK → 卸载overlay → 倒序卸载APP → 卸载OPT
IcfMountResult unmount_icf_apps(const Config &cfg);
}
+227
View File
@@ -0,0 +1,227 @@
#include "icf_parser.h"
#include "utils/aes.h"
#include "utils/crc32.h"
#include "utils/crypto_constants.h"
#include <algorithm>
#include <format>
namespace icf {
static std::string dec2(uint8_t val) {
return std::format("{:02}", val);
}
static std::string dec4(uint16_t val) {
return std::format("{:04}", val);
}
std::string decode_version(const uint8_t *ver, bool zeropad) {
uint16_t major = static_cast<uint16_t>(ver[3]) << 8 | ver[2];
std::string major_str = zeropad ? dec4(major) : std::to_string(major);
return std::format("{}.{}.{}", major_str, dec2(ver[1]), dec2(ver[0]));
}
std::string decode_time(const uint8_t *vals) {
uint16_t year = static_cast<uint16_t>(vals[1]) << 8 | vals[0];
return std::format("{:04}{:02}{:02}{:02}{:02}{:02}", year, vals[2], vals[3], vals[4], vals[5], vals[6]);
}
std::string encode_status(uint16_t magic) {
magic &= 0xFFFF;
switch (magic) {
case 0x0102: return "";
case 0x0201: return "#downloading";
case 0x0202: return "#staged";
default: {
uint16_t be = (magic >> 8 | magic << 8) & 0xFFFF;
return std::format("#{:04x}", be);
}
}
}
std::string sanity_check(const uint8_t *data, size_t length) {
if (!data) return "Invalid input";
if (length < 0x40) return "Bad length";
if (length & 0xF) return "Bad alignment";
uint32_t len_field = *reinterpret_cast<const uint32_t *>(data + 4);
if (len_field != length) return "Bad length";
uint32_t crc_field = *reinterpret_cast<const uint32_t *>(data + 0);
uint32_t crc_calc = crc32(data + 4, length - 4);
if (crc_calc != crc_field) return "Bad CRC";
uint16_t enc_sections = *reinterpret_cast<const uint16_t *>(data + 0x10);
uint32_t num_sections = length / 0x40;
if (enc_sections != num_sections - 1) return "Bad section count";
uint32_t crc2 = 0;
for (uint32_t i = 1; i < num_sections; i++) {
const uint8_t *secdata = data + i * 0x40;
if (secdata[0] != 2 || secdata[1] != 1) continue;
crc2 ^= crc32(secdata, 0x40);
}
uint32_t crcsum2 = *reinterpret_cast<const uint32_t *>(data + 0x20);
if (crcsum2 != crc2) return "Bad section CRC";
return "";
}
std::optional<std::vector<uint8_t> > decrypt(const std::vector<uint8_t> &encrypted,
std::string *error) {
if (encrypted.size() & 0xF) {
if (error) *error = "Data length not aligned to 16 bytes";
return std::nullopt;
}
std::string err;
auto result = aes::cbc_decrypt(crypto::BOOT_KEY, crypto::BOOT_IV, encrypted.data(), encrypted.size(), &err);
if (result.empty()) {
if (error) *error = std::format("Decryption failed: {}", err);
return std::nullopt;
}
return result;
}
std::optional<IcfFile> parse_file(const std::vector<uint8_t> &encrypted_data,
std::string *error) {
IcfFile file;
std::string dec_err;
auto decrypted = decrypt(encrypted_data, &dec_err);
if (!decrypted) {
if (error) *error = dec_err;
return std::nullopt;
}
file.raw_data = *decrypted;
std::string sanity_err = sanity_check(file.raw_data.data(), file.raw_data.size());
file.sanity_error = sanity_err;
std::memcpy(&file.header, file.raw_data.data(), sizeof(IcfHeader));
char game_id[5] = {};
char plat_id[4] = {};
std::memcpy(game_id, file.header.game_id, 4);
std::memcpy(plat_id, file.header.platform_id, 3);
std::string hdr_name = std::format("{}_{}", game_id, plat_id);
if (file.header.platform_gen) {
hdr_name += static_cast<char>('0' + file.header.platform_gen);
}
hdr_name += ".icf";
file.header_filename = hdr_name;
auto num_sections = static_cast<uint32_t>(file.raw_data.size() / 0x40);
uint32_t sysver = 0;
std::vector<std::string> app_datanames;
std::vector<std::string> app_timestamps;
for (uint32_t i = 1; i < num_sections; i++) {
DecodedEntry entry;
entry.section_index = i;
const auto *sec = reinterpret_cast<const IcfSection *>(file.raw_data.data() + i * 0x40);
entry.type = static_cast<EntryType>(sec->type);
entry.status = static_cast<EntryStatus>(sec->magic);
entry.part_index = sec->part_index;
entry.timestamp = decode_time(sec->timestamp);
std::string pfx, sfx, dataname;
switch (sec->type) {
case 0: {
sfx = "0.pack";
pfx = std::string(file.header.platform_id, 3);
dataname = decode_version(sec->version, true);
entry.version = dataname;
if (sysver != 0) {
entry.error = "Redundant SYSTEM image";
}
sysver = *reinterpret_cast<const uint32_t *>(sec->platform_version);
break;
}
case 1: {
dataname = decode_version(sec->version);
entry.version = dataname;
app_datanames.push_back(dataname);
app_timestamps.push_back(entry.timestamp);
if (sec->part_index) {
std::string basename = decode_version(sec->base_version);
entry.base_version = basename;
entry.base_timestamp = decode_time(sec->base_timestamp);
bool found_base = false;
for (size_t k = 0; k < app_datanames.size(); k++) {
if (app_datanames[k] == basename) {
if (app_timestamps[k] != entry.base_timestamp) {
entry.error = "Base APP timestamp mismatch";
}
found_base = true;
break;
}
}
if (!found_base) {
entry.error = std::format("Unable to locate base APP {}", basename);
}
sfx = std::format("{}_{}.app", sec->part_index, basename);
} else {
sfx = "0.app";
}
if (*reinterpret_cast<const uint32_t *>(sec->platform_version) != sysver) {
if (entry.error.empty()) entry.error = "SYSTEM version mismatch";
}
pfx = std::string(file.header.game_id, 4);
break;
}
case 2: {
sfx = "0.opt";
pfx = std::string(file.header.game_id, 4);
dataname = std::string(reinterpret_cast<const char *>(sec->version), 4);
entry.version = dataname;
break;
}
default: {
entry.error = std::format("Unknown Type: {}", sec->type);
file.entries.push_back(entry);
continue;
}
}
entry.filename = std::format("{}_{}_{}_{}{}", pfx, dataname, entry.timestamp, sfx,
encode_status(sec->magic));
file.entries.push_back(entry);
}
return file;
}
std::string infer_filename(const IcfFile &file) {
if (!file.sanity_error.empty()) return "";
std::string game_id(file.header.game_id, 4);
std::string plat_id(file.header.platform_id, 3);
std::string rom_ver;
std::string data_ver;
for (const auto &entry: file.entries) {
if (static_cast<uint16_t>(entry.status) != 0x0102) continue;
switch (entry.type) {
case EntryType::App:
rom_ver = entry.version;
break;
case EntryType::Opt:
if (entry.version > data_ver) data_ver = entry.version;
break;
default:
break;
}
}
if (!data_ver.empty()) data_ver = "_" + data_ver;
return std::format("{}_{}_{}{}.icf", game_id, plat_id, rom_ver, data_ver);
}
}
+98
View File
@@ -0,0 +1,98 @@
#pragma once
#include <cstdint>
#include <vector>
#include <string>
#include <optional>
namespace icf {
constexpr size_t SECTION_SIZE = 0x40;
constexpr size_t HEADER_SIZE = 0x40;
#pragma pack(push, 1)
struct IcfHeader {
uint32_t overall_crc;
uint32_t total_length;
uint8_t reserved_08[8];
uint16_t num_sections;
uint8_t reserved_12[6];
char game_id[4];
char platform_id[3];
uint8_t platform_gen;
uint32_t section_crc;
uint8_t reserved_24[28];
};
#pragma pack(pop)
static_assert(sizeof(IcfHeader) == 0x40, "IcfHeader size must be 0x40");
enum class EntryType : uint8_t {
Pack = 0,
App = 1,
Opt = 2
};
enum class EntryStatus : uint16_t {
Active = 0x0102,
Downloading = 0x0201,
Staged = 0x0202
};
#pragma pack(push, 1)
struct IcfSection {
uint16_t magic;
uint8_t reserved_02[2];
uint8_t type;
uint8_t part_index;
uint8_t type_reserved[2];
uint8_t reserved_08[24];
uint8_t version[4];
uint8_t timestamp[8];
uint8_t platform_version[4];
uint8_t base_version[4];
uint8_t base_timestamp[8];
uint8_t reserved_3c[4];
};
#pragma pack(pop)
static_assert(sizeof(IcfSection) == 0x40, "IcfSection size must be 0x40");
struct DecodedEntry {
std::string filename;
size_t section_index = 0;
EntryType type = EntryType::Pack;
EntryStatus status = EntryStatus::Active;
std::string version;
std::string timestamp;
uint8_t part_index = 0;
std::string base_version;
std::string base_timestamp;
std::string error;
};
struct IcfFile {
IcfHeader header{};
std::vector<uint8_t> raw_data;
std::vector<DecodedEntry> entries;
std::string header_filename;
std::string sanity_error;
};
// AES密钥和IV已统一至 utils/crypto_constants.h 的 crypto::BOOT_KEY / BOOT_IV
std::string decode_version(const uint8_t *ver, bool zeropad = false);
std::string decode_time(const uint8_t *vals);
std::string encode_status(uint16_t magic);
std::optional<IcfFile> parse_file(const std::vector<uint8_t> &encrypted_data,
std::string *error = nullptr);
std::optional<std::vector<uint8_t> > decrypt(const std::vector<uint8_t> &encrypted,
std::string *error = nullptr);
std::string sanity_check(const uint8_t *data, size_t length);
std::string infer_filename(const IcfFile &file);
}
+158
View File
@@ -0,0 +1,158 @@
#define LOG_MODULE "MAIN"
#include "config.h"
#include "driver_service.h"
#include "icf_mount.h"
#include "utils/strings.h"
#include "utils/files.h"
#include "utils/log.h"
#include <fstream>
#include <string>
// 去除字符串首尾空白
static std::string trim(const std::string &s) {
auto start = s.find_first_not_of(" \t\r\n");
if (start == std::string::npos) return "";
auto end = s.find_last_not_of(" \t\r\n");
return s.substr(start, end - start + 1);
}
// 解析config.ini,将键值对填入Config
static bool parse_config(const std::wstring &path, Config &cfg, std::string *error) {
std::ifstream file(path);
if (!file.is_open()) {
if (error) *error = "Cannot open config file: " + utils::wstr_to_str(path);
return false;
}
std::string line;
while (std::getline(file, line)) {
line = trim(line);
if (line.empty() || line[0] == ';' || line[0] == '#') continue;
if (line[0] == '[') continue; // 跳过section头
size_t eq = line.find('=');
if (eq == std::string::npos) continue;
std::string key = trim(line.substr(0, eq));
std::string val = trim(line.substr(eq + 1));
if (key == "ICF_PATH") {
cfg.icf_path = utils::to_wstring(val);
} else if (key == "IMAGE_DIR") {
cfg.image_dir = utils::to_wstring(val);
} else if (key == "OVERLAY_DIR") {
cfg.overlay_dir = utils::to_wstring(val);
} else if (key == "MOUNT_LETTER") {
if (!val.empty()) {
char ch = val[0];
if (ch >= 'a' && ch <= 'z') ch -= 'a' - 'A';
cfg.mount_letter = static_cast<wchar_t>(ch);
}
} else if (key == "APP_LINK") {
cfg.app_link = utils::to_wstring(val);
} else if (key == "OPT_LINK") {
cfg.opt_link = utils::to_wstring(val);
} else if (key == "APP_KEY") {
cfg.app_key = val;
} else if (key == "APP_IV") {
cfg.app_iv = val;
} else if (key == "OPT_KEY") {
cfg.opt_key = val;
} else if (key == "OPT_IV") {
cfg.opt_iv = val;
}
}
return true;
}
static void print_usage() {
LOG_INFO(
"Usage:\n"
" sega_mount -m [config.ini] 挂载\n"
" sega_mount -u [config.ini] 卸载\n"
"\n"
" config.ini默认读取exe所在目录,也可在命令行指定路径\n"
" Warn: UNMOUNT maybe unstable"
);
}
int main(int argc, char *argv[]) {
// 设置控制台为UTF-8代码页,配合/utf-8编译选项避免中文乱码
SetConsoleOutputCP(CP_UTF8);
SetConsoleCP(CP_UTF8);
if (argc < 2) {
print_usage();
return 1;
}
// 解析命令:-m 挂载,-u 卸载
std::string cmd = argv[1];
bool is_mount = cmd == "-m";
bool is_unmount = cmd == "-u";
if (!is_mount && !is_unmount) {
LOG_ERROR("Unknown command: {}", cmd);
print_usage();
return 1;
}
// 确定config.ini路径:优先命令行参数,否则使用exe所在目录
std::wstring config_path;
if (argc >= 3) {
config_path = utils::to_wstring(argv[2]);
} else {
config_path = std::format(L"{}\\config.ini", utils::get_exe_dir());
}
// 解析config.ini
Config cfg;
std::string cfg_err;
if (!parse_config(config_path, cfg, &cfg_err)) {
LOG_ERROR("{}", cfg_err);
return 1;
}
if (cfg.icf_path.empty()) {
LOG_ERROR("ICF_PATH is empty in config");
return 1;
}
LOG_INFO("Config loaded from {}", utils::wstr_to_str(config_path));
LOG_INFO(" ICF_PATH: {}", utils::wstr_to_str(cfg.icf_path));
LOG_INFO(" IMAGE_DIR: {}", utils::wstr_to_str(cfg.image_dir));
LOG_INFO(" OVERLAY_DIR: {}", utils::wstr_to_str(cfg.overlay_dir));
LOG_INFO(" MOUNT_LETTER: {}", static_cast<char>(cfg.mount_letter));
LOG_INFO(" APP_LINK: {}", utils::wstr_to_str(cfg.app_link));
LOG_INFO(" OPT_LINK: {}", utils::wstr_to_str(cfg.opt_link));
LOG_INFO(" APP_KEY: {}", cfg.app_key.empty() ? "(use built-in table)" : "(override)");
LOG_INFO(" OPT_KEY: {}", cfg.opt_key.empty() ? "(use built-in table)" : "(override)");
// 确保sgfscrypt服务已安装并运行
std::string svc_err;
if (!driver::ensure_running(&svc_err)) {
LOG_ERROR("Failed to start driver service: {}", svc_err);
LOG_ERROR("Hint: run this program as Administrator");
return 1;
}
if (is_mount) {
auto result = icf_mount::mount_icf_apps(cfg);
if (!result.success) {
LOG_ERROR("Mount failed: {}", result.error);
return 1;
}
LOG_INFO("Mount Success: {} APPs, {} OPTs mounted",
result.apps.size(), result.opts.size());
return 0;
}
// is_unmount
auto result = icf_mount::unmount_icf_apps(cfg);
if (!result.success) {
LOG_ERROR("Unmount failed: {}", result.error);
return 1;
}
LOG_INFO("Unmount Success: {} APPs, {} OPTs unmounted",
result.apps.size(), result.opts.size());
return 0;
}
+80
View File
@@ -0,0 +1,80 @@
#include "aes.h"
#include "bcrypt_util.h"
#include "strings.h"
#include <windows.h>
#include <bcrypt.h>
#include <vector>
#include <string>
#include <format>
namespace aes {
// AES-128-CBC加密
std::vector<uint8_t> cbc_encrypt(const uint8_t *key, const uint8_t *iv,
const uint8_t *input, size_t length,
std::string *error) {
std::vector<uint8_t> output;
BCRYPT_ALG_HANDLE hAlg = nullptr;
if (!bcrypt_util::open(hAlg, BCRYPT_CHAIN_MODE_CBC, error)) return output;
BCRYPT_KEY_HANDLE hKey = nullptr;
std::vector<uint8_t> key_obj;
if (!bcrypt_util::generate_symmetric_key(hAlg, hKey, key_obj,
key, KEY_SIZE_128, error)) {
BCryptCloseAlgorithmProvider(hAlg, 0);
return output;
}
output.resize(length);
std::vector iv_copy(iv, iv + BLOCK_SIZE);
ULONG cb_output = 0;
NTSTATUS st = BCryptEncrypt(hKey, bcrypt_util::to_puchar(input),
static_cast<ULONG>(length), nullptr,
iv_copy.data(), BLOCK_SIZE,
output.data(), static_cast<ULONG>(length),
&cb_output, 0);
BCryptDestroyKey(hKey);
BCryptCloseAlgorithmProvider(hAlg, 0);
if (!BCRYPT_SUCCESS(st)) {
if (error) *error = std::format("BCryptEncrypt failed: {}", utils::ntstatus_str(st));
output.clear();
return output;
}
output.resize(cb_output);
return output;
}
// AES-128-CBC解密
std::vector<uint8_t> cbc_decrypt(const uint8_t *key, const uint8_t *iv,
const uint8_t *input, size_t length,
std::string *error) {
std::vector<uint8_t> output;
BCRYPT_ALG_HANDLE hAlg = nullptr;
if (!bcrypt_util::open(hAlg, BCRYPT_CHAIN_MODE_CBC, error)) return output;
BCRYPT_KEY_HANDLE hKey = nullptr;
std::vector<uint8_t> key_obj;
if (!bcrypt_util::generate_symmetric_key(hAlg, hKey, key_obj,
key, KEY_SIZE_128, error)) {
BCryptCloseAlgorithmProvider(hAlg, 0);
return output;
}
output.resize(length);
std::vector iv_copy(iv, iv + BLOCK_SIZE);
ULONG cb_output = 0;
NTSTATUS st = BCryptDecrypt(hKey, bcrypt_util::to_puchar(input),
static_cast<ULONG>(length), nullptr,
iv_copy.data(), BLOCK_SIZE,
output.data(), static_cast<ULONG>(length),
&cb_output, 0);
BCryptDestroyKey(hKey);
BCryptCloseAlgorithmProvider(hAlg, 0);
if (!BCRYPT_SUCCESS(st)) {
if (error) *error = std::format("BCryptDecrypt failed: {}", utils::ntstatus_str(st));
output.clear();
return output;
}
output.resize(cb_output);
return output;
}
}
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include <cstdint>
#include <vector>
#include <string>
namespace aes {
constexpr size_t BLOCK_SIZE = 16;
constexpr size_t KEY_SIZE_128 = 16;
std::vector<uint8_t> cbc_encrypt(const uint8_t *key, const uint8_t *iv,
const uint8_t *input, size_t length,
std::string *error = nullptr);
std::vector<uint8_t> cbc_decrypt(const uint8_t *key, const uint8_t *iv,
const uint8_t *input, size_t length,
std::string *error = nullptr);
}
+54
View File
@@ -0,0 +1,54 @@
#include "bcrypt_util.h"
#include "strings.h"
#include <vector>
#include <format>
namespace bcrypt_util {
// 打开BCrypt算法提供者并设置链模式
bool open(BCRYPT_ALG_HANDLE &hAlg, const wchar_t *chaining, std::string *err) {
NTSTATUS st = BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_AES_ALGORITHM, nullptr, 0);
if (!BCRYPT_SUCCESS(st)) {
if (err) *err = std::format("BCryptOpenAlgorithmProvider failed: {}", utils::ntstatus_str(st));
return false;
}
st = BCryptSetProperty(hAlg, BCRYPT_CHAINING_MODE, to_puchar(chaining),
sizeof(wchar_t) * (wcslen(chaining) + 1), 0);
if (!BCRYPT_SUCCESS(st)) {
if (err) *err = std::format("BCryptSetProperty failed: {}", utils::ntstatus_str(st));
BCryptCloseAlgorithmProvider(hAlg, 0);
return false;
}
return true;
}
// 获取对称密钥对象所需缓冲区大小
bool get_object_length(BCRYPT_ALG_HANDLE hAlg, ULONG &cb_obj, std::string *err) {
ULONG cb_result = 0;
NTSTATUS st = BCryptGetProperty(hAlg, BCRYPT_OBJECT_LENGTH,
reinterpret_cast<PUCHAR>(&cb_obj),
sizeof(cb_obj), &cb_result, 0);
if (!BCRYPT_SUCCESS(st)) {
if (err) *err = std::format("BCryptGetProperty(OBJECT_LENGTH) failed: {}", utils::ntstatus_str(st));
return false;
}
return true;
}
// 生成对称密钥,key_obj由调用方持有
bool generate_symmetric_key(BCRYPT_ALG_HANDLE hAlg,
BCRYPT_KEY_HANDLE &hKey,
std::vector<uint8_t> &key_obj,
const uint8_t *key, ULONG key_len,
std::string *err) {
ULONG cb_obj = 0;
if (!get_object_length(hAlg, cb_obj, err)) return false;
key_obj.resize(cb_obj);
NTSTATUS st = BCryptGenerateSymmetricKey(hAlg, &hKey, key_obj.data(), cb_obj,
to_puchar(key), key_len, 0);
if (!BCRYPT_SUCCESS(st)) {
if (err) *err = std::format("BCryptGenerateSymmetricKey failed: {}", utils::ntstatus_str(st));
return false;
}
return true;
}
}
+27
View File
@@ -0,0 +1,27 @@
#pragma once
#include <windows.h>
#include <bcrypt.h>
#include <string>
#include <vector>
namespace bcrypt_util {
// 将const T*转换为PUCHAR,供BCrypt API使用(BCrypt参数声明为非const但实际不修改输入)
template<typename T>
PUCHAR to_puchar(const T *p) {
return reinterpret_cast<PUCHAR>(const_cast<T *>(p));
}
// 打开BCrypt算法提供者并设置链模式
bool open(BCRYPT_ALG_HANDLE &hAlg, const wchar_t *chaining, std::string *err = nullptr);
// 获取对称密钥对象所需缓冲区大小
bool get_object_length(BCRYPT_ALG_HANDLE hAlg, ULONG &cb_obj, std::string *err = nullptr);
// 生成对称密钥,key_obj由调用方持有
bool generate_symmetric_key(BCRYPT_ALG_HANDLE hAlg,
BCRYPT_KEY_HANDLE &hKey,
std::vector<uint8_t> &key_obj,
const uint8_t *key, ULONG key_len,
std::string *err = nullptr);
}
+26
View File
@@ -0,0 +1,26 @@
#include "crc32.h"
static uint32_t crc32_table[256];
static bool table_initialized = false;
static void init_crc32_table() {
if (table_initialized) return;
for (int i = 0; i < 256; i++) {
uint32_t c = i;
for (int z = 8; z; z--) {
uint32_t mask = 0xEDB88320;
c = c & 1 ? c >> 1 ^ mask : c >> 1;
}
crc32_table[i] = c;
}
table_initialized = true;
}
uint32_t crc32(const uint8_t *data, size_t length, uint32_t crc) {
init_crc32_table();
crc = ~crc;
for (size_t i = 0; i < length; i++) {
crc = crc32_table[(crc ^ data[i]) & 0xff] ^ crc >> 8;
}
return ~crc;
}
+5
View File
@@ -0,0 +1,5 @@
#pragma once
#include <cstdint>
uint32_t crc32(const uint8_t *data, size_t length, uint32_t crc = 0);
+16
View File
@@ -0,0 +1,16 @@
#pragma once
#include <cstdint>
namespace crypto {
// BootId / ICF 文件共用的 AES-128 密钥和 IV
constexpr uint8_t BOOT_KEY[16] = {
0x09, 0xca, 0x5e, 0xfd, 0x30, 0xc9, 0xaa, 0xef,
0x38, 0x04, 0xd0, 0xa7, 0xe3, 0xfa, 0x71, 0x20
};
constexpr uint8_t BOOT_IV[16] = {
0xb1, 0x55, 0xc2, 0x2c, 0x2e, 0x7f, 0x04, 0x91,
0xfa, 0x7f, 0x0f, 0xdc, 0x21, 0x7a, 0xff, 0x90
};
}
+86
View File
@@ -0,0 +1,86 @@
#include "utils/files.h"
#include <fstream>
#include <filesystem>
#include <system_error>
#include <format>
namespace utils {
// 读取文件全部内容
std::vector<uint8_t> read_file(const std::wstring &path) {
std::vector<uint8_t> data;
std::ifstream file(path, std::ios::binary | std::ios::ate);
if (!file.is_open()) return data;
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
data.resize(static_cast<size_t>(size));
if (!file.read(reinterpret_cast<char *>(data.data()), size)) {
data.clear();
}
return data;
}
// 获取文件所在目录路径
std::wstring get_directory(const std::wstring &path) {
size_t pos = path.find_last_of(L"\\/");
if (pos == std::wstring::npos) return L".";
return path.substr(0, pos);
}
// 获取当前exe所在目录
std::wstring get_exe_dir() {
wchar_t buf[32768];
DWORD len = GetModuleFileNameW(nullptr, buf, _countof(buf));
if (len == 0 || len == _countof(buf)) return L".";
return get_directory(std::wstring(buf, len));
}
// 递归拷贝目录
bool copy_dir_overwrite(const std::wstring &src_dir,
const std::wstring &dst_dir,
std::string *error,
size_t *out_files_copied) {
namespace fs = std::filesystem;
std::error_code ec;
size_t count = 0;
if (!fs::exists(src_dir, ec)) {
if (out_files_copied) *out_files_copied = count;
return true;
}
for (auto it = fs::recursive_directory_iterator(
src_dir, fs::directory_options::skip_permission_denied, ec);
it != fs::recursive_directory_iterator(); it.increment(ec)) {
if (ec) {
if (error) *error = std::format("iter error: {}", ec.value());
return false;
}
auto &entry = *it;
fs::path rel = entry.path().lexically_relative(src_dir);
fs::path dst = fs::path(dst_dir) / rel;
if (entry.is_directory()) {
fs::create_directories(dst, ec);
if (ec) {
if (error)
*error = std::format("mkdir: {} {}", ec.value(), dst.string());
return false;
}
continue;
}
if (entry.is_regular_file()) {
fs::create_directories(dst.parent_path(), ec);
fs::copy_file(entry.path(), dst,
fs::copy_options::overwrite_existing, ec);
if (ec) {
if (error)
*error = std::format("copy_file: {} {}", ec.value(), dst.string());
return false;
}
count++;
}
}
if (out_files_copied) *out_files_copied = count;
return true;
}
}
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include <string>
#include <vector>
#include <cstdint>
#include <windows.h>
namespace utils {
// 读取文件全部内容到字节数组,失败返回空vector
std::vector<uint8_t> read_file(const std::wstring &path);
// 获取文件所在目录路径(末尾不带反斜杠),无分隔符时返回 "."
std::wstring get_directory(const std::wstring &path);
// 获取当前exe所在目录(末尾不带反斜杠)
std::wstring get_exe_dir();
// 递归拷贝 src_dir -> dst_dir,文件已存在时强制覆盖
// 返回:成功返回true,out_files_copied 输出已拷贝文件数
bool copy_dir_overwrite(const std::wstring &src_dir,
const std::wstring &dst_dir,
std::string *error,
size_t *out_files_copied);
}
+97
View File
@@ -0,0 +1,97 @@
#pragma once
#include <cstdint>
// 此文件由 gen_keys.py 从 res/keys.json 自动生成,请勿手动编辑
// 源文件: res/keys.json (76 条记录)
namespace game_keys {
// 单个游戏的密钥条目(KEY/IV为16字节原始数组,无需运行时hex转换)
struct Entry {
const char *game_id;
const uint8_t app_key[16];
const uint8_t app_iv[16];
const uint8_t opt_key[16];
const uint8_t opt_iv[16];
};
// 所有游戏的密钥表(编译期常量,无需运行时解析)
inline constexpr Entry ENTRIES[] = {
{"SBZS", {0x2E, 0xCB, 0xCF, 0xF6, 0x5C, 0xE0, 0xAB, 0xEC, 0xC1, 0x05, 0x47, 0xF8, 0xAC, 0x83, 0x51, 0xD8}, {0xF2, 0xAC, 0x6C, 0x28, 0x17, 0xD0, 0x57, 0x4B, 0xBA, 0x11, 0x3D, 0x49, 0x7E, 0x31, 0x9F, 0x3E}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SBZT", {0x9A, 0xB9, 0xCE, 0x55, 0xED, 0x9C, 0x19, 0x4A, 0x71, 0x5A, 0x73, 0xA7, 0x69, 0x9F, 0x79, 0x5B}, {0x85, 0x52, 0xDE, 0x88, 0xFE, 0xDD, 0xA6, 0xE8, 0x59, 0x36, 0x9F, 0xB0, 0x00, 0xF4, 0x4D, 0x5B}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SBZU", {0xEB, 0x12, 0x28, 0x25, 0x4C, 0xDD, 0x30, 0x77, 0xEB, 0x3E, 0x44, 0x1C, 0x02, 0x27, 0xBF, 0x40}, {0x3F, 0x9B, 0x46, 0x76, 0x11, 0x8C, 0xEE, 0x12, 0x9F, 0xE2, 0xF1, 0xCB, 0x27, 0x47, 0xBC, 0xA5}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SBZV", {0x32, 0x74, 0xA3, 0x99, 0x59, 0x4D, 0x84, 0x77, 0x96, 0x25, 0x94, 0x0B, 0x69, 0xC0, 0x2D, 0x3F}, {0x67, 0x5B, 0xA6, 0x6D, 0x29, 0xC8, 0x79, 0x23, 0xF5, 0xF1, 0x54, 0xC4, 0x06, 0xAF, 0xEE, 0x42}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDAP", {0x41, 0xB5, 0x02, 0x7C, 0x5E, 0x99, 0xD9, 0x4A, 0xA9, 0x33, 0x5D, 0x6D, 0x71, 0x83, 0x8E, 0xCF}, {0xD1, 0x95, 0xD7, 0xAD, 0x28, 0x14, 0x20, 0x71, 0x15, 0xDB, 0x35, 0x31, 0x20, 0x0A, 0xE3, 0xBA}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDAQ", {0xC2, 0x8F, 0x22, 0xBC, 0x1B, 0x33, 0x9A, 0xE6, 0x41, 0x80, 0x73, 0x98, 0x86, 0xDC, 0x83, 0xD6}, {0x0A, 0x29, 0xFD, 0x14, 0x5D, 0x72, 0xBF, 0x8D, 0xED, 0xD4, 0x36, 0x02, 0x5D, 0xF0, 0xA9, 0xFC}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDAV", {0xEE, 0xD9, 0x55, 0x13, 0x26, 0x6A, 0x49, 0x9A, 0x55, 0xE2, 0x65, 0xB0, 0x49, 0x16, 0x9C, 0x44}, {0x84, 0xC0, 0xE5, 0x93, 0x1D, 0x91, 0xA6, 0xA4, 0x77, 0xD6, 0x2C, 0x27, 0x15, 0x46, 0x05, 0x6E}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDBE", {0x70, 0x53, 0xFB, 0x94, 0x45, 0x72, 0xE5, 0xB6, 0x31, 0xA6, 0x65, 0xCE, 0xF4, 0xB5, 0xBC, 0xDD}, {0xAE, 0x4D, 0x7E, 0x88, 0x40, 0x02, 0xC7, 0x9E, 0xB3, 0x57, 0x11, 0x55, 0x4D, 0x61, 0x30, 0x57}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDBN", {0xC1, 0xF1, 0x4A, 0xE2, 0xE8, 0x5B, 0x09, 0x5E, 0x31, 0x3C, 0x8B, 0xAE, 0xC1, 0x25, 0x80, 0x5E}, {0x3C, 0x53, 0x8E, 0xEA, 0x66, 0x25, 0x1A, 0xCD, 0x54, 0x04, 0xB9, 0x3F, 0x89, 0x76, 0xA7, 0xF7}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDBT", {0xA6, 0xA8, 0x70, 0x67, 0x1F, 0xD4, 0x32, 0xEC, 0x63, 0x7A, 0xDF, 0x7A, 0x82, 0x2F, 0x97, 0xDA}, {0x2C, 0x27, 0x7F, 0x31, 0xCD, 0x55, 0x0C, 0xFA, 0x2C, 0x99, 0x3B, 0x4D, 0xD5, 0x6B, 0x85, 0xAE}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDBX", {0x3D, 0xC1, 0x9C, 0x2D, 0x0C, 0x20, 0xAC, 0x19, 0x9D, 0x5F, 0xA4, 0x6E, 0x7F, 0x63, 0x35, 0xA6}, {0xD8, 0xF0, 0x29, 0xEC, 0x90, 0xFE, 0x55, 0xBE, 0x67, 0x58, 0x4F, 0x74, 0x2C, 0x55, 0xEF, 0x8B}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDBZ", {0x52, 0x1B, 0xDE, 0x44, 0x60, 0xF4, 0x18, 0x4E, 0xDD, 0x87, 0x91, 0x36, 0xAD, 0xEE, 0xA5, 0xEE}, {0x1B, 0x83, 0x24, 0x03, 0x2D, 0xB6, 0x9D, 0x7B, 0x09, 0x54, 0x79, 0x4A, 0xA2, 0x29, 0xFE, 0x68}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDCA", {0x16, 0x49, 0x49, 0x0A, 0x03, 0xD6, 0xC2, 0xAE, 0xC1, 0xC4, 0x96, 0x98, 0x2C, 0xB0, 0x40, 0x5C}, {0x46, 0x80, 0x71, 0x1C, 0x7E, 0x67, 0xA2, 0x6F, 0x92, 0x30, 0xD5, 0xAF, 0x74, 0xB5, 0xDC, 0xFB}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDCD", {0x43, 0xB3, 0x85, 0x02, 0xD8, 0xF6, 0xD3, 0xC7, 0xB0, 0x2B, 0x95, 0xFC, 0x28, 0xDB, 0x53, 0x08}, {0x6D, 0xFC, 0xB9, 0x4B, 0xF7, 0x4F, 0x15, 0x2B, 0x55, 0xF3, 0xE0, 0xC7, 0xF3, 0x5B, 0x44, 0xB5}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDCF", {0xDF, 0x98, 0x68, 0x83, 0xDA, 0x83, 0x75, 0x38, 0xE3, 0x7B, 0x95, 0x9A, 0x3E, 0x41, 0x17, 0xCD}, {0xDA, 0xBF, 0x53, 0x97, 0x38, 0x85, 0x2F, 0x17, 0x71, 0x48, 0x11, 0xAF, 0x70, 0x43, 0x5A, 0x83}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDCH", {0xE2, 0xDA, 0x76, 0x9E, 0x94, 0xF1, 0xD3, 0xAC, 0xA1, 0x93, 0x0C, 0xDB, 0xE0, 0x70, 0x8C, 0x9F}, {0xC7, 0xDC, 0xCE, 0x20, 0x3C, 0x84, 0xAB, 0x04, 0x77, 0x23, 0x6D, 0x69, 0x75, 0x70, 0xDA, 0xDC}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDCR", {0x49, 0x61, 0xA5, 0x1F, 0xD3, 0x6F, 0x14, 0xE7, 0x26, 0x64, 0xF5, 0x23, 0x73, 0x05, 0x21, 0x60}, {0x25, 0xD7, 0xD1, 0x34, 0x1A, 0x28, 0x2C, 0x5E, 0x0A, 0x34, 0xC6, 0x45, 0x62, 0xC0, 0x23, 0xEC}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDCT", {0xD6, 0xAE, 0x51, 0xF1, 0x0E, 0xC7, 0x6D, 0xA9, 0x3C, 0x98, 0x18, 0x00, 0xFC, 0x3A, 0xD3, 0xCB}, {0xFB, 0x8E, 0x43, 0xE2, 0x80, 0xD3, 0x30, 0xD0, 0x65, 0x81, 0x73, 0x2F, 0x2E, 0x11, 0xA6, 0xDC}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDCX", {0x79, 0x50, 0x4C, 0xCC, 0x50, 0x9B, 0x67, 0xD1, 0xF7, 0xA3, 0xF5, 0x93, 0xE6, 0xF9, 0xD9, 0xD6}, {0x15, 0x51, 0xEA, 0x89, 0x26, 0xF2, 0xAE, 0xE2, 0x33, 0xEE, 0xC3, 0x09, 0xDE, 0x3E, 0x5F, 0x3C}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDDB", {0x87, 0x56, 0x79, 0xB2, 0xCD, 0x16, 0x37, 0x96, 0x2B, 0x0D, 0xB2, 0x5C, 0x51, 0xFB, 0x21, 0xA6}, {0x8E, 0xF4, 0x47, 0x22, 0xA0, 0x56, 0x6E, 0x8F, 0x57, 0x23, 0x56, 0x24, 0x56, 0x87, 0xFB, 0xE5}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDDD", {0x56, 0x4E, 0x96, 0x78, 0x73, 0xDE, 0x6C, 0xBC, 0xD2, 0x2E, 0xFE, 0xCA, 0x69, 0x52, 0xE9, 0xDC}, {0x4E, 0x3D, 0xD4, 0x65, 0xCF, 0x09, 0xCD, 0x82, 0xB2, 0x59, 0xF7, 0xBE, 0xD5, 0xFC, 0x2D, 0x6D}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDDD", {0x75, 0x71, 0x3E, 0x3A, 0x63, 0x40, 0x41, 0x65, 0x98, 0xCF, 0xA9, 0xE3, 0x8F, 0xBA, 0x68, 0xE4}, {0x59, 0xA1, 0x4E, 0x3E, 0x13, 0xA6, 0xF8, 0xB8, 0x67, 0x56, 0x1E, 0xF7, 0x34, 0xE1, 0xA5, 0xFF}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDDF", {0x65, 0x05, 0x85, 0x73, 0xA0, 0xCB, 0x81, 0x74, 0x9E, 0x69, 0x4A, 0xE1, 0x64, 0xC6, 0x1B, 0x04}, {0x98, 0x1C, 0x4F, 0x45, 0xE3, 0xC6, 0x95, 0x8F, 0x05, 0x4E, 0x5D, 0x00, 0x91, 0x6B, 0xDF, 0x2B}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDDJ", {0x63, 0x0F, 0xE5, 0x22, 0x76, 0x53, 0x7B, 0xD7, 0xFB, 0x26, 0x7A, 0xDF, 0x17, 0x5F, 0x4E, 0x99}, {0xDC, 0x57, 0x55, 0xBE, 0x57, 0xDE, 0xD2, 0xCD, 0xB3, 0x44, 0x33, 0xBB, 0xBA, 0x22, 0x04, 0xFF}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDDL", {0x99, 0x24, 0x58, 0x29, 0x5F, 0xD0, 0x6D, 0x6A, 0x8A, 0xF0, 0xDF, 0xB3, 0xF6, 0x85, 0x4C, 0x19}, {0x84, 0x84, 0x90, 0x6D, 0x4C, 0xD5, 0xFD, 0x22, 0x5E, 0x03, 0x28, 0x43, 0xED, 0x37, 0x49, 0x5D}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDDM", {0x01, 0x27, 0x95, 0x82, 0x10, 0xF6, 0xAE, 0x9B, 0xDE, 0xB8, 0x97, 0x50, 0x18, 0xB5, 0xAF, 0x24}, {0x18, 0x17, 0x16, 0xBA, 0xDC, 0xCF, 0xF4, 0xBC, 0x2B, 0x1E, 0x29, 0xAE, 0x02, 0xA1, 0xBB, 0xBB}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDDN", {0x41, 0xDD, 0x8E, 0x66, 0x29, 0x01, 0x17, 0xAC, 0x67, 0xD3, 0x11, 0xA2, 0xF0, 0xA6, 0x41, 0x6E}, {0x73, 0xE1, 0x8E, 0x84, 0x18, 0xF6, 0xCE, 0xEF, 0xB1, 0x1E, 0x27, 0x67, 0xFD, 0xEA, 0x19, 0x0C}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDDP", {0xCF, 0x6D, 0x64, 0x42, 0x7E, 0xEC, 0xA4, 0x76, 0x74, 0xE1, 0x7B, 0xCD, 0x46, 0xD1, 0xEA, 0x8C}, {0xCE, 0x51, 0x74, 0x09, 0x3D, 0x26, 0xCA, 0x2A, 0x31, 0xB5, 0x85, 0x41, 0xE8, 0x5A, 0xC2, 0x76}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDDS", {0x16, 0x1B, 0xEC, 0x6D, 0x90, 0x98, 0x9D, 0x0E, 0x26, 0xD7, 0x91, 0x17, 0x06, 0x07, 0xA4, 0x40}, {0x81, 0xDC, 0x26, 0xA2, 0x70, 0x28, 0xE2, 0x09, 0x23, 0x32, 0x03, 0x8A, 0xA1, 0xBF, 0xFC, 0x47}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDDT", {0x3F, 0x76, 0x58, 0x72, 0x8B, 0x95, 0x17, 0xD3, 0x31, 0x4E, 0x68, 0x4F, 0xA2, 0xE2, 0xA0, 0x45}, {0x41, 0x57, 0x88, 0x33, 0xC5, 0x47, 0xAA, 0xFF, 0x04, 0xDB, 0x59, 0x7A, 0x6E, 0x9E, 0xB7, 0x84}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDDT", {0x21, 0x69, 0x34, 0x36, 0x46, 0xA3, 0x24, 0x86, 0x19, 0x75, 0x5B, 0x68, 0xE6, 0xEF, 0x2C, 0x2C}, {0x69, 0x82, 0x40, 0x6E, 0x69, 0xC9, 0xEC, 0x25, 0x74, 0xED, 0xC5, 0x25, 0x3F, 0xC6, 0xA3, 0x24}, {0x81, 0x3A, 0x11, 0x58, 0xC5, 0x2B, 0x82, 0x08, 0x9D, 0x71, 0x0A, 0xEC, 0x3B, 0x5F, 0xCC, 0x91}, {0xA1, 0x2F, 0x44, 0xF9, 0xF1, 0xBE, 0x52, 0xE6, 0x99, 0x4A, 0xC6, 0xE0, 0x52, 0xC1, 0x59, 0x65}},
{"SDDU", {0x64, 0x9A, 0xE9, 0x98, 0x26, 0x25, 0xF9, 0x0C, 0x55, 0xAF, 0x86, 0x71, 0x3C, 0x55, 0xD3, 0xFD}, {0x18, 0x71, 0x16, 0xFC, 0x46, 0x47, 0xA7, 0xD3, 0xB6, 0xF2, 0x30, 0x3A, 0x34, 0xF0, 0xA2, 0xFE}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDDW", {0x11, 0x85, 0x65, 0xD3, 0x44, 0xF3, 0xE1, 0x4C, 0xA6, 0x92, 0x99, 0xEE, 0xAC, 0x04, 0x9B, 0xB9}, {0x9D, 0x6D, 0x39, 0x2E, 0xC3, 0x5E, 0xD9, 0x4E, 0xF9, 0xFE, 0x0A, 0x5B, 0xE0, 0x57, 0x39, 0x81}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDDX", {0x42, 0x8B, 0xFF, 0x0F, 0x9E, 0x7A, 0xAF, 0xC1, 0x69, 0xA7, 0xA7, 0x57, 0x51, 0xFF, 0xDA, 0x98}, {0xF8, 0x25, 0x05, 0x94, 0xF4, 0x25, 0x33, 0x2C, 0x6D, 0x34, 0x9D, 0x7E, 0xA0, 0xE8, 0x66, 0x69}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDEA", {0x9F, 0x9C, 0xF1, 0x48, 0xAC, 0x3C, 0x50, 0xAA, 0xF9, 0x25, 0xAF, 0x1D, 0xFB, 0x27, 0xF5, 0x8B}, {0x4D, 0x8E, 0xBB, 0xD9, 0x71, 0x89, 0x6B, 0x8A, 0x4A, 0x3D, 0xD8, 0x4A, 0x23, 0xB3, 0x29, 0xFC}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDEB", {0xD5, 0x11, 0xED, 0x69, 0x04, 0x15, 0xF6, 0x35, 0x98, 0x43, 0xA1, 0x34, 0xFD, 0x47, 0x83, 0x6A}, {0xAC, 0x13, 0x9B, 0x38, 0x2A, 0xCD, 0xD1, 0x12, 0xE3, 0x15, 0x64, 0xEA, 0x7F, 0x38, 0x18, 0x6C}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDEC", {0xF2, 0x72, 0xE5, 0x01, 0x68, 0x63, 0xAF, 0x2B, 0xA0, 0x33, 0x7F, 0x50, 0xDE, 0x68, 0x6F, 0x6E}, {0x53, 0x27, 0xE1, 0x32, 0x63, 0x1E, 0x7F, 0x71, 0xB6, 0x1B, 0xE7, 0xCC, 0x0D, 0xF3, 0x82, 0xCE}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDED", {0x21, 0xFC, 0xEC, 0x77, 0x9A, 0x16, 0x76, 0x9F, 0x52, 0x77, 0xA3, 0x6F, 0xB5, 0x42, 0x99, 0x2C}, {0x22, 0xB5, 0x02, 0x39, 0xF1, 0xB4, 0x0C, 0xCC, 0x3E, 0x55, 0xA2, 0xD6, 0x9C, 0x69, 0xB1, 0x60}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDED", {0xDE, 0x78, 0xB3, 0xF3, 0xD3, 0x35, 0x8F, 0x40, 0xD5, 0x52, 0xCB, 0xF5, 0x89, 0x21, 0xA4, 0x99}, {0x20, 0xF3, 0x33, 0xCC, 0x75, 0x74, 0x62, 0x84, 0x26, 0x56, 0xB4, 0x30, 0xC7, 0x82, 0x34, 0x34}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDEE", {0x19, 0x1E, 0xB7, 0x44, 0x06, 0x72, 0xDA, 0xB0, 0x8D, 0xDB, 0xB7, 0x19, 0x5E, 0xFB, 0x35, 0x6F}, {0xC2, 0x78, 0xB5, 0x38, 0x6D, 0xC3, 0x8B, 0xD7, 0x6D, 0x71, 0xDB, 0xCD, 0x82, 0x69, 0x54, 0xCF}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDEG", {0x72, 0x18, 0x53, 0xDB, 0xE2, 0xD3, 0x0B, 0xAF, 0xE2, 0x4F, 0x0E, 0xDB, 0xD2, 0x10, 0xDE, 0xEB}, {0x4D, 0xFB, 0x0B, 0xCE, 0xC8, 0x61, 0x59, 0xAA, 0xB2, 0x97, 0x16, 0x6B, 0xCD, 0x50, 0x9E, 0x6F}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDEJ", {0x9D, 0xE1, 0xEA, 0x6A, 0xE3, 0x8D, 0x90, 0x11, 0xF5, 0x5D, 0x8E, 0xE8, 0x64, 0x39, 0x5D, 0x24}, {0xF6, 0x0C, 0xDE, 0x21, 0x98, 0x28, 0x76, 0xD1, 0x2D, 0x17, 0x66, 0x2A, 0x48, 0xD9, 0x08, 0x36}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDEM", {0x70, 0x06, 0x17, 0xF2, 0x93, 0x69, 0x6C, 0x07, 0xFB, 0x9F, 0x35, 0x6D, 0x3B, 0x99, 0x24, 0x0D}, {0x66, 0x7D, 0x02, 0x6D, 0x6C, 0xDF, 0x32, 0x9F, 0xF3, 0x51, 0xDB, 0xAF, 0x70, 0x98, 0xE8, 0x1D}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDEP", {0xFA, 0x2B, 0x7C, 0xA5, 0x3A, 0x82, 0x3C, 0x15, 0x2D, 0x94, 0x09, 0x72, 0xCB, 0xF5, 0x32, 0xF5}, {0xF4, 0xAF, 0x35, 0x12, 0x0C, 0x48, 0x61, 0x77, 0x04, 0xBB, 0x5B, 0x84, 0x71, 0x79, 0x7A, 0x62}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDER", {0x7D, 0x73, 0x36, 0x7E, 0xBB, 0x21, 0x8E, 0xC8, 0x29, 0x30, 0xD5, 0x8D, 0xC6, 0xD7, 0x95, 0x0B}, {0x97, 0x88, 0xC3, 0xEC, 0xA2, 0xDB, 0x6B, 0xA9, 0x2B, 0xAC, 0x4F, 0x6F, 0x7B, 0x70, 0x63, 0x08}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDET", {0x46, 0x43, 0xE7, 0xB2, 0xC3, 0x00, 0x6E, 0x02, 0x64, 0x16, 0x3E, 0xDC, 0x85, 0x45, 0xFB, 0x72}, {0x61, 0x2B, 0xCA, 0x81, 0xEA, 0x29, 0x58, 0xFF, 0xBA, 0xC3, 0x6F, 0x78, 0x0F, 0x1E, 0xD6, 0x88}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDEU", {0x23, 0xB3, 0xE9, 0xBB, 0x47, 0xE3, 0xAC, 0x99, 0x98, 0xF6, 0xE6, 0xC1, 0xAD, 0xC4, 0xAE, 0x33}, {0xA9, 0x64, 0x71, 0x4C, 0xEA, 0x60, 0x68, 0x84, 0x07, 0xBF, 0x55, 0x4B, 0xD1, 0xC2, 0x7E, 0xC2}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDEV", {0x3C, 0x1F, 0x01, 0x8D, 0x88, 0x92, 0x6D, 0x98, 0x16, 0x3B, 0x07, 0xA1, 0x56, 0x3A, 0x48, 0x18}, {0xCA, 0x73, 0x73, 0xC9, 0xC7, 0xDF, 0xEB, 0xAC, 0x0F, 0xC2, 0x42, 0x54, 0xC0, 0x30, 0xE4, 0xAD}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDEZ", {0xD1, 0x36, 0xEB, 0xA0, 0x5D, 0x40, 0xE8, 0x26, 0x82, 0xE6, 0xAA, 0xD8, 0xD9, 0xE8, 0x68, 0x8C}, {0xC4, 0x84, 0xDE, 0xEA, 0xA0, 0x24, 0x9E, 0xF4, 0x66, 0x95, 0xF6, 0x36, 0x94, 0xB7, 0x37, 0x2F}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDFA", {0x8E, 0x81, 0x6B, 0x43, 0x62, 0xDB, 0x24, 0xA2, 0x30, 0x87, 0x78, 0x85, 0x86, 0x4D, 0x20, 0x6D}, {0x8E, 0x5A, 0x0B, 0xA6, 0xA0, 0xA1, 0x15, 0x0D, 0x47, 0xD1, 0x2B, 0xDB, 0x64, 0xDE, 0xBB, 0xA7}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDFE", {0xF6, 0x17, 0x19, 0xC3, 0x71, 0xE5, 0xBC, 0xA6, 0x78, 0x8C, 0x13, 0x9A, 0x53, 0x09, 0x16, 0x17}, {0x67, 0xD4, 0x31, 0x73, 0xE3, 0x43, 0x81, 0x3F, 0xA2, 0x09, 0x7F, 0xD3, 0x29, 0x92, 0xA8, 0xE2}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDFE", {0x24, 0xF9, 0x63, 0xD0, 0xA7, 0xCF, 0x3D, 0x65, 0xF3, 0x67, 0x09, 0x77, 0xFA, 0xA5, 0xDA, 0xBA}, {0x37, 0xA1, 0x75, 0x83, 0x8F, 0x1E, 0x5C, 0x6D, 0x10, 0x5F, 0x4F, 0xB0, 0x59, 0x9F, 0x60, 0x7B}, {0x81, 0x3A, 0x11, 0x58, 0xC5, 0x2B, 0x82, 0x08, 0x9D, 0x71, 0x0A, 0xEC, 0x3B, 0x5F, 0xCC, 0x91}, {0xA1, 0x2F, 0x44, 0xF9, 0xF1, 0xBE, 0x52, 0xE6, 0x99, 0x4A, 0xC6, 0xE0, 0x52, 0xC1, 0x59, 0x65}},
{"SDFG", {0x33, 0x98, 0xFB, 0x86, 0xBF, 0xE6, 0x30, 0xA1, 0x49, 0x79, 0x41, 0x18, 0x79, 0x86, 0x1A, 0xC7}, {0xA7, 0x94, 0xC4, 0x9C, 0x2C, 0x76, 0x39, 0xCD, 0x80, 0x57, 0x18, 0x07, 0xC1, 0x72, 0x46, 0xFF}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDFL", {0x24, 0x49, 0xB4, 0x80, 0x67, 0xB9, 0x17, 0x6A, 0x6E, 0x0F, 0x95, 0x63, 0x48, 0x1E, 0x97, 0xF4}, {0x61, 0x6F, 0x87, 0x10, 0x45, 0x46, 0x32, 0xEB, 0x4F, 0xB1, 0xD8, 0x9D, 0x8C, 0x19, 0xC1, 0x9A}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDFN", {0x29, 0xF6, 0x2E, 0x22, 0xC6, 0xA9, 0xFD, 0x8B, 0xE3, 0x27, 0x63, 0x1C, 0x68, 0x54, 0x64, 0x05}, {0x2A, 0x86, 0x09, 0x76, 0xE6, 0xD9, 0x85, 0x13, 0x82, 0x5F, 0x29, 0x1E, 0x56, 0xCF, 0xB5, 0xEE}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDFP", {0x57, 0x0B, 0x87, 0x26, 0x3A, 0x7C, 0xA0, 0xAA, 0x4C, 0x13, 0x88, 0xE2, 0x04, 0xEE, 0x6D, 0x4B}, {0x76, 0x40, 0x88, 0x60, 0x11, 0xA2, 0x30, 0x0A, 0x91, 0xFA, 0xD9, 0xF3, 0x6A, 0x8C, 0x47, 0x75}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDFT", {0x92, 0xA2, 0x5F, 0x38, 0x8C, 0x50, 0x73, 0x7E, 0x39, 0xC3, 0xC2, 0xF0, 0x06, 0x64, 0x5F, 0x31}, {0xA9, 0x7E, 0x72, 0xF9, 0x90, 0x41, 0x74, 0x88, 0xCB, 0x4C, 0x67, 0xF8, 0xF0, 0xC3, 0xFB, 0x25}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDFV", {0xFE, 0x82, 0xDB, 0x9A, 0x60, 0x29, 0x5D, 0x82, 0x9B, 0x95, 0xF0, 0x3C, 0x22, 0x76, 0x01, 0x8B}, {0x34, 0xD8, 0x27, 0x72, 0xAE, 0x18, 0x17, 0x4F, 0x0A, 0x18, 0x1D, 0xC5, 0x33, 0x99, 0xEA, 0x9C}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDGA", {0x0A, 0x66, 0x10, 0xA6, 0x2E, 0xF6, 0x70, 0xC6, 0x5B, 0x7E, 0x7B, 0x17, 0x50, 0xFF, 0xB7, 0xA1}, {0x17, 0xA2, 0xA2, 0x29, 0x15, 0xF8, 0x1C, 0x58, 0x96, 0xED, 0xBB, 0xA4, 0xC4, 0x12, 0x58, 0x5E}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDGB", {0x7C, 0xA4, 0xE6, 0xB6, 0xF3, 0xD6, 0xE8, 0xB2, 0x64, 0x72, 0x97, 0x38, 0x87, 0xD7, 0xFA, 0x3A}, {0x53, 0xFE, 0x71, 0x35, 0x76, 0x2D, 0xE3, 0xF9, 0x7E, 0x7F, 0xE7, 0x6B, 0x0F, 0xEF, 0x3F, 0x27}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDGH", {0xB3, 0xE3, 0x0E, 0x7E, 0xAB, 0xAC, 0x37, 0x67, 0xAD, 0xE1, 0x3C, 0x69, 0xC9, 0xB2, 0xF2, 0x2B}, {0x03, 0xDE, 0xAE, 0xA3, 0x74, 0x2D, 0x69, 0x67, 0x5B, 0x36, 0xCD, 0xDC, 0x8B, 0x15, 0xAC, 0x91}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDGK", {0x9D, 0xC4, 0xA1, 0x7F, 0xC3, 0x9F, 0xCA, 0x5A, 0x8A, 0x35, 0x89, 0x84, 0x80, 0x1C, 0xAA, 0xA7}, {0xE0, 0x44, 0x5B, 0x11, 0xDC, 0xFA, 0x0D, 0xAE, 0x56, 0xC8, 0x5E, 0x87, 0x87, 0xE1, 0x1D, 0x9B}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDGP", {0xC8, 0x7A, 0xB3, 0x12, 0x47, 0xE7, 0xB6, 0xFF, 0x95, 0xFD, 0xD7, 0x9F, 0xB9, 0x1F, 0x9F, 0x37}, {0x24, 0x67, 0xAB, 0x3C, 0x03, 0x1E, 0x3D, 0xC0, 0x56, 0x8B, 0x70, 0x77, 0xEF, 0xD2, 0x7C, 0x36}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDGQ", {0xC5, 0x35, 0x6D, 0xAE, 0x7B, 0x06, 0x6B, 0xCE, 0x88, 0x98, 0x4A, 0xEC, 0x36, 0xDE, 0xB6, 0x2D}, {0x4E, 0x9F, 0x29, 0x82, 0x46, 0x0E, 0x2F, 0xD9, 0x07, 0xBD, 0xE1, 0x57, 0x09, 0xED, 0xFB, 0xA7}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDGS", {0xA5, 0x15, 0x0C, 0xC5, 0x06, 0x5D, 0x2C, 0x59, 0xEE, 0x2F, 0x8F, 0x33, 0x2C, 0xBD, 0x29, 0xD5}, {0x84, 0x01, 0x4D, 0x26, 0x69, 0x6F, 0x29, 0x0A, 0xD7, 0xEA, 0xD7, 0x0C, 0x75, 0x49, 0xBD, 0x81}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDGT", {0x9D, 0x0B, 0xBA, 0x20, 0xD1, 0xE8, 0x4F, 0x24, 0x59, 0x39, 0x9F, 0x53, 0x83, 0xBE, 0xEE, 0x72}, {0x5D, 0x34, 0x00, 0x13, 0xFD, 0xFB, 0x24, 0x64, 0xD2, 0x53, 0x09, 0x36, 0x02, 0xFE, 0x4B, 0x64}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDGV", {0x57, 0x3F, 0x8C, 0xC4, 0x4F, 0x10, 0xF3, 0x1E, 0xC7, 0x49, 0xB6, 0x95, 0xEB, 0xE8, 0x86, 0xA8}, {0x6B, 0xFC, 0xA8, 0x6F, 0x9D, 0x20, 0x8A, 0x79, 0x44, 0xCD, 0xFC, 0x25, 0xEA, 0x3C, 0xD2, 0x20}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDGY", {0xC0, 0x4B, 0x66, 0x3A, 0x59, 0x05, 0x5A, 0xCB, 0xDF, 0xEB, 0xC6, 0xD3, 0xDF, 0x0E, 0x6A, 0x04}, {0x76, 0xFC, 0x5F, 0x1D, 0x88, 0x60, 0x51, 0x07, 0x94, 0x7D, 0x0C, 0x1F, 0xF3, 0x47, 0x02, 0x2D}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDGZ", {0x9A, 0xD7, 0x4E, 0xFB, 0x20, 0x8D, 0x6E, 0xE4, 0xFE, 0x5E, 0xE7, 0x70, 0x33, 0x17, 0x12, 0xCF}, {0x45, 0xF6, 0xE5, 0x3F, 0x0E, 0xB8, 0xFA, 0xE6, 0x66, 0x5B, 0x45, 0x44, 0x4A, 0x61, 0xE2, 0x66}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDHD", {0x3A, 0xBD, 0x00, 0xD7, 0xA8, 0x20, 0xCE, 0x86, 0x2E, 0xAF, 0x47, 0x4B, 0xF6, 0xC8, 0xF3, 0x3E}, {0x0F, 0x1E, 0x7E, 0xEA, 0x78, 0xDA, 0x7E, 0x03, 0x7E, 0x05, 0x52, 0xC2, 0x84, 0x3E, 0x1B, 0x6A}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDHD", {0x81, 0xB1, 0x58, 0xCB, 0xB8, 0x83, 0x70, 0xA1, 0x26, 0x4C, 0x99, 0x80, 0x0C, 0x5D, 0xDE, 0x4D}, {0x6F, 0xD5, 0x01, 0xED, 0x31, 0xEB, 0xE1, 0x18, 0x26, 0xDB, 0xAC, 0x39, 0x46, 0xE2, 0x4D, 0x57}, {0x81, 0x3A, 0x11, 0x58, 0xC5, 0x2B, 0x82, 0x08, 0x9D, 0x71, 0x0A, 0xEC, 0x3B, 0x5F, 0xCC, 0x91}, {0xA1, 0x2F, 0x44, 0xF9, 0xF1, 0xBE, 0x52, 0xE6, 0x99, 0x4A, 0xC6, 0xE0, 0x52, 0xC1, 0x59, 0x65}},
{"SDHH", {0xFC, 0x6F, 0x88, 0x7F, 0x37, 0x17, 0xC5, 0xD6, 0x71, 0x31, 0x13, 0xB9, 0x2F, 0xA3, 0xFB, 0x27}, {0xAD, 0x76, 0x60, 0x64, 0x60, 0xDB, 0xE1, 0xE9, 0x1E, 0x41, 0xBE, 0xF7, 0xAB, 0x0C, 0x15, 0x35}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDHJ", {0x98, 0x5E, 0xA6, 0x6E, 0xCB, 0x5B, 0x1F, 0x20, 0x8C, 0x90, 0xE2, 0xB8, 0x98, 0xF0, 0xB0, 0x73}, {0x16, 0x4A, 0x65, 0x42, 0x2E, 0x7F, 0x01, 0xB7, 0xF1, 0xB0, 0x84, 0x9F, 0xC7, 0x73, 0x7C, 0xDB}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDHK", {0xBC, 0x92, 0xD6, 0x3C, 0x2A, 0x09, 0x9C, 0xA2, 0x31, 0x5A, 0x48, 0x3C, 0x30, 0x41, 0xFD, 0xD7}, {0xB1, 0x4D, 0x84, 0x49, 0xB6, 0xD4, 0x32, 0x5D, 0x83, 0xA2, 0x77, 0x4B, 0x13, 0xDD, 0x21, 0xFF}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDHN", {0x89, 0x21, 0x23, 0xA2, 0x6D, 0x7C, 0x03, 0xD4, 0x9E, 0xDD, 0x12, 0xA8, 0x0E, 0xE0, 0xC5, 0x8F}, {0x76, 0xAA, 0x15, 0xA6, 0x86, 0x8B, 0x8D, 0xBD, 0xF7, 0x20, 0x79, 0x06, 0x35, 0x4D, 0x51, 0x69}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
{"SDHR", {0x1F, 0xB8, 0x97, 0xCA, 0xB9, 0x7C, 0x81, 0x70, 0xA6, 0xAC, 0x0A, 0x21, 0x68, 0x5C, 0x58, 0xD9}, {0xF9, 0xB6, 0x0F, 0x65, 0xB0, 0x1E, 0x8E, 0x83, 0x6A, 0x4B, 0xC2, 0x0F, 0x7D, 0x39, 0xFA, 0xF5}, {0x5C, 0x84, 0xA9, 0xE7, 0x26, 0xEA, 0xA5, 0xDD, 0x35, 0x1F, 0x2B, 0x07, 0x50, 0xC2, 0x36, 0x97}, {0xC0, 0x63, 0xBF, 0x6F, 0x56, 0x2D, 0x08, 0x4D, 0x79, 0x63, 0xC9, 0x87, 0xF5, 0x28, 0x17, 0x61}},
};
}
+62
View File
@@ -0,0 +1,62 @@
#include "log.h"
#include <windows.h>
#include <cstdio>
namespace logger {
// 全局最低输出等级
static auto g_min_level = Level::Debug;
// 等级对应的标签文字(固定5字符宽度对齐)
static const char *level_tag(Level level) {
switch (level) {
case Level::Debug: return "DEBUG";
case Level::Info: return "INFO ";
case Level::Warn: return "WARN ";
case Level::Error: return "ERROR";
default: return "?????";
}
}
// 等级对应的 Windows 控制台颜色属性
static WORD level_color(Level level) {
switch (level) {
case Level::Debug: return FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY; // 灰白
case Level::Info: return FOREGROUND_GREEN | FOREGROUND_INTENSITY; // 亮绿
case Level::Warn: return FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; // 亮黄
case Level::Error: return FOREGROUND_RED | FOREGROUND_INTENSITY; // 亮红
default: return FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
}
}
void set_min_level(Level level) {
g_min_level = level;
}
void output_raw(Level level, const char *module, const std::string &message) {
if (static_cast<int>(level) < static_cast<int>(g_min_level)) return;
HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
WORD orig_attr = 7; // 默认灰白
if (hStdout != INVALID_HANDLE_VALUE) {
CONSOLE_SCREEN_BUFFER_INFO csbi{};
if (GetConsoleScreenBufferInfo(hStdout, &csbi)) {
orig_attr = csbi.wAttributes;
}
SetConsoleTextAttribute(hStdout, level_color(level));
}
// 输出: [LEVEL][MODULE] message\n
std::fputs("[", stdout);
std::fputs(level_tag(level), stdout);
std::fputs("][", stdout);
std::fputs(module, stdout);
std::fputs("] ", stdout);
std::fputs(message.c_str(), stdout);
std::fputc('\n', stdout);
std::fflush(stdout);
if (hStdout != INVALID_HANDLE_VALUE) {
SetConsoleTextAttribute(hStdout, orig_attr);
}
}
}
+37
View File
@@ -0,0 +1,37 @@
#pragma once
#include <string>
#include <format>
namespace logger {
// 日志等级
enum class Level {
Debug,
Info,
Warn,
Error
};
// 设置全局最低输出等级(低于此等级的日志不输出),默认 Debug(全部输出)
void set_min_level(Level level);
// 原始字符串输出(已格式化完成),带模块名
void output_raw(Level level, const char *module, const std::string &message);
// 格式化输出(内部使用,外部建议用 LOG_XXX 宏)
template<typename... Args>
void output(Level level, const char *module, std::format_string<Args...> fmt_str, Args &&... args) {
output_raw(level, module, std::format(fmt_str, std::forward<Args>(args)...));
}
}
// 每个cpp文件在include本头文件前定义 LOG_MODULE 可自定义模块名
#ifndef LOG_MODULE
#define LOG_MODULE "????"
#endif
// 日志宏,自动附带 LOG_MODULE 作为模块名,格式: [LEVEL][MODULE] message
#define LOG_DEBUG(...) logger::output(logger::Level::Debug, LOG_MODULE, __VA_ARGS__)
#define LOG_INFO(...) logger::output(logger::Level::Info, LOG_MODULE, __VA_ARGS__)
#define LOG_WARN(...) logger::output(logger::Level::Warn, LOG_MODULE, __VA_ARGS__)
#define LOG_ERROR(...) logger::output(logger::Level::Error, LOG_MODULE, __VA_ARGS__)
+68
View File
@@ -0,0 +1,68 @@
#include "strings.h"
#include <cstdio>
#include <format>
namespace utils {
// 将NTSTATUS转换为0x%08X格式的字符串
std::string ntstatus_str(NTSTATUS status) {
return std::format("0x{:08X}", static_cast<unsigned>(status));
}
// 将十六进制字符串转换为字节数组
bool hex_to_bytes(const std::string &hex, uint8_t *out, size_t len) {
if (hex.size() < len * 2) return false;
for (size_t i = 0; i < len; i++) {
unsigned val = 0;
if (sscanf_s(hex.c_str() + i * 2, "%02x", &val) != 1) return false;
out[i] = static_cast<uint8_t>(val);
}
return true;
}
// 规范化路径:将/转换为\,不翻倍反斜杠
std::wstring normalize_path(const std::wstring &path) {
std::wstring result;
result.reserve(path.size() + 8);
for (wchar_t c: path) {
if (c == L'/') c = L'\\';
result += c;
}
return result;
}
// 将wstring转为string
std::string wstr_to_str(const std::wstring &ws) {
std::string s;
for (wchar_t c: ws) {
if (c < 0x80) s.push_back(static_cast<char>(c));
else {
s += std::format("\\x{:04X}", static_cast<unsigned>(c));
}
}
return s;
}
// 将UTF-8 char*字符串转为wstring
std::wstring to_wstring(const char *s) {
if (!s) return L"";
int len = MultiByteToWideChar(CP_UTF8, 0, s, -1, nullptr, 0);
if (len <= 0) return L"";
std::wstring ws(static_cast<size_t>(len - 1), 0);
MultiByteToWideChar(CP_UTF8, 0, s, -1, ws.data(), len);
return ws;
}
// 将string转为wstring(UTF-8)
std::wstring to_wstring(const std::string &s) {
return to_wstring(s.c_str());
}
// 将字节数组格式化为十六进制小写字符串
std::string bytes_to_hex(const uint8_t *data, size_t len) {
std::string s;
for (size_t i = 0; i < len; i++) {
s += std::format("{:02x}", data[i]);
}
return s;
}
}
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#include <string>
#include <cstdint>
#include <windows.h>
namespace utils {
// 将NTSTATUS转换为0x%08X格式的字符串
std::string ntstatus_str(NTSTATUS status);
// 将十六进制字符串转换为字节数组
bool hex_to_bytes(const std::string &hex, uint8_t *out, size_t len);
// 规范化路径:将/转换为\,不翻倍反斜杠
std::wstring normalize_path(const std::wstring &path);
// 将wstring转为string
std::string wstr_to_str(const std::wstring &ws);
// 将UTF-8 char*字符串转为wstring
std::wstring to_wstring(const char *s);
// 将string转为wstring(UTF-8)
std::wstring to_wstring(const std::string &s);
// 将字节数组格式化为十六进制小写字符串
std::string bytes_to_hex(const uint8_t *data, size_t len);
}
+217
View File
@@ -0,0 +1,217 @@
#include "vhd_mount.h"
#include "utils/strings.h"
#include <initguid.h>
#include <virtdisk.h>
#include <string>
#include <vector>
#include <format>
namespace vhd_mount {
// ===== 内部 helper =====
// 打开VHD句柄,失败返回INVALID_HANDLE_VALUE并填充error
static HANDLE open_vhd(const std::wstring &vhd_path, VIRTUAL_DISK_ACCESS_MASK access_mask, DWORD rw_depth,
std::string *error) {
VIRTUAL_STORAGE_TYPE vst = {};
vst.DeviceId = VIRTUAL_STORAGE_TYPE_DEVICE_VHD;
vst.VendorId = VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT;
OPEN_VIRTUAL_DISK_PARAMETERS open_params = {};
open_params.Version = OPEN_VIRTUAL_DISK_VERSION_1;
open_params.Version1.RWDepth = rw_depth;
HANDLE hVhd = nullptr;
DWORD err = OpenVirtualDisk(&vst, vhd_path.c_str(),
access_mask,
OPEN_VIRTUAL_DISK_FLAG_NONE,
&open_params, &hVhd);
if (err != ERROR_SUCCESS) {
if (error)
*error = std::format("OpenVirtualDisk failed: {} path={}", err, utils::wstr_to_str(vhd_path));
return INVALID_HANDLE_VALUE;
}
return hVhd;
}
// 通过任意卷设备路径(\\.\X: 或 \\?\Volume{GUID})获取卷所在磁盘号
static bool device_get_disk_number(const std::wstring &device_path, DWORD &disk_number) {
HANDLE hVol = CreateFileW(device_path.c_str(),
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
nullptr,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
nullptr);
if (hVol == INVALID_HANDLE_VALUE) return false;
struct VolExtentsBuf {
DWORD NumberOfDiskExtents;
DISK_EXTENT Extents[256];
} buf{};
std::memset(&buf, 0, sizeof(buf));
DWORD bytes_returned = 0;
BOOL ok = DeviceIoControl(hVol, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,
nullptr, 0,
&buf, sizeof(buf),
&bytes_returned, nullptr);
CloseHandle(hVol);
if (!ok || buf.NumberOfDiskExtents == 0) return false;
disk_number = buf.Extents[0].DiskNumber;
return true;
}
// 获取指定物理磁盘号上的所有卷GUID路径(封装FindFirstVolumeW,不重复扫描)
// 返回的路径形如 \\?\Volume{GUID}\(带尾部反斜杠)
static bool get_volumes_on_disk(DWORD disk_number,
std::vector<std::wstring> &out_volumes) {
out_volumes.clear();
wchar_t vol_name[MAX_PATH];
HANDLE hFind = FindFirstVolumeW(vol_name, _countof(vol_name));
if (hFind == INVALID_HANDLE_VALUE) {
return false;
}
do {
std::wstring guid_path(vol_name);
// FindFirstVolumeW返回\\?\前缀,CreateFile需要\\.\前缀才能打开卷设备
std::wstring dev_path = guid_path;
if (dev_path.size() >= 4 && dev_path.substr(0, 4) == L"\\\\?\\") {
dev_path = std::format(L"\\\\.\\{}", dev_path.substr(4));
}
if (!dev_path.empty() && dev_path.back() == L'\\') dev_path.pop_back();
DWORD disk_num = 0;
if (!device_get_disk_number(dev_path, disk_num)) continue;
if (disk_num != disk_number) continue;
out_volumes.push_back(guid_path);
} while (FindNextVolumeW(hFind, vol_name, _countof(vol_name)));
FindVolumeClose(hFind);
return true;
}
// ===== 公开接口 =====
// 可写挂载VHD,返回磁盘号、物理路径、卷GUID(不分配盘符)
// 流程:Open(ATTACH_RW|GET_INFO) → Attach(NO_DRIVE_LETTER) → GetVirtualDiskPhysicalPath → 查找卷GUID
bool attach_vhd(const std::wstring &vhd_path, MountedVhdInfo &info, std::string *error) {
// 可写方式打开overlay层(父盘仍是只读的,overlay本身本地可写)
HANDLE hVhd = open_vhd(vhd_path,
VIRTUAL_DISK_ACCESS_ATTACH_RW | VIRTUAL_DISK_ACCESS_GET_INFO,
1, // 顶层1层可写
error);
if (hVhd == INVALID_HANDLE_VALUE) return false;
// 挂载VHD(可写,不自动分配盘符)
ATTACH_VIRTUAL_DISK_PARAMETERS attach_params = {};
attach_params.Version = ATTACH_VIRTUAL_DISK_VERSION_1;
attach_params.Version1.Reserved = 0;
DWORD err = AttachVirtualDisk(hVhd, nullptr,
ATTACH_VIRTUAL_DISK_FLAG_PERMANENT_LIFETIME |
ATTACH_VIRTUAL_DISK_FLAG_NO_DRIVE_LETTER,
0, &attach_params, nullptr);
if (err != ERROR_SUCCESS) {
if (error) *error = std::format("AttachVirtualDisk (RW) failed: {}", err);
CloseHandle(hVhd);
return false;
}
// 通过VHD句柄直接获取物理磁盘路径(\\.\PhysicalDriveX),重试等待系统定位
// 等待:20次 × 100ms = 最长2秒
wchar_t phys_path[MAX_PATH] = {};
ULONG path_size = sizeof(phys_path);
err = ERROR_NOT_READY;
for (int retry = 0; retry < 20; retry++) {
path_size = sizeof(phys_path);
err = GetVirtualDiskPhysicalPath(hVhd, &path_size, phys_path);
if (err == ERROR_SUCCESS) break;
Sleep(100);
}
CloseHandle(hVhd);
if (err != ERROR_SUCCESS) {
if (error) *error = std::format("GetVirtualDiskPhysicalPath failed: {}", err);
return false;
}
info.physical_path = phys_path;
// 解析 "\\.\PhysicalDriveX" 中的磁盘号
DWORD disk_num = 0;
if (swscanf_s(phys_path, L"\\\\.\\PhysicalDrive%u", &disk_num) != 1) {
if (error)
*error = std::format("Failed to parse physical disk path: {}", utils::wstr_to_str(phys_path));
return false;
}
info.disk_number = disk_num;
// 在该磁盘上查找卷GUID路径(重试等待卷枚举就绪)
// 等待:20次 × 100ms = 最长2秒
for (int retry = 0; retry < 20; retry++) {
std::vector<std::wstring> vols;
get_volumes_on_disk(disk_num, vols);
if (!vols.empty()) {
info.volume_guid = vols.front();
return true;
}
Sleep(100);
}
if (error) *error = std::format("no volume found on disk {}", disk_num);
return false;
}
bool detach_vhd(const std::wstring &vhd_path, std::string *error) {
HANDLE hVhd = open_vhd(vhd_path, VIRTUAL_DISK_ACCESS_DETACH | VIRTUAL_DISK_ACCESS_GET_INFO, 0, error);
if (hVhd == INVALID_HANDLE_VALUE) return false;
DWORD err = DetachVirtualDisk(hVhd, DETACH_VIRTUAL_DISK_FLAG_NONE, 0);
CloseHandle(hVhd);
if (err != ERROR_SUCCESS) {
if (error) *error = std::format("DetachVirtualDisk failed: {}", err);
return false;
}
return true;
}
bool create_diff_vhd(const std::wstring &vhd_path, const std::wstring &parent_vhd_path, std::string *error) {
// 如果上次运行遗留了已挂载的overlay.vhd,先尝试卸载再删除
std::string detach_err;
(void) detach_vhd(vhd_path, &detach_err);
DeleteFileW(vhd_path.c_str());
VIRTUAL_STORAGE_TYPE vst = {};
vst.DeviceId = VIRTUAL_STORAGE_TYPE_DEVICE_VHD;
vst.VendorId = VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT;
CREATE_VIRTUAL_DISK_PARAMETERS params = {};
params.Version = CREATE_VIRTUAL_DISK_VERSION_2;
params.Version2.UniqueId = GUID_NULL;
params.Version2.MaximumSize = 0; // 差分盘跟随父盘大小
params.Version2.BlockSizeInBytes = 0;
params.Version2.SectorSizeInBytes = 512; // VHD1必须为512
params.Version2.ParentPath = parent_vhd_path.c_str();
params.Version2.SourcePath = nullptr;
params.Version2.OpenFlags = OPEN_VIRTUAL_DISK_FLAG_NONE;
params.Version2.ResiliencyGuid = GUID_NULL;
// Version2强制要求VirtualDiskAccessMask必须为VIRTUAL_DISK_ACCESS_NONE
HANDLE hVhd = nullptr;
DWORD err = CreateVirtualDisk(&vst,
vhd_path.c_str(),
VIRTUAL_DISK_ACCESS_NONE,
nullptr,
CREATE_VIRTUAL_DISK_FLAG_NONE,
0,
&params,
nullptr,
&hVhd);
if (err != ERROR_SUCCESS) {
if (error)
*error = std::format("CreateVirtualDisk failed: {} path={} parent={}", err,
utils::wstr_to_str(vhd_path), utils::wstr_to_str(parent_vhd_path));
return false;
}
CloseHandle(hVhd);
return true;
}
} // namespace vhd_mount
+36
View File
@@ -0,0 +1,36 @@
#pragma once
#include <windows.h>
#include <string>
namespace vhd_mount {
// VHD挂载后返回的信息
struct MountedVhdInfo {
DWORD disk_number; // 物理磁盘号
std::wstring physical_path; // 形如 \\.\PhysicalDriveX
std::wstring volume_guid; // 形如 \\?\Volume{GUID} (带尾部反斜杠)
};
// 可写挂载VHD,返回磁盘号、物理路径、卷GUID(不分配盘符)
// 内部依次:OpenVirtualDisk → AttachVirtualDisk(NO_DRIVE_LETTER)
// → GetVirtualDiskPhysicalPath → 查找卷GUID
bool attach_vhd(const std::wstring &vhd_path,
MountedVhdInfo &info,
std::string *error);
// 卸载VHD文件
// vhd_path: 与挂载时相同的路径
// error: 输出错误信息
// 返回:成功返回true
bool detach_vhd(const std::wstring &vhd_path,
std::string *error);
// 创建差分VHD(overlay层),父盘指向parent_vhd_path
// vhd_path: 输出VHD文件路径(将被创建,如exe目录\overlay.vhd)
// parent_vhd_path: 父盘NT路径,如 \\?\FscryptDisk_APP_N\internal_N.vhd
// error: 输出错误信息
// 返回:成功返回true
bool create_diff_vhd(const std::wstring &vhd_path,
const std::wstring &parent_vhd_path,
std::string *error);
} // namespace vhd_mount