技术博客

PCK 文件格式与加密导出:资源包能不能被解包

第 105 / 149 篇 · 2025-09-06 · godot · 资源管线发布源码分析

导出的 Godot 游戏目录里那个几 GB 的 .pck 文件,装着全部脚本、场景和资源。它能不能被人解开掏出素材?答案分三层:格式本身完全公开,未加密的包等于明文;加密导出把门槛抬到"需要逆向二进制";而真正的防线从来不在文件层。这篇先拆格式,再谈加密。

字节级布局:目录为什么在文件尾

PCK 的读取逻辑在 core/io/file_access_pack.cpp 的 PackedData::add_pack(),它的第一步很特别——从文件尾部往前读 12 字节:

┌─────────────────────────── 文件尾 12 B ───────────────────────────┐
│  pack header 的偏移量(uint64)  │  magic "GDPC"(uint32)         │
└────────────────────────────────────────────────────────────────────┘

拿到 header 偏移后跳回去,才读到真正的头部:GDPC magic 再来一遍、格式版本、引擎版本(major/minor)、文件总数。之后是文件目录表,每个条目:

路径(Pascal 字符串) │ 数据偏移(uint64) │ 大小(uint64) │ MD5(16 B) │ 标志位

标志位里有一位是"本文件已加密"。目录表之后就是文件数据区,每个文件按 64 字节对齐存放(对齐值也写在头部,方便整页映射)。

目录放文件尾是打包格式的经典设计,动机是让追加和嵌入都便宜:往 exe 尾部嵌入 PCK 时(--embed-pck 导出选项),只需更新 footer 里的偏移量;热更那篇说的"运行时挂载补丁包"也依赖这个性质——load_resource_pack 打开补丁文件读尾部,整个目录自包含,不用问宿主进程任何事。

读取一条资源的路径

运行时 load("res://hero.png") 命中 PCK 的查找过程:路径经 simplify_path 规范化 → 在目录表的哈希索引里查(Godot 4 目录表加载时建成 HashMap,不是线性扫)→ 得到偏移和大小 → FileAccessPack 按偏移 seek 读数据。MD5 在常规读取时不校验(全量校验成本太高),它是给重导入/校验工具用的。整个流程和普通文件系统调用同构,所以资源层代码对"文件在 PCK 里"完全无感。

PCKPacker:自己造包

引擎给了正向工具 PCKPacker,导出后用脚本就能打补丁包,CI 里常用:

var packer := PCKPacker.new()
packer.pck_start("user://patch_v12.pck")
packer.add_file("res://levels/level5.tscn", "out/level5.tscn")
packer.add_file("res://ui/new_theme.tres", "out/new_theme.tres")
packer.flush()

pck_start 的可选参数里能指定 key——用自己编译模板时的同一 key 加密补丁包,补丁才能被引擎解开。

加密导出:AES-256 的真实防护等级

导出预设里可以勾选加密图片/场景/脚本,机制分两部分:

密钥的产生与分发。 加密不是导出界面里设个密码就完事——key 要在你自己编译导出模板时编进二进制:SCRIPT_AES256_ENCRYPTION_KEY 环境变量给 64 位十六进制(256 bit),编译时嵌进模板。运行时 FileAccessEncrypted 用这个内置 key 做 AES-256 解密。也就是说密钥跟着客户端走,这是所有本地加密方案的死穴。

防护等级的诚实评估。 AES-256 本身在算力上不可破——$2^{256}$ 的密钥空间,按当前计算能力暴力搜索没有讨论价值。但攻击者根本不用暴力:key 就在二进制里,静态分析(字符串交叉引用、启动时内存 dump)几小时内能拿到。所以加密导出挡住的 casual 解包者(双击运行 godot-unpacker 就想看素材的人),挡不住认真的逆向。这个防护等级和商业 DRM 一样是"抬高成本"而非"杜绝"。

加密的运行时代价也要入账。 每个加密文件打开时多一次 AES 解密,脚本全加密意味着首次加载的 CPU 开销和卡顿都增加;加密数据无法被 OS 页缓存直接复用,反复开关文件更贵。素材量级大、加载频繁的工程,建议只加密脚本,素材保持明文——省下的性能比素材被看的损失大。

真正防解包的手段

资产安全的价格排序永远是:客户端加密(便宜、可破)< 结构拆分(中等成本、增加逆向工作量)< 服务器权威(贵、本质安全。值钱的东西——数值、稀有度判定、经济系统逻辑——放服务器,客户端只拿表现层。PCK 里被偷走的只是皮肤。

PCK 文件布局与寻址流程 header:GDPC magic │ 格式版本 │ 引擎版本 │ 文件总数 │ 对齐值 文件目录表(每条:路径 │ 偏移 uint64 │ 大小 uint64 │ MD5×16B │ 加密标志) 加载时建成 HashMap,load() 按路径 O(1) 查到偏移 数据区:文件依次存放,每个按 64 B 对齐 hero.png main.gd(可加密) level5.tscn 文件尾 12 B:header 偏移量(uint64)+ GDPC magic 先读尾部 再跳头部 目录在文件尾 → 追加补丁包、嵌入 exe 只需改尾部偏移