Detroit: Become Human (PC) BigFile 封包系统分析
大肥鱼Flash逆向整合
1. 封包文件清单
| 文件 | 作用 |
|---|---|
BigFile_PC.idx |
全局索引:373748 条记录,指向所有资源 |
BigFile_PC.dat |
数据分片 0(约 2 GiB) |
BigFile_PC.d01 ~ BigFile_PC.d29 |
数据分片 1~29(每个约 2 GiB) |
BigFile_PC.dep |
依赖清单,与 idx 同构的精简版 |
索引和数据的分离设计:.idx 只有 10 MB,游戏启动时先加载它,之后按需从 30 个分片里 seek 读取。
2. BigFile_PC.idx 索引格式
2.1 头部
偏移 0: 51 55 41 4E 54 49 43 44 52 45 41 4D 54 41 42 49 4E 44 45 58
"QUANTICDREAMTABINDEX"(19 字节,明文魔数)
偏移 19: 4 字节,值为 0x12(未知,疑似版本号)
偏移 23: 4 字节,0x00B0385F(未知)
偏移 27: "ZEP" × 24(填充区,疑似调试/对齐用)
偏移 101: uint32 BE 条目数(Steam 版 = 373748)
偏移 105: 条目区起点
自校验公式:105 + 条目数 × 28 == 文件大小,对 Steam 版:105 + 373748×28 = 10465049,精确相等。
2.2 条目结构(28 字节,全大端)
从偏移 105 起,每条 28 字节,7 个 uint32 BE:
| 字段 | 说明 |
|---|---|
type |
资源类型(见附录类型表) |
| 恒为 1 | 疑似版本/标志 |
data_id |
资源 ID |
offset |
相对该分片文件的偏移 |
size |
资源大小 |
unk |
未知(多数为 0,音频里另有含义) |
bigfile |
分片号:0 = .dat,1.d01.d29 |
真实数据示例(前几条):
@105: type=19001 f2=1 id=166 off=2048 size=1529 unk=0 file=0
@133: type=19001 f2=1 id=706 off=4096 size=73 unk=0 file=0
@161: type=19001 f2=1 id=635 off=6144 size=41 unk=0 file=0
读取方法:offset/size 相对于 bigfile 指定的分片文件,直接 seek(offset) + read(size) 即可拿到资源本体。容器层无加密、无压缩。
3. BigFile_PC.dep 依赖清单
与 idx 同构但更精简:
"QUANTICDREAMTABINDEX" + 00 00 00 12 + uint32 BE 条目数 + 条目区(从偏移 27 起)
条目同样 28 字节,但 offset/size 恒为 0(它只声明"存在哪些 type/id",供依赖分析用)。
4. 资源本体格式
按索引切出来的资源分两大类:
4.1 QZIP 容器(模型/纹理/音频等)
"QZIP\0" + 8 字节类型魔数 + 类型相关头 + 数据
常见魔数:GOGCCHK_(19001)、NOCHK___(19000)、ETF_RAW_(2229)、ETF_RAWL(2230)、DC_INFO(29)、CSNDDATA(1033)、CSNDEVNT(1023)等。
QZIP 内部压缩是 Quantic Dream 自研算法,不是标准 zlib(对多个偏移尝试 zlib.decompress 均失败)。模型/纹理的最终解码目前依赖 daemon1 的工具(见第 8 节)。
4.2 COM_CONT 明文结构(文本/目录类)
LOCALIZ_(1016)、CATALOG(3)、MOTION_GRAPH(15000)等直接以 COM_CONT 开头,无 QZIP 壳,是带标签的序列化结构(详见第 6 节)。
5. 语音资源深挖(CSNDDATA)
这是本项目重点。类型 1033(CSNDDATA)共 80976 条,全部可解析,结构:
"QZIP\0CSNDDATA" (13 字节)
00 00 00 00 (4 字节 0)
uint32 LE (数据区大小 - 21)
uint32 LE 20 (占位串长度)
"18446744073709551615" (20 字节占位串)
float32 LE @45 时长(秒,与解码结果一致)
uint32 LE @49 事件名长度
ASCII 事件名 @53 如 X1106M_HOLDON_INTRO_PC_X11MMARKUS_SINGING_ENG
uint32 LE 内嵌 RIFF 总大小(8 + riff_size)
"RIFF" + ... "WAVE" + "fmt " + "data" (标准 RIFF/WAVE,Wwise 封装)
关键点:
- 内层是 Wwise "Custom Vorbis"(WAVE_FORMAT_EXTENSIBLE + 私有 GUID,48 kHz 单声道)。用
vgmstream可直接解成 PCM WAV,不用关心 GUID。 - 事件名 = 台词/音效名,命名规则见下;这也是"说话人"信息的来源。
- 时长字段是可信的(35.47s 的 float 与 vgmstream 解码结果一致)。
5.1 事件名命名规则
X1106M_HOLDON_INTRO_PC_X11MMARKUS_SINGING_ENG
│ │ │ │ │ │ │ └─ 语言后缀(ENG/JPN/...)
│ │ │ │ │ └──────┴─ 角色 token:X11M + "MARKUS"
│ │ │ └───┴─ 平台/用途(PC/FA/VO...)
│ └───┴─ 场景名(HOLDON_INTRO)
└────────── 章节码(X11 + 主角线 M)
章节码第 4 位是主角线:K=卡拉线、C=康纳线、M=马库斯线、A/X=其他。角色 token 里 X<章节><线><角色名>,如 X07KKARA、X08CHANK、X05MMARKUS、X09KALICE。
5.2 说话人提取规则(实测)
- 取 token 中
X+ 数字 + 线字母(K/C/M/A/X)后的部分为角色名 K/C/M/X线字母直接剥离;A仅在后面是已知人名时剥离(否则 "ANDROID" 这类保留)- 已知人名表兜底:KARA/MARKUS/CONNOR/HANK/ALICE/AMANDA/CARL/LUTHER/ROSE/JOSH/NORTH/SIMON/JERRY/RALPH/ZLATKO/KAMSKI/DANIEL/TODD/CHLOE/LEO/REED/GAVIN/FOWLER/TRACI/CARLOS/CURTIS/HARVEY/MARLA/PERKINS/CARTLAND/WARREN
- 解析不到标
UNKNOWN
5.3 语言分布(全量验证)
80976 = 12 种配音语言 × 每种恰好 6748 条:
| 语言 | 条数 | 语言 | 条数 |
|---|---|---|---|
| ENG | 6748 | FRE | 6748 |
| GER | 6748 | ITA | 6748 |
| SPA | 6748 | MEX | 6748 |
| POR | 6748 | BRA | 6748 |
| JPN | 6748 | POL | 6748 |
| ARA | 6748 | RUS | 6748 |
中文(SCH 简体 / CHI 繁体)只有字幕,没有配音。全部配音总时长约 29.2 小时/语言。
6. LOCALIZ_ 本地化文本格式
类型 1016,1327 条,COM_CONT 明文。结构要点:
"COM_CONT" + uint32 + uint32 (容器头)
... "LOCALIZ_" 标记 ...
uint32 LE loca_type(5 或 6) (5=英法双语,6=全语言)
uint32 LE lang_count
内容区:
[ 语言块 ] × lang_count
语言块头: 01 03 00 00 + 语言码(3 字节 ASCII,如 ENG)
键值流:
uint32 unk + uint32 key_len
key(ASCII)
uint32 text_len + text(UTF-16LE)
实测:每语言 18140 键;SCH(简体)与 CHI(繁体)同时存在。带章节码的台词键约 1 万/语言,其中 6748 个有配音(其余为字幕/选项/流程名)。文本里带 {S}{*1}、<QD_BR> 等事件标记,训练前需清洗。
7. CSNDEVNT 音频事件
类型 1023/1031,QZIP\0CSNDEVNT 壳:
"QZIP\0CSNDEVNT"
01 00 00 00 (版本/标志)
3C 00 00 00 (结构大小)
0A 00 00 00 (ID 串长度)
"1284272540" (事件 ID,十进制 ASCII)
16 00 00 00 (事件名长度)
"Play_AD_VO_CAR_SHOP_PH" (事件名,AD_VO=语音对白)
00 00 00 00
float32 × 2 (疑似音量/时长)
uint32 1022 (引用类型 CSNDBNK_)
uint32 (bank 索引)
8. exe 侧逆向线索(脱壳后)
用 radare2 / IDA 分析 DetroitBecomeHuman.exe.unpacked.exe:
| 地址 | 内容 |
|---|---|
0x141DED060 / 0x141DED070 |
魔数被拆成两段存储:"TABINDEX" + "QUANTICDREAM",加载时拼接校验 |
sub_140528BA0 |
用 "%s%.2d" 拼接分片文件名,再调 sub_14052A130 打开并校验魔数 |
sub_1401367E0 |
BigFile 管理器初始化(读取 "Bigfile" 配置键) |
0x141DEDB10 |
字符串 "BigFile_Chunk_%i_PC" |
结论:索引读取逻辑就是"校验魔数 → 读条目数 → 循环读 28 字节大端条目",与文件侧观察完全一致。
9. 提取工具链
9.1 本仓库工具(Rust)
dbh-bigfile-unpack:解析 idx、按类型/分片过滤提取、自动分片目录、生成 HTML 索引 + CSV 清单。核心优化:
- 30 个分片各一个线程,组内按偏移排序顺序读(消除随机寻址)
- 大类型目录按 4096 个/子目录分片(规避 NTFS 单目录瓶颈,8 万小文件从 165s 降到 26s)
- 8MB 滑窗批量读(语言过滤场景下把寻道量减少 30 倍)
gui_dingzhi:中英/中日语音数据集导出(语音 + 说话人 + 三语文本 + 角色名映射),自动调 vgmstream 转 WAV 并裁剪首尾静音。
# 解包全部资源(按类型目录)
dbh-bigfile-unpack.exe -i 游戏目录 -o unpacked -t 1016 -t 1033
# 语音数据集(中英中日)
dbh-bigfile-unpack.exe --dataset -i 游戏目录 -o E:\dbhvoic --lang ENG,JPN --wav-convert
9.2 社区工具
- daemon1 的 Detroit.exe(XeNTaX t=19586):模型/贴图/动画/声音提取,PC/PS4 通用,自带角色文件清单
- dbh-file-parser(GitHub detroitbecometext):LOCALIZ_ 文本 → JSON
- vgmstream:Wwise
.wem→ PCM WAV(支持该游戏的内层 Vorbis)
10. 附录:类型号全表(Steam 版 373748 条)
| type | 名称 | 条数 | 说明 |
|---|---|---|---|
| 3 | CATALOG | 2649 | 资源目录 |
| 29 | DATA_CONTAINER | 3265 | 数据容器 |
| 1016 | LOCALIZ_ | 1327 | 本地化文本 |
| 1033 | CSNDDATA | 80976 | 音频数据 |
| 2033 | ANIMDATA | 24562 | 动画 |
| 2212 | EVENTS__ | 24555 | 事件 |
| 2229/2230 | ETF_RAW_/ETF_RAWL | 127939 | 纹理 |
| 4091 | HEADER__ | 80976 | 音频头 |
| 19000/19001 | NOCHK___/GOGCCHK_ | 1374 | 关卡数据 |
| 4288/4333 | SCRIPT/SCPTBANK | 5485 | 脚本 |
| 14000 | NAVM____ | 79 | 导航网格 |
| 15000 | MOTION_GRAPH | 48 | 动作图 |
完整 41 种类型用 dbh-bigfile-unpack.exe -i 游戏目录 --list-types 查看。
11. 总结
- 容器层零加密:
QUANTICDREAMTABINDEX明文魔数 + 大端 28 字节条目 + 纯 seek 读取 - 资源层:QZIP 自研压缩壳(文本类除外) + Wwise 音频 + 自研序列化
- 语音文件名本身就是元数据(章节/场景/角色/语言)
- 游戏实际配音量 = 12 语言 × 6748 条/语言
