SAH 和 SAF 文件结构解析

momo314相同方式共享非商业用途署名转载



关于 SAH 和 SAF

据说这是一种曾经普遍在客户端游戏中使用的游戏资源文件打包方式。因为研究的游戏有点古老,对应的技术具体叫什么名字,我在搜索引擎上搜了半天也没找到。

从具体表现上来看,打包后的资源分为两个文件:*.sah*.saf

从文件扩展名上来看,sah 的 h 应该是 header 的意思,而 saf 的 f 可能是 file 的意思?

总之,*.sah 是一个索引文件,其中指定了资源文件存储的目录结构,资源文件名称,大小,位置等信息;而 *.saf 则是用来存储实际的资源文件的,他把资源文件转换为文件流,并将每个资源文件以头尾相连的方式打包为一个资源包。

所以,我们只需要能够解构 SAH 文件,就可能对资源进行解包、更新、重新封包等操作(实际上,这也是游戏资源文件的更新方式)。

SAH 文件结构

因为并没有找到具体的技术文档,以下文件结构是通过分析现有文件,加上搜索和猜测得到的,并不能保证完全准确,不过,从使用来看,基本是对的。

SAH文件分为 header 和 body 两部分:header 部分主要标记文件类型、签名、描述等信息;而 body 部分则主要负责资源文件的目录结构,文件名、文件位置、文件长度等索引信息。

SAH Header

header 部分一共由51个字节组成,按顺序分别是:

  1. index[0 - 2], 长度为 3 个字节的文件类型编码:SAH
  2. index[3 - 6], 长度为 4 个字节的 Int32 类型数据,作用未知,以 0 填充。
  3. index[7 - 10], 长度为 4 个字节的 Int32 类型数值,代表资源文件总数(仅包含文件,不包含文件夹)
  4. index[11 - 50], 长度为 40 个字节的 String 类型数据,目前看来可能是资源包的一些自定义描述性的信息,比如作者,签名之类的。

SAH Body

  1. index[51], 长度为 1 字节,值固定为 1,作用未知。
  2. index[52 - 55], 长度为 4 字节,值固定为 0,作用未知。
  3. 从 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. “资源文件模块”循环结束后,进入“文件夹模块”:
    • 长度为 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 文件的最后一个字节。

经过如上步骤,理论上我们就会得到一个文件和文件夹列表,结构类似于:

从SAH文件读取到的文件结构从SAH文件读取到的文件结构

✎﹏ 本文来自于 momo314和他们家的猫,文章原创,转载请注明作者并保留原文链接。