Pak 与 IoStore
自动关联目录:Pak 与 IoStore
Pak 是 UE 的资源容器:把成百上千个散文件打包成一个大文件,减少 IO 次数、便于分发、便于加密与更新。UE5 引入了 IoStore(.utoc + .ucas)作为新一代容器。
一句话定位:Pak 解决"文件太多",IoStore 解决"IO 太随机";而挂载顺序决定谁覆盖谁,这是热更新的全部原理。
Pak 结构
MyGame-Windows.pak
├── Index(文件表:路径 → 偏移/大小/压缩方式/Hash)
├── 加密的 Index(可选)
└── Data(所有资产的内容,按块压缩)| 特性 | 说明 |
|---|---|
| 只读 | 运行时不写入 |
| 可挂载多个 | 后挂载的优先 |
| 可加密/签名 | 见下文 |
| 可压缩 | 省体积,代价是解压时间 |
| 命名决定优先级 | 带 _P(patch)后缀的优先级更高 |
常用命令:
UnrealPak.exe MyGame.pak -List # 列出内容
UnrealPak.exe MyGame.pak -Extract ExtractDir # 解包
UnrealPak.exe Out.pak -Create=response.txt # 打包(response 文件描述内容)挂载顺序
多个 pak 里如果有同名资产,优先级高的那个生效。优先级由 pak 文件名和挂载顺序共同决定。
(低) MyGame-Windows.pak 主包
↑
MyGame-Windows_P.pak 补丁包(_P 后缀,优先级更高)
↑
(高) 运行时动态挂载的 pakFPakPlatformFile* PakPlatform = (FPakPlatformFile*)FPlatformFileManager::Get().FindPlatformFile(TEXT("PakFile"));
PakPlatform->Mount(TEXT("D:/Patch/MyPatch.pak"), PakOrder, TEXT("D:/Patch/"));PakOrder 越大优先级越高。热更新的本质就是:发一个优先级更高的 pak,里面放新版本的资产。
IoStore
| Pak | IoStore | |
|---|---|---|
| 文件 | .pak | .utoc(索引)+ .ucas(内容) |
| 设计目标 | 减少文件数 | 减少随机 IO,优化加载 |
| 索引方式 | 文件路径 | 按 chunk 组织的容器,读取更连续 |
| UE5 默认 | 否 | 是(可关闭) |
| 补丁 | 覆盖式 pak | 支持 Patch 容器层 |
[/Script/Engine.Engine]
bUseIoStore=True
bUseZenStore=FalseIoStore 的收益在移动端最明显——移动设备的随机 IO 代价极高,容器化后加载时间能明显下降。
代价是工具链更复杂:解包要同时处理 .utoc 和 .ucas,热更新的容器合并逻辑也比单纯覆盖 pak 麻烦。
签名与加密
| 机制 | 作用 | 代价 |
|---|---|---|
| Index 加密 | 隐藏文件表,无法直接 -List | 需配置密钥 |
| 内容加密 | 资产内容加密,无法直接解包 | 运行时解密开销 |
| 签名 | 验证 pak 未被篡改 | 启动校验时间 |
| 主索引签名 | 防止伪造整个包 | — |
[/Script/UnrealEd.ProjectPackagingSettings]
bEncryptIniFiles=True
bEncryptPakIndex=True
bEncryptPakFiles=False # 全包加密,开销大,按需
bEnablePakSigning=True密钥在 DefaultCrypto.ini(不要提交到公开仓库)。
加密只能提高门槛,不能防住决心逆向的人——见 Security。判断是否值得加密的标准是:被解包的商业损失 vs 运行时的解密开销与兼容性风险。
压缩
| 维度 | 不压缩 | 压缩 |
|---|---|---|
| 包体 | 大 | 小(通常 40–60%) |
| 加载 | 快(直接读) | 慢(要解压) |
| 内存 | 无额外 | 解压缓冲 |
| 适用 | 主机/PC、磁盘空间不敏感 | 移动端、下载体积敏感 |
[/Script/UnrealEd.ProjectPackagingSettings]
bCompressPaks=True移动端推荐做法:包体压缩(省下载),但对启动时必需的资源单独放不压缩的 chunk,避免开局慢。
分块与按需下载
| 方案 | 说明 |
|---|---|
| ChunkDownloader 插件 | UE 官方的按需下载,读 BuildManifest-*.json |
| 自定义下载 | 自己管理 pak 下载 + Mount |
| 平台机制 | Android App Bundle / iOS On-Demand Resources |
流程:
1. 启动时读本地版本清单
2. 比对服务器清单,算出缺失/过期的 chunk
3. 下载 pak 到本地持久目录
4. 校验(大小 + 哈希)
5. Mount,通知游戏可用下载一定要校验:下载中断会产生截断的 pak,挂载后表现为各种诡异的资源错误。
与热更新的关系
热更新的完整链路见 热更新与补丁。这里只需记住三条:
- 新 pak 的优先级必须高于旧 pak(文件名
_P后缀或更大的PakOrder) - pak 一旦挂载,同名资产就被覆盖,原 pak 里的旧版本不再生效
- 删除 pak 等于回滚——但已加载进内存的资产不会立刻变
排查
| 现象 | 查什么 |
|---|---|
| 更新后资源没变 | pak 挂载顺序、PakOrder、文件名后缀 |
| pak 挂载失败 | 路径、签名校验、文件完整性 |
| 解包看不到内容 | 是否开了 index 加密 |
| 加载变慢(UE5 升级后) | IoStore 配置、压缩设置 |
| 包里缺资产 | 回到 Cook 阶段的引用链 |
// 打印当前挂载的 pak 列表
TArray<FString> MountedPaks;
FCoreDelegates::OnMountPak.IsBound();
// 或用 -logpaks 启动参数常见坑
| 坑 | 说明 |
|---|---|
| 补丁 pak 优先级没设高 | 更新不生效 |
| pak 放在会被系统清理的目录 | 移动端常见,更新后丢失 |
| 下载后没校验就挂载 | 截断的 pak 导致随机资源错误 |
| 加密密钥泄露在仓库 | 等于没加密 |
| 全包加密导致加载变慢 | 只加密 index 通常够用 |
| 移动端开了压缩导致开局慢 | 关键资源应放不压缩 chunk |
| 混用 pak 与 IoStore 配置不一致 | 部分资产读不到 |
| 热更新后没重启就期望生效 | 已加载资源需重新加载或重启 |
许可协议:CC BY
作者:Davids
本文链接:https://hustjjd.github.io/939d0f11.html
更新于:2026年10月10日