热更新与补丁

自动关联目录:热更新与补丁

热更新的前提是"资源可以覆盖,代码不可以"。UE 的 pak 挂载机制天然支持资源覆盖,所以热更的对象是资产与配置;C++ 逻辑要改,只能换包(或用 Lua 这类脚本层)。

一句话定位:热更新 = 下载更高优先级的 pak → 校验 → 挂载。技术难度不高,难的是版本管理与失败处理。

能力边界

能热更不能热更
贴图、网格、动画、音效C++ 代码
蓝图(作为资产)引擎本身的改动
数据表、配置(ini/Curve/DataTable)模块的结构(新增 UCLASS 若被 C++ 引用会出问题)
Lua / 脚本(见 UnLua)平台层、SDK(见 Platform)

设计约束:任何"可能会被热更需要改动"的逻辑,一开始就该放在脚本层或数据层。把逻辑写死在 C++ 里,等于放弃热更能力。

完整流程

① 启动 → 读本地版本清单(version.txt / manifest)
② 请求服务器版本 → 比对
③ 有更新 → 下载(全量 pak 或 差分补丁)
④ 校验(文件大小 + 哈希 + 可选签名)
⑤ 写入持久目录(原子写:先 .tmp,再 rename)
⑥ 更新本地版本清单(这一步必须在挂载之前持久化完成)
⑦ Mount pak
⑧ 进入游戏

第 ⑥ 步是本流程中唯一必须"先落盘再生效"的地方。如果先挂载再写清单,中途崩溃会导致"代码以为已更新、磁盘上还是旧的",也就是所谓"半更新状态"。

版本管理

方案说明适用
单调递增版本号123 → 124,简单可靠推荐
全量 pak + 版本目录每个版本一个目录简单但下载大
差分补丁链v1→v2→v3 依次打省流量,但链断了就废
基线 + 累积补丁定期出基线,中间打补丁折中
PersistentDir/
  Paks/
    patch_v120.pak
    patch_v121.pak
    patch_v122.pak     ← 挂载时按版本号倒序,新的优先
  version.txt          ← 已生效的版本

差分补丁的隐患:补丁链越长,中间任一环出错就越难恢复。常见做法是超过 N 个补丁就要求下载全量包。

差分方案

工具特点
bsdiff / bspatch经典,压缩率高,内存占用大
HDiffPatch(HDiffZ)中文文档友好,适合大文件,支持流式
zstd --patch-from集成在 zstd 里,速度快
自研按资产粒度只替换变动的资产,跳过未改动的

按资产粒度做差分往往比二进制差分更实用:pak 里资产顺序一变,二进制差分就失效;而按资产对比能稳定识别出"只有这 3 个贴图变了"。

校验

层手段
传输HTTPS
完整性SHA-256 / MD5(防传输损坏)
来源pak 签名(防伪造,见 Pak)
版本清单里带版本号与哈希,双重校验
// 写入前校验哈希,不匹配就删掉重新下
FString Hash = FMD5::HashFile(*TmpPath);
if (Hash != ExpectedHash) { IFileManager::Get().Delete(*TmpPath); return false; }
IFileManager::Get().Move(*FinalPath, *TmpPath);   // 原子替换

永远不要直接下载到目标文件名——中断会产生半截文件,下一次启动会把它当成有效文件。

失败与回滚

失败点处理
下载中断断点续传;失败保留旧版本继续进游戏
校验失败删除临时文件,重试 N 次,仍失败则用旧版本进游戏(或强更)
写盘失败检查磁盘空间,先检查再下载
挂载失败记录日志,回退到旧版本
更新后启动崩溃需要"安全模式":检测到连续崩溃 N 次就跳过更新 pak

最重要的兜底:让"更新失败"不等于"进不去游戏"。强更(不更新不能玩)要有单独的理由与提示,不要默认开启。

// 崩溃保护:用本地计数值判断
int32 CrashCount = LoadCrashCount();
if (CrashCount >= 3) { SkipMountedPatches(); }   // 安全模式

强制更新与兼容性

情况必须强更
协议不兼容(服务端已升级)是
存档格式变了且无法前向兼容是
纯美术资源更新否
平衡性数值否(可以下次再说)

判断标准:旧版本客户端继续运行会不会造成数据污染或破坏他人体验。会 → 强更;不会 → 允许延迟更新。

移动端额外事项

平台注意
Android存储权限、分区存储(Scoped Storage)、OBB 大小限制
iOSApp Store 审核规则:不能下载可执行代码,脚本引擎要小心
通用后台下载、断点续传、蜂窝网络提示、磁盘空间检查

iOS 上热更脚本引擎是有审核风险的,Lua 相对安全,但下发 .lua 源码/字节码仍可能触发审核问题——上线前必须确认当时的审核口径。

监控

指标为什么
更新成功率直接反映分发是否有问题
下载耗时分布CDN 与包体
更新后崩溃率最关键的"坏了没有"信号
版本分布决定什么时候可以下线旧补丁
回滚次数兜底逻辑是否生效

没有监控的热更新等于盲飞。至少要有"更新后 24 小时崩溃率"这一条。

常见坑

坑说明
先挂载后写版本清单崩溃导致半更新状态
直接下载到目标文件名中断产生半截 pak
不校验就挂载截断文件导致随机资源错误
补丁链过长中间断一环全废
pak 优先级没设高更新不生效(见 Pak)
更新失败就不让玩家进游戏无谓的流失
没做空间检查下载到一半失败
忽略更新后崩溃率线上炸了才发现
把逻辑写死在 C++失去热更能力
版本回滚不彻底旧补丁还在持久目录里被挂载