噬神者(GOD EATER)PC版本解包记录 - haojun0823.xyz
文档原创

噬神者(GOD EATER)PC版本解包记录

详细记录噬神者PC版本游戏文件的解包方法与数据结构

# 噬神者PC版本文件结构1.7

**CODE EATER 汉化组**  
https://www.haojun0823.xyz/  
email@haojun0823.xyz  
汉化群:461217810  
https://github.com/HaoJun0823/GECV 参见GECV_II  

**版本1.7,日期2024/11/23**

---

## PRES 结构

`Pres`固定开头:

- `magic_header``0x73657250` (`PRES`)
- `magic_1`, `magic_2`, `magic_3`:意义未知,从解包时获取,似乎永远固定(每个`int32`大小,共3个)
- `pres_config_length`:所有PRES信息的文件长度(包括下文),类型为`Int32`
- `zerozero``Int64`长度的0(因为游戏是32位寻址,几乎不重要)
- `country_count`:国家数量,只有1和6两种。若为1,则文件没有国家语言内容;若为6,则按 (EN, FR, IT, DE, ES, RU) 排列

`Pres`固定开头大小:4+4+4+4+4+8+4 = 32字节

---

### Pres国家(country)结构

**条件:** 如果`country_count != 1`时才存在;若`country_count == 1`,则直接从dataset开始(即country不存在)

- `dataset_offset`:集合(dataset)信息的地址指针
- `dataset_length`:整个集合的大小

`Pres`国家结构大小:0 或 (4+4)*6 = 48字节(若country不存在,则数量和指针均为0,并立刻开始读取dataset)

---

### Pres集合(dataset)结构

总是有8个,分别是:`res`, `prx`, `asset`, `unk`, `conf`, `tbl`, `text`, `rtbl`

- `data_offset`:第一个dataset的数据指针
- `data_count`:data的数量

`Pres`集合结构大小:(4+4)*8 = 64字节(若dataset不存在,则数量和指针均为0)

---

### Pres集合的数据区(data)

每个data大小为 4+4+4+4+4+4+4+4 = 32字节

- `offset``Int32`。偏移地址,转换为十六进制后,第一位为`B``F`,后七位是Pres文件内的指针(从文件开始算起)。  
  算法:`offset & ((1 << (32 - 4)) - 1)`
  - `offset_type`(非游戏内数据):若为`B`,文件是虚拟引用,目标指针为虚拟引用的文件名,游戏运行时读取;若为`F`,根据集合情况储存文件到指定区块。假设后七位为`real_offset`,第一位为`offset_type`
  - `real_offset`(非游戏内数据):`offset`后七位的`Int32`
- `csize``Int32`。data存在于pres中的大小。若文件为`B`,此处为0。  
  (若`dataset == tbl`,则此大小需除以4(`csize/4`),解包时需乘以4(`csize*4`))
- `conf_offset``Int32``data_conf`(数据信息)的指针位置
- `conf_count``Int32``data_conf`的数量
- `unk1``Int32`。未知(1,2,3),只会被游戏载入
- `unk2``Int32`
- `unk3``Int32`
- `usize`:若文件是blz4压缩,则为解压后的大小;否则与`csize`相同(用于BLZ2)
- `file_data`(非游戏内数据):若`real_offset``F`,从`real_offset``csize`大小的文件(若`dataset == tbl`,则`csize`需乘以6[存疑,目前*4即可])
- `B_file_name`(非游戏内数据):若`real_offset``B`,从`real_offset`读取UTF8字符串直到`00`结束

---

### Pres集合信息的数据(data_conf)

#### 特殊情况(第6、7集合,`dataset == tbl``dataset == text`,且data的offset为`F`(真实引用))

- 文件不可被重复引用,每个国家的语言是单独的。
- `file_data`:需用0对齐到16的整数倍,若已是16的整数倍,则补16个0。地址为`real_offset``offset = 'F' + real_offset`

#### 文件信息数据块(data_conf)

- `conf_offset`指向此处的数据。
  - 根据`conf_count`数量写入指针`conf_offset_{index}`:指向data的起始指针。
  - 根据`conf_count`数量写入数据`conf_data_{index}`:一段UTF8编码的字符串,末尾以十六进制的`00`结束。
- 需要用0对齐到16的整数倍,若已是16的整数倍,则补16个0。

#### 若`offset_type == 'B'`

- 文件不可被重复引用,每个国家的语言是单独的。
- `B_file_name`:需用0对齐到16的整数倍,若已是16的整数倍,则补16个0。地址为`real_offset``offset = 'B' + real_offset`

---

每个国家的dataset结束后,`pres_config_length` = 当前位置的指针,应为16的整数倍。

---

### 末尾文件区块

- `file_data`:若为`F`,且`dataset`不是`tbl``text`,文件放在此处,可被重复引用。需用0对齐到16的整数倍,若已是16的整数倍,则补16个0。被引用文件的`real_offset`为此处地址值,`offset = 'F' + real_offset`

整个文件应为16的整数倍大小。

---

## BLZ4

### 头部

- `magic``0x347a6c62` (`BLZ4`)
- `unpack_size``int32`,解压后的大小
- `zerozero``int64`,8个0(推测`unpack_size`应为`int64`,但游戏不需要这么大)
- `md5`:16字节,解压后文件的MD5

### 区块

- `chunk_size`:若为0,则文件未压缩,从此处直接取到最后;否则取`chunk_size`大小的`chunk_data`。若最后为0或无数据,则结束。在PRES中所有文件都是16的整数倍。
- `chunk_data``UINT16`类型,根据上文取到的数据长度为zlib压缩过的数据。

### 解包过程

假设有8个区块`01234567`,按照`12345670`的顺序分别zlib解压然后提取。

### 打包过程

根据一定大小切割原始文件(建议32768,若为65535即UINT16上限,可能因压缩后变大而溢出)。假设切割出8个区块,前7个大小32768,最后一个10000,则按`01234567`顺序压缩,最后写入`12345670`

---

## BLZ2

### 头部

- `magic``0x327a6c62` (`BLZ2`)

### 区块

- `chunk_size`:最大`0xff`(65535,`UINT16`
- `chunk_data`:存储的文件数据区块,通过`Zlib.Deflate`压缩(与BLZ4不同)

### 解包过程

同BLZ4,第一区块位置仍然颠倒。  
若文件大小 ≤ `0xff`,则不会被BLZ2压缩,此类文件永远只有一个区块,且`chunk_size == 文件大小`

### zlib配置

参考 `(9, zlib.DEFLATED, -15)` (Python)

---

## BNSF / IS14

万代魔改的G722.1C。

- 解码参考:https://github.com/kode54/vgmstream/blob/master/src/meta/bnsf.c  
- 编码参考:https://exvsfbce.home.blog/2020/02/04/guide-to-encoding-bnsf-is14-audio-files-converting-wav-back-to-bnsf-is14/

### 结构

- `magic``INT32`,固定为`BNSF`
- `file_size`:总文件大小 - 0x08
- `info``byte[8]`,8个字符,表示文件版本
- `conf_1``Int32`(未知,可能是比特率或宽度)
- `conf_2``Int32`,声道数(1=单声道,2=双声道)
- `conf_3``Int32`(未知)
- `conf_4``Int32`,WAV样本大小(sample)
- `conf_5``Int32`(未知)
- `conf_6``Int32`(未知)
- `conf_7``Int32`(未知)
- `conf_8``Int32`,后面所有数据的大小(总文件大小 - 0x30)
- `file_data`:被编码的数据

---

## GNF

没有明显的头特征。

- `count`:内部的DDS数量,`int32`
- `file_size_{count}`:每个`int32`,表示每个DDS文件的大小
- `dds_file`:根据上述大小依次获取

---

## QPCK

- `magic``0x37402858`,固定`INT32`
- `count``int32`,文件数量

根据文件数量依次取得:

- `offset``INT64`,QPCK内的偏移
- `hash``INT64`,数据的hash(转换为16进制字符串,游戏调用依据)
- `size``INT32`,文件大小

根据上述`offset``size`依次获取数据,命名为 `{文件顺序}_{hash的16进制16长度字符串}`。打包QPCK时需按顺序打包回去,不要修改hash。

> **注1**:可在`offset`处读取`int32`来判断文件类型。  
> **注2**:QPCK实际文件结构由内部某文件决定(如开头`80 02 63 72 6C 73 66 69 6C 65 0A 52 6C 73 44 61 74 61`),目前无法解开该结构。

---

## TR2

### 头部

- `header``int32`,固定为`0x3272742E`
- `header_magic``int32`,基本固定,例如`00 00 DF 07`表示2015(新版本/PC),`00 00 DA 07`表示2010(旧版本/SONY_A),旧版本还可能有2000、1999。
- `table_name``byte[48]`,固定48字节,从头读到0结束。
- `table_column_inf_offset`:表信息开始的地方,绝大多数为`0x40`
- `table_column_int_count`:头信息的个数

### 表开始(0x40)

假设对象为 `row_inf`

- `id``int32`,表的ID
- `offset``int32`,从文件开始算起,表的数据偏移
- `magic``int32`,表的魔术码
- `csize``int32`,表的大小
- `usize``int32`,与BLZ4中类似,但此处无压缩,永远等于`csize`
- `row_data`:定义对象,从`offset``csize`大小的`byte[]`

### 表配置

`0x40 + table_column_int_count * 5 * 20`(表开始结束)  
如果是老版本TR2,没有此部分,需要通过每个分表重建。  
若TR2没有此部分,说明该文件未被游戏调用。假设为 `row_conf`

- `header_count``int32`,表ID的数据数量
- `header_id``int32[headercount]`,数组,表的列名称

以上内容需对齐16的整数倍,不足补0,若正好是16倍则不变。

### `row_data`(从文件0x00开始算起始指针,不能从TR2文件内部算)

- `row_name``byte[48]`,固定48字节,从头读到0结束
- `row_serial``byte[16]`,怀疑是对象类型序列化特征。前8字节为数据格式,后8字节为Bin版本。  
  前8字节老版本参考:`6A 61 6A 70 00 00 00 00` = "jajp"  
  后8字节参考:`79 6F 62 69 38` = "yobi8"(老:`00 CF 07`,新:`02 DF 07`
- `row_type``byte[48]`,固定48字节,读到0结束。可能值为:`INT8`, `UINT8`, `INT16`, `UINT16`, `INT32`, `UINT32`, `FLOAT32`,以及 `ASCII`, `UTF-8`, `UTF-16LE`,可能还有 `UTF-16`(大端?)

以上为固定长度内容,然后从`0x70`开始:

- `0x70-0x73`:不要动
- `0x74`:未知
- `0x75`:对齐方式(若为2则最后补2的倍数,4则补4的倍数)
- `0x76`:一个byte,表示数据的数组大小(**2024-11-23更新**`0x76``0x77`共同组成`INT16`数组大小)
- `0x77`:未知
- `0x78-0x7B`:不要动
- `row_data_count``0x7C-0x7F`,数据的数量

根据`row_data_count`循环读取:

- `row_data[row_data_count].offset``int32`,地址连续。若超出数据最大长度,则为无效指针,应标记为数据结束位置。指针可能重复同一地址(游戏编译时合并相同数据以优化)。

#### 老版本TR2存储(old_tr2_offset_area)

- `row_data[row_data_count].id``int32`(老版本没有开头的`table_column_int_count`,因此此处存储4字节ID,可能重复但不乱序)
- `row_data[row_data_count].offset``int32`,同上
- `row_data[row_data_count].length``int32`,数据长度。一些数据不通过`0x76`判断大小,而是通过此值除以数据类型长度(如`int16`为2,则表示有1个`int16`)。建表时需标记有效/无效数据。

**注意**:多数组文本存储方式不同(见下文)。

- `lastmark``int32`,结束的数据记号,即所有`row_data_data`结束时的指针
- `lastmark_zero4`:4个0,用于分割后续数据

从此处开始对齐16的整数倍,不足补0,若正好16倍则不变。

### `row_data_data`

跳转到每个`row_data[row_data_count].offset`的位置开始读取:

- 根据`row_type`决定读取长度:数字类型分别为1,1,2,2,4,4,4;文字类型(ASCII/UTF-8)读到`0x00`结束,UTF-16LE读到`0x0000`结束。注意UTF-16LE有特殊编码,不能在普通编辑器中修改。
- 根据`0x76`决定读取数量。  
  例如:`0x76` = 9,`row_type` = `"UINT8"``row_data_count` = 3,则有3个数据对象,每个对象数组长度为9,每次读1字节。
- 若数据超过指针范围,则剩余数据不存在,做记号。

### 表格展示方式

- 列:  
  - `row_inf.id`  
  - `row_inf.row_data.row_name`  
  - `row_inf.row_data.row_type`  
  - `row_inf.row_data.0x77`(作为索引)  
  - `key(column)`:根据`row_data_count`顺序提取`row_conf`,即 `row_conf[row_data_count].header_id`  
  - `value(row)``row_inf[id].row_data[row_data_count].row_data_data[0x76]`

> 注:参见GECV TR2 GUI EDITOR展示方法。

### 2024/05/04新发现

`0x76`为多数组且为文本格式(ASCII/UTF8/UTF16),可能出现以下情况(示例`0x76` = 0x14(十进制20),UTF8):


FF FF 2C 00 42 00 6A 00 7D 00 8D 00 A9 00 BC 00 C9 00 D2 00 DB 00 E4 00 ED 00 F6 00 FF 00 08 01 11 01 1A 01 23 01 2C 01 35 01 
FF FF E5 A4 9A E7 81 BD E5 A4 9A E9 9B A3 E7 9A 84 E6 97 A5 E5 AD 90 00 E4 BA 9E E8 8E 89 E8 8E 8E EF BC 8C E5 9B A0 E9 81 8E 
E5 BA A6 E5 8B 9E E7 B4 AF E8 80 8C E5 80 92 E4 B8 8B E3 80 82 00 ……


- 第一个`FF FF`:双字节指针组
- 第二个`FF FF`:文本组

将第一个`FF FF`的地址视为0x00,则指针`2C 00`跳转到`E5 A4`,取UTF8直到`00`结束,正确文本为:  
`E5 A4 9A E7 81 BD E5 A4 9A E9 9B A3 E7 9A 84 E6 97 A5 E5 AD 90 00`

第二个指针`42 00`跳转到正确文本:  
`E4 BA 9E E8 8E 89 E8 8E 8E EF BC 8C E5 9B A0 E9 81 8E E5 BA A6 E5 8B 9E E7 B4 AF E8 80 8C E5 80 92 E4 B8 8B E3 80 82 00`

### 2024/05/05 老版本TR2多数组文本存储

老版本将下一级数组的存储放在`old_tr2_offset_area`层。假设`0x76` = 4,`row_data_count` = 12,则有12/4=3组数据,ID从00到02。

存储情况如下:


00 文本指针 文本长度 00 文本指针 文本长度 00 文本指针 文本长度
01 文本指针 文本长度 01 文本指针 文本长度 01 文本指针 文本长度
02 文本指针 文本长度 02 文本指针 文本长度 02 文本指针 文本长度


文本区域

文本长度不包含UTF8/ASCII的`00`和UTF16的`00 00`,但文本需要以`00`结尾。

### 如何判断TR2版本

- PC版本称为VER.2,SONY_A版本称为VER.1  
- PC版本有`header_count`。若读取`header_count`为0,或`header_count`超过头的大小,则为VER.1,否则为VER.2  
- 头的总长度由第一个片段的`offset`决定(即头数据的结尾)。

---

`row_data`从此处开始对齐16的整数倍,不足补0,若正好16倍则不变。
切换主题

点击选择主题预览