UE4 Pak 文件格式

UE4 打包过程中,会调用 UnrealPak 将 Cook 后的文件资源打包成一整个 Pak 文件,这个 Pak 中的内容可以分为三大块,按写入顺序分别为:文件内容区 + 文件索引信息区 + Pak文件信息区

  • 文件内容区: 依次存储每个文件的 FPakEntry + 文件内容
  • 文件索引信息区: 依次存储每个文件的 文件名 + FPakEntry
  • Pak文件信息区: 存储整个 Pak 文件的信息

    注意: 文件内容区文件索引信息区 FPakEntry 部分除了 Offset 外是冗余存储的,文件内容区域的 FPakEntry 序列化时 Offset 的8字节内容全为 0,如图

    PakInfo_Redundant.png

Pak文件信息区: FPakInfo

这个结构体序列化后,写到pak文件的最后的地方,固定 45 字节,格式如下,代码见 FPakFile::Initialize

PakInfo.png

  • bEncryptedIndex: 1 字节,表示pak的索引部分是否被加密

    PakInfo_EncryptedIndex.png

    Pak 文件加密的基本过程:编译时,TargetRules.cs 文件中会添加一个宏定义 IMPLEMENT_ENCRYPTION_KEY_REGISTRATION(),这个宏展开成 UE_REGISTER_ENCRYPTION_KEY(YourAESKeyCode),这个宏里会声明一个全局结构体实例 GEncryptionKeyRegistration,在构造函数里调用 RegisterEncryptionKeyCallback 注册一个回调,这个回调就是返回 YourAESKeyCodeRegisterEncryptionKeyCallback 这个函数里其实就是向FCoreDelegates::GetPakEncryptionKeyDelegate 里绑定了一个 lambda,lambda里调用回调函数获取密钥。完整的宏展开流程 IMPLEMENT_APPLICATION -> IMPLEMENT_ENCRYPTION_KEY_REGISTRATION -> UE_REGISTER_ENCRYPTION_KEY。也就是说,默认 Pak 加密的 AES 密钥是硬编码在代码里的

  • Magic: 4 字节,值必须为 0x5A6F12E1,否则是非法pak

    PakInfo_Magic.png

  • Version: 4 字节,pak文件格式的版本号

    PakInfo_Version.png

    每个版本的区别如下:

    • 1:初始版本号
    • 2: 移除了 FPakEntry 中的时间戳
    • 3:增加文件加密功能和分块压缩功能,FPakEntry 中多了分块信息数据和是否加密的标志位
    • 4:增加索引加密功能,FPakInfo 中多了索引是否加密的标志位
    • 5:压缩分块信息中的起始和结束位置是相对于 FPakEntry.Offset 的偏移,之前的版本是相对于 Pak 文件起始位置(即0)的偏移
  • IndexOffset: 8 字节,Pak 文件索引信息区的起始位置

    PakInfo_IndexOffset.png

  • IndexSize: 8 字节,Pak 文件索引信息区的大小

    PakInfo_IndexSize.png

  • IndexHash: 20 字节,文件索引信息的SHA1值

    PakInfo_IndexHash.png

文件索引信息区

从上面的结构体中根据索引的起始偏移和大小定位索引内容,并且根据 bEncryptedIndex 来判断是否要对索引内容进行解密,格式如下,代码见 FPakFile::LoadIndex

  • MountPoint: 默认挂载点,类型是字符串,长度不定。字符串序列化格式为:4字节(内容为字符串长度) + 字符串内容(字符串以0结尾)

    Index_MountPoint.png

  • NumEntries: 4 字节,序列化的文件数量

    Index_NumEntries.png

  • 接下来就是依次存储每个文件的 Filename(文件路径) 和对应的 FPakEntry,有 NumEntries 个

Filename

Index_PakEntry_Filename.png

FPakEntry

FPakEntry 记录的是每一个文件序列化到 Pak 中的文件头信息,序列化格式如下

  • Offset: 8 字节,文件实际内容区域起始位置在 Pak 文件中的偏移,第一个文件就是 0

    Index_PakEntry_Offset.png

  • Size:8 字节,文件压缩后的大小

    Index_PakEntry_Size.png

  • UncompressedSize:8 字节,文件原始大小

    Index_PakEntry_UncompressedSize.png

  • CompressionMethod: 4 字节,文件压缩方式,见 ECompressionFlags

    Index_PakEntry_CompressionMethod.png

  • Timestamp: 8 字节,时间戳,这个数据只在老版本的pak(Version <= 1) 中才有,Version >= 2 中的 pak 已经废弃不序列化了

  • Hash: 20 字节,文件的SHA1哈希值

    Index_PakEntry_Hash.png

  • CompressionBlocks: 分块信息列表,每一个分块信息为 16 字节(两个 uint64,分别是块内容起始位置偏移和结束位置偏移)。分块信息数据只有在 Version >= 3,并且开启了压缩的情况下才会序列化。TArray 的序列化格式为: 4 字节(内容为Array长度) + 每一个 Array项 的序列化。所以这部分数据大小为: 4 + 16 * 分块数量,下面的图中只有一个分块

    Index_PakEntry_CompressionBlocks.png

  • bEncrypted: 1 字节,文件块内容是否加密,这部分数据只有在 Version >= 3 才有

    Index_PakEntry_IsEncrypted.png

  • CompressionBlockSize: 4 字节,每一个压缩分块的大小,这部分数据只有在 Version >= 3 才有。序列化时,每个文件会按这个值进行分块,然后每个分块再进行压缩加密,默认值是 64K,如果文件大小小于这个值,则CompressionBlockSize 就是文件实际大小。反序列化时,读取每个分块,进行解密,然后再分配CompressionBlockSize 大小的内存进行解压,所有的内存块合起来就是真正的文件内容,下图的分块大小是 1055

    Index_PakEntry_CompressionBlockSize.png

文件内容区

这个区域位于 Pak 文件的起始位置,依次存储每一个文件的 FPakEntry 和实际文件内容,如果开启了压缩,则文件内容需要进行分块压缩,如果开启了加密,则压缩过的文件内容还要进行加密

FileChunk.png