导出的 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 里被偷走的只是皮肤。