SAH 和 SAF 文件结构解析
关于 SAH 和 SAF
据说这是一种曾经普遍在客户端游戏中使用的游戏资源文件打包方式。因为研究的游戏有点古老,对应的技术具体叫什么名字,我在搜索引擎上搜了半天也没找到。
从具体表现上来看,打包后的资源分为两个文件:*.sah
和 *.saf
。
从文件扩展名上来看,sah 的 h
应该是 header 的意思,而 saf 的 f
可能是 file 的意思?
总之,*.sah
是一个索引文件,其中指定了资源文件存储的目录结构,资源文件名称,大小,位置等信息;而 *.saf
则是用来存储实际的资源文件的,他把资源文件转换为文件流,并将每个资源文件以头尾相连的方式打包为一个资源包。
所以,我们只需要能够解构 SAH 文件,就可能对资源进行解包、更新、重新封包等操作(实际上,这也是游戏资源文件的更新方式)。
SAH 文件结构
因为并没有找到具体的技术文档,以下文件结构是通过分析现有文件,加上搜索和猜测得到的,并不能保证完全准确,不过,从使用来看,基本是对的。
SAH文件分为 header 和 body 两部分:header 部分主要标记文件类型、签名、描述等信息;而 body 部分则主要负责资源文件的目录结构,文件名、文件位置、文件长度等索引信息。
SAH Header
header 部分一共由51个字节组成,按顺序分别是:
- index[0 - 2], 长度为 3 个字节的文件类型编码:
SAH
。 - index[3 - 6], 长度为 4 个字节的 Int32 类型数据,作用未知,以 0 填充。
- index[7 - 10], 长度为 4 个字节的 Int32 类型数值,代表资源文件总数(仅包含文件,不包含文件夹)
- index[11 - 50], 长度为 40 个字节的 String 类型数据,目前看来可能是资源包的一些自定义描述性的信息,比如作者,签名之类的。
SAH Body
- index[51], 长度为 1 字节,值固定为 1,作用未知。
- index[52 - 55], 长度为 4 字节,值固定为 0,作用未知。
- 从 index[56] 开始,进入“资源文件模块”:
- 长度为 4 字节的 Int32 类型数据,代表当前文件夹下的资源文件数量(设为
file_count
)。 - 如果
file_count
不为0,则开始进行循环,循环次数为file_count
,每个循环单元依次为:- 长度为 4 字节的 Int32 类型数据,代表当前资源文件的文件名长度(设为
file_name_size
)。 - 长度为
file_name_size
的 String 类型数据,代表资源文件的文件名(注意,文件名的最后一位固定为终止符\0
)。 - 长度为 8 字节的 Int64 类型数据,代表当前资源文件在
*.saf
文件中的偏移量。 - 长度为 4 字节的 Int32 类型数据,代表当前资源文件的长度。
- 长度为 4 字节的 未知 类型数据, 表示当前资源文件的一些属性,目前看应该没什么用,可以用来存储一些自定义数据。
- 长度为 4 字节的 Int32 类型数据,代表当前资源文件的文件名长度(设为
- 长度为 4 字节的 Int32 类型数据,代表当前文件夹下的资源文件数量(设为
- “资源文件模块”循环结束后,进入“文件夹模块”:
- 长度为 4 字节的 Int32 类型数据,代表当前文件夹下的子文件夹数量(设为
dir_count
)。 - 如果
dir_count
不为0,则开始进行循环,循环次数为dir_count
,每个循环单元依次为:- 长度为 4 字节的 Int32 类型数据,代表当前子文件夹名字的长度(设为
dir_name_size
)。 - 长度为
dir_name_size
的 String 类型数据,代表子文件夹的名字(dir_name
)(注意,文件名的最后一位固定为终止符\0
)。 - 然后再次进入“资源文件模块”,开始遍历
dir_name
子文件夹下的资源文件。 - 然后再次进入“文件夹模块”,开始遍历
dir_name
子文件夹下的子文件夹。 - 一直循环下去,直到
*.sah
文件的最后一个字节。
- 长度为 4 字节的 Int32 类型数据,代表当前子文件夹名字的长度(设为
- 长度为 4 字节的 Int32 类型数据,代表当前文件夹下的子文件夹数量(设为
经过如上步骤,理论上我们就会得到一个文件和文件夹列表,结构类似于: