UE4资源热更打包工具HotPatcher

HotPatcher是我最近写的用于打包UE项目资源热更的工具,用于追踪工程版本的资源变动来打出Patch。支持一键Cook多平台,一键打包多平台Patch,编辑器支持Windows和MacOS,再写一套从服务器下载patch的流程就是一套完整的游戏热更方案。HotPatcher在项目里已经使用了一段时间,目前比较稳定,今天整理了一下文档,开源出来,会持续更新,欢迎提issus。

HotPatcher与UnrealFrontEnd中的Patch不同,UE的Patch是基于Cook出的资产作为版本管理的内容,这样在管理工程时存在问题:同样的工程版本,很难在不同的电脑上打出相同的Patch(除非把Cooked同步提交,很难管理),也无法基于Patch的版本再打出一个Patch,而且我们还需要能够方便地能够把外部文件打包到pak中(如lua文件),并且方便管理工程和Patch版本,这个插件就是为了解决上面这样的问题。

目前支持的引擎版本为UE4.21-UE4.25,最近有很多朋友私信来问插件相关的问题,我创建了个群来讨论UE4热更新和HotPatcher插件的问题(QQ群958363331),欢迎加入。

注意:该插件只能打出包含UE的资源以及添加的外部文件作为热更的内容(支持lua),修改的C++代码无法热更,需要自己设计好架构。如果是纯蓝图项目则可以完全使用这个工具进行热更。

HotPatcher插件支持根据选择只打包指定的资源(可以指定包含过滤器以及忽略过滤器以及可以指定单个资源),支持资源的递归依赖分析(如只打包某个地图及其所有依赖的资源),打包的Path中可以选择不包含未引用的资源,也不会包含未改动的资源,还会分析项目中的无效资产以及忽略重定向器的资源,实现了基于项目资产的版本追踪而无需管理Cooked的内容,只需要在打Patch之前Cook一遍保证Cook的内容是基于最新工程版本的即可。而且还支持Chunk、支持bMonilithic模式,配置化的方式支持随意组合,支持多线程打包支持导出资源的依赖关系,而且还支持通过Commandlet来执行Cook和Patch以及Release信息的导出,可以随意结合ci/cd实现自己的出包流程。

除了包含UE的资源文件外,额外的支持:

  • 支持包含项目Cook出的非资源文件。如AssetRegistry.bin/GlobalShaderCache*.bin/ShaderBytecode*.ushaderbytecode;
  • 支持包含引擎、项目、和插件的ini文件;
  • 支持包含外部的文件夹和文件(如lua文件,视频文件等),并且可以自定义挂载点,供运行时访问;
  • 打出的pak中可以不包含任何UE的资源文件(只要在HotPatcher中不添加任何资源即可),方便只更新lua的代码或者只更新Ini之类的配置文件;

插件的其他功能:

  • 支持基于上一个patch版本再打出一个patch版本;
  • 支持检测未cook的资源;
  • 支持版本间的diff,可以看到新增、修改、删除(但是删除的资源在之前的版本的pak中是无法删除的,只是diff展示用)的资源信息;
  • 支持检测重复的文件包含;
  • 支持导出该插件所有的中间生成信息和配置;
  • 支持自定义UnrealPak的参数(可以自己指定加密参数与项目统一);
  • 支持同时打出多个平台的patch;
  • 支持Patch的Chunk,指定在一个Patch版本中把资源分别打包到多个Pak中;
  • 支持检测Chunk的文件包含进行错误处理
  • 支持把Patch中的每个资源单独打包为pak,支持任意资源(包括但不限于UE asset/外部文件/各种cooked的bin数据、ini)只要是hotpatcher里可以添加的都可以单独打成pak。
  • 支持多线程打包pak,不启动UrealPak.exe,提高执行效率。
  • 支持导出所选资源的依赖关系

同时,我也写了一个批量Cook的工具,用于一键Cook指定的多个平台(当然使用命令行也可以),目的就是使用最少的步骤完成任务。

我录了一个插件使用说明的视频(汗,第一次录视频还有点紧张):

如果不能翻墙可以看B站的链接:UE4热更新:HotPatcher插件使用教程

插件使用流程

为了方便版本管理,项目一定要使用某种版本控制工具,建议Git.

  1. 打开HotPatcher,选择ByRelease,导出*_Release.json,其中记录了所指定的每个资源的信息;
  2. 使用UE直接打包任意平台的项目(如windows/android/ios)
  3. 在工程中修改/添加/删除资源,修改引擎/项目/插件的设置等;
  4. 打开HotPatch,在Cook项选择你要打Patch的平台并执行Cook;
  5. 打开HotPatcher,选择ByPatch,以上面导出的*_Release.json为基础版本,根据需求选择你需要打到Pak中的内容;
  6. 点击GeneratedPatch,会生成Pak文件和各种信息(其中也包含当前版本的*_Release.json,使Patch可以增量更新)
  7. 生成的Pak中就包含了与上次打出的包中所有差异的内容。

Cook参数说明

HotPatcher中的Cook部分是为了打Patch时方便Cook多个平台,以及方便指定地图(插件会扫描整个项目中的所有地图并列出),不用每次Cook都使用命令行。

Cook可选的分为Platforms/Map(s)/Settings

  • Platforms:选择Cook 的平台,可以多选;
  • Map(s):选择要Cook的Map,该选项下会列出当前工程里所有的Map,可以多选(代码里我也提供了列出引擎和插件目录下Map的选项,如果需要可以在HotPatcherEditor.build.cs中通过控制ENABLE_COOK_ENGINE_MAPENABLE_COOK_PLUGIN_MAP的值来自行开启);
  • Filter(s):可以选择只Cook指定的目录,可以添加多个;
  • Settings:选择Cook设置,默认提供了Iterator/UnVersioned/CookAll/Compressed四个选项,我也提供了OtherOptions可以自己指定要Cook 的参数。

注意:Map(s)中的地图和Settings中的CookAll必须要选中其中的一个或者Filters中具有指定的目录才可以执行Cook,如果只选了CookMap,则只会Cook该Map所引用到的资源,没有被引用的不会被Cook,这个需要注意。

点击CookContent之后会将Cook的log输出到UE的OutputLog中。

ByRelease参数说明

ByRelease操作导出的是一个json的文件,记录了导出时所选择的每个Asset资源的HASH值,基于此HASH值我们可以在后续的Patch中知道该资源是不是被修改了。

Release Settings

  • VersionId:指定当前导出的资源信息是什么版本。
  • ByPakList:从PakList导入来生成Release的配置。
  • PakListFile:打基础包生成的PakList*.txt文件。

在不通过指定PakList*.txt导出Release的情况下,导出的release.json中只包含项目中有引用的资源,没有引用的不会包含。但是引擎默认打包时会把一些没有引用的资源也打包到里面,这就会导致一些资源在基础包中事实上已经存在了,但是在release.json中没有记录,有用到这些资源的话会导致重复包含(使用时没有问题,只是这些资源在pak中会有两份)。使用PakList*.txt就不会存在这个问题。

  • IncludeFilter:当前Release扫描哪些目录下的资源。
  • IgnoreFilter:当前Release忽略哪些目录下的资源。
  • bAnalysisFilterDependencies:对所选过滤器的资源进行依赖分析
  • AssetRegistryDependencyTypes:对所选过滤器中得资源进行依赖分析时要包含的引用资源类型,依赖于bAnalysisFilterDependencies是否启用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
namespace EAssetRegistryDependencyType
{
enum Type
{
// Dependencies which don't need to be loaded for the object to be used (i.e. soft object paths)
Soft = 0x01,

// Dependencies which are required for correct usage of the source asset, and must be loaded at the same time
Hard = 0x02,

// References to specific SearchableNames inside a package
SearchableName = 0x04,

// Indirect management references, these are set through recursion for Primary Assets that manage packages or other primary assets
SoftManage = 0x08,

// Reference that says one object directly manages another object, set when Primary Assets manage things explicitly
HardManage = 0x10,

// Note: Also update FAssetRegistryDependencyOptions when adding more flags
};

static const Type None = (Type)(0);
static const Type All = (Type)(Soft | Hard | SearchableName | SoftManage | HardManage);
static const Type Packages = (Type)(Soft | Hard);
static const Type Manage = (Type)(SoftManage | HardManage);
}

Soft是具有FSoftObjectReference的资源引用,Hard是直接的资源引用,如果想要Soft和Hard都包含,可以选择Package,这个选项默认情况下选Package就好,除非你自己来处理资源引用关系。

  • IncludeHasRefAssetsOnly:对选中的过滤器中的资源文件进行依赖分析(递归分析至Map),如果资源没有被引用则不会打包到Patch中。
  • IncludeSpecifyAssets:结构的数组,可以指定当前Release中的单个资源,该结构的第一个参数需要指定资源,第二个参数控制是否分析并包含当前指定资源的依赖到pak中。
  • AddExternFileToPak:记录Release中包含的非资源文件
  • AddExternDirectoryToPak:记录Release中包含的非资源文件

AddExternFileToPakAddExternDirectoryToPak对应的是Project Settings-Packing中的Additional Non-Asset Directories To Package等设置,用于标记本地包中所包含的非资源文件。

SaveTo

  • SaveAssetDependency:是否存储当前Release版本中所选资源的依赖关系。
  • SaveReleaseConfig:是否存储当前Release的所有选项信息。
  • SavePath:本次的Release信息存储位置。会在当前目录下创建出名字为VersionId的文件夹,所有的文件在此文件夹中。

注意:导出Release时,资源的包含需要与UE直接打包时的设置一致,因为这里导出的Release是记录使用UE直接打出的包所包含的资源。

ByPatch参数说明

ByPatch是真正执行打包出Pak的工具,可以基于之前导出的版本(通过ByRelease导出的Json文件),也可以包含外部文件/文件夹、配置文件(ini)、Cook出的非资源文件(AssetRegistry.bin等),并且可以指定多个平台,支持输入UnrealPak的参数,可以导出当前Patch的各种信息。

支持指定资源过滤器:

HotPatcher打Patch的选项解析:

BaseVersion

  • bByBaseVersion:是否是基于某个基础版本的Patch,若为false,则只打包选择的过滤器文件(依然会分析依赖)和添加的外部文件,若为true则必须要指定一个基础版本,否则无法执行Patch。同时该属性也会控制是否生成Diff信息,若为false则不生成Diff(没有基础版本diff也无意义)。
  • BaseVersion:该选项应选择Patch所基于的版本文件,可以ByRelease或者上次一的Patch生成,默认为*_Release.json

PatchSettings

  • VersionId:当前Patch的版本的ID
  • IncludeFilter:当前Patch扫描哪些目录下的资源变动。
  • IgnoreFilter:当前Patch忽略哪些目录下的资源变动。
  • bAnalysisFilterDependencies:对所选过滤器的资源进行依赖分析
  • AssetRegistryDependencyTypes:与Release中描述的含义相同。
  • IncludeHasRefAssetsOnly:对选中的过滤器中的资源文件进行依赖分析(递归分析至Map),如果资源没有被引用则不会打包到Patch中。
  • IncludeSpecifyAssets:结构的数组,可以指定需要打到Pak中的单个资源,该结构的第一个参数需要指定资源,第二个参数控制是否分析并包含当前指定资源的依赖到pak中,第三个参数AssetRegistryDependencyTypesRelease中描述的含义相同。。
  • IncludeAssetRegistry:在当前的Patch打出的Pak中包含AssetRegistry.bin文件。
  • IncludeGlobalShaderCache:在当前的Patch打出的Pak中包含GlobalShaderCache-*.bin文件。
  • IncludeShaderBytecode:在当前Patch打出的Pak中包含PROJECT_NAME\Content\ShaderArchive*.ushaderbytecode文件。
  • IncludeEngineIni:在当前打出的Patch中包含引擎目录下的ini,也会包含平台相关的ini
  • IncludePluginIni:在当前打出的Patch中包含所有启用的插件中的ini(引擎目录和项目目录的插件都会包含)
  • IncludeProjectIni:在当前的Patch打出的Pak中包含项目的ini文件(不会包含DefaultEditor*.ini
  • bEnableExternFilesDiff:是否对添加的外部文件进行Diff比对,用于只打包修改或者新增的外部文件。
  • AddExternFileToPak:添加外部的非资源文件到Pak中,如txt、视频。

AddExternFileToPak的元素要求:

  1. FilePath需要指定所选文件的路径。
  2. MountPath为该文件被打包到Pak中的挂载路径,默认是../../../PROJECT_NAME/下。

在游戏运行时可以通过FPaths::ProjectDir来访问。
如,AAAA.json的MountPath../../../HotPatcherExample/AAAAA.json,在运行时加载的路径:

1
FPaths::Combine(FPaths::ProjectDir(),TEXT("AAAAAA.json"));

Pak中的所有文件可以通过IPlatformFile来访问。

  • AddExternDirectoryToPak:结构的数组,添加外部文件夹到Pak中,该结构第一个参数(DirectoryPath)为指定系统中的文件夹路径,第二个参数(Mount Point)指定该路径在Pak中的挂载路径;指定文件夹下的所有文件会被递归包含,并且挂载路径均相对于所指定的MountPoint

  • IncludePakVersionFile:是否在当前打出的Patch中存储版本信息。(已废弃)

  • PakVersionFileMountPoint:由IncludePakVersionFile控制是否可以编辑,用于指定*_PakVersion.json文件在Pak文件中的挂载点,默认为../../../PROJECT_NAME/Extention/Versions(已废弃)

ChunkOptions

我为HotPatch增加了Chunk的功能,支持把一个Patch中的资源分别打包到不同的Pak中去。如把一个Patch中的所有Package打包到一个pak里(或者把当前Patch的指定地图打包到某个pak里),所有的lua打包到另一个pak里。

Chunk支持的参数如下:

  • bEnableChunk:是否对当前的Patch执行Chunk
  • ChunkInfos:Chunk信息的列表,用于指定Chunk中应该包含哪些资源。

ChunkInfos的是一个FChunkInfo的结构数组,其结构的成员为:

  • ChunkName:当前Chunk的名字
  • bMonolithic:是否为当前Chunk里的每个资源打出Pak(已支持),在开启bMonolithic模式时,会禁止添加外部文件和ini/Cook的内部数据,不然存放路径会有些歧义。
  • MonolithicPathMode:单片模式的pak存储路径模式,MountPathMount的方式,PackagePath是资源的LongPackageName的路径。
  • bSavePakCommands:是否存储当前Chunk的PakCommand.txt
  • AssetIncludeFilters:指定Chunk中的资源过滤器(注意所选过滤器中的资源不会进行依赖分析)
  • AssetIgnoreFilters:指定Chunk的忽略过滤器
  • bAnalysisFilterDependencies:对所选过滤器的资源进行依赖分析
  • AssetRegistryDependencyTypes:与Release中描述的含义相同。
  • IncludeSpecifyAssets:指定Chunk中包含哪些单个资源,支持进行依赖分析
  • AddExternFileToPak:添加外部文件到Chunk
  • AddExternDirectoryToPak:添加外部目录到Chunk,支持递归分析
  • InternalFiles:用于指定Chunk中是否包含AssetRegistry.binIni等文件

基本上Chunk的参数和Patch选择的参数的一致,可以按照需求配置。

注意:Chunk的原理是:先在Patch的设置中选择资源,会执行一遍整个Patch的资源分析,分析出来的结果用于Chunk配置的过滤,意思就是Chunk中所包含的资源必须是本次Patch的中具有的,一定要注意AssetIncludeFilters不会进行依赖分析,如果你Patch中的过滤器中的资源引用到了其他模块,比如引擎或者插件,在Chunk中也需要指定相应的目录到过滤器。

每个Chunk中所指定的资源可以重复,一份资源可以在ChunkA中包含也可以被ChunkB包含。

而且我还给Chunk增加了错误提示,如果开启了Chunk模式,但是Patch中的所有资源没有在Chunk中全部指定,会在HotPatcher底部的Infomations中提示有哪些资源没有在Chunk中被包含,方便进行错误处理。

bMonolithic模式

  • 会把Chunk中所有的单个资源、文件都单独打包为一个pak
  • 存放路径为该资源的mount point路径

PakOptions

  • PakCommandOptions:该数组可以指定*PakCommand.txt中每条要打包到pak中资源的的参数。
  • ReplacePakCommandTexts:用于替换PakCommand.txt中的文本

  • UnrealPakOptions:该数组为UnrealPak.exe程序的参数。如果什么都不指定,默认的配置为UnrealPak.exe PAKFILE.pak -create=PAK_LIST.txt
  • PakTargetPlatforms:该数组为选择要打出的Patch的平台,可以多选,一定要注意所选的平台已经被Cook。

PakOptions Advanced:

  • bCustomPakNameRegular:是否重写Pak命名规则,控制PakNameRegular是否可修改。
  • PakNameRegular:生成Pak的命名规则,可以通过预置变量值来控制,目前预置值有:{VERSION}/{BASEVERSION}/{CHUNKNAME}/{PLATFORM}四个值,在生成时会替换为当前配置的具体值,默认的命名规则为{VERSION}_{CHUNKNAME}_{PLATFORM}_001_P,如果在没有开启Chunk的情况下,{CHUNKNAME}值为空,并且会处理掉两个连续的__

SaveTo

  • SavePakList:是否存储UnrealPak.exe-Create参数文件。
  • SaveDiffAnalysis:是否存储当前的Patch版本与Base版本的差异信息。
  • SaveAssetDependency:是否存储当前Patch版本中所选资源的依赖关系。
  • SavePatchConfig:是否存储当前Patch的所有选项信息。
  • SavePath:本次的Patch信息存储位置。会在当前目录下创建出名字为VersionId的文件夹,所有的文件在此文件夹中。

Commandlet

我给插件提供了三个Commandlet,分别是HotCookerHotPatcher以及HotRelease,用于Cook和打Patch以及导出Release信息,使用方法:

Cooker使用方法:

1
UE4Editor.exe PROJECT.uproject -run=HotCooker -config="cook-config.json"

在编辑器中导出cook配置的时候如果选中了所有地图,会在导出的配置文件里会把bCookAllMap为true,在Cook的Commandlet里执行就会Cook所有地图了,而且只要bCookAllMap为true就会Cook所有地图,和CookMaps里的地图数量没关系。

Patcher使用方法:

1
UE4Editor.exe PROJECT.uproject -run=HotPatcher -config="patch-config.json"

Release使用方法:

1
UE4Editor.exe PROJECT.uproject -run=HotRelease -config="export-release-config.json"

它们的-config参数所接收的文件都可以从编辑器中通过插件导出。

Update Log

2020.08.31 Update

  • 增加导出Release的Commandlet支持
  • 清理重复的代码,整理组织方式
  • 增加控制生成Pak的命名规则
  • 修复Chunk不包含任何文件时也会打出空包的问题
  • 修复UFlibPakHelper::GetMountedPaks在4.25中Crash的问题

2020.06.04 Update

  • 增加Cook导出配置的bCookAllMap属性,当为true时,在Commandlet模式下会Cook工程目录下中所有的地图。
  • 增加ByRelease支持通过PakList*.txt来生成配置,从而可以完全匹配基础包中的所有资源。

2020.06.03 Update

  • 增加插件Cook的Commandlet
  • 增加插件Patch的Commandlet

2020.04.27 Update

  • 增加Chunk的bMonolithic模式下的MonolithicPathMode,用于指定单片模式下的pak存储路径规则。

2020.04.24 Update

  • 增加是否对过滤器中的资源进行依赖分析的选项

2020.04.23 Update

  • 增加可选引用资源的类型

2020.04.18 Update

  • UnrealOptions默认添加-compressionformats参数。
  • 增加PreviewChunk的选项,在开启Chunk的情况下可以方便地知道哪个chunk中有哪些资源。

2020.04.10 Update

  • 增加ReplacePakCommandTexts选项,可以自己根据需求替换PakCommand.txt中的文本,如替换打到pak中的资源的MountPath

2020.04.09 Update

  • 修复在没有选择过滤器时从PatchSetting生成Chunk的问题/对资源重定向器的溯源操作
  • 修复转换Cooked路径时的问题
  • 支持导出所有pak的信息、删除pakversion选项
  • 支持Release和Patch导出资源的依赖关系
  • 给ExportRelease增加FScopeSlowTask进度显示
  • 增加可以指定PakCommand参数的选项

2020.04.08 Update

  • 支持多线程打包Pak,直接调用ExecuteUnrealPak而不是启动UnrealPak.exe,极大提升效率。
  • 修复PreviewPatch在选中BaseVersion的情况下与Diff信息不一致的问题

2020.04.07 Update

  • 支持bMonolithic模式,可以把所有引用每个资源单独打到一个pak中。
  • 统一了Asset、Cooked的bin、外部文件、ini等所有文件的pak方式。

2020.04.03 Update

  • 完成Patch的Chunk功能,统一Patch和Chunk的打包流程
  • 代码优化、流程梳理
  • 去除IncludePakVersionFilePakVersionFileMountPoint选项

2020.04.02 Update

  • 增加Patch的Chunk功能
  • 修复FPatcherSpecifyAsset序列化与反序列化的问题
  • 优化组织结构

2020.03.28 Update

  • 修复当基于基础版本打patch时导出的*Release.json不全的问题。

2020.03.05 Update

  • 修复当Cook的进程异常退出时没有清掉任务状态的问题。
  • 在代码中增加Cook时可选Engine或Plugin中的地图,在HotPatcherEditor.build.cs中通过控制ENABLE_COOK_ENGINE_MAPENABLE_COOK_PLUGIN_MAP的值来自行开启。

2020.02.14 Update

  • 增加了Patch和Release配置的导入导出
  • 生成Pak的默认命名改成XXX_TARGET_PLATFORM_001_P.pak
  • 修复了一些潜在问题,建议更新。

2019.12.08 Update

  • 新增是否对资源进行依赖扫描的选项,增加diff预览。

2020.02.19 Update

  • 为插件增加了导出release时可以选择添加外部文件和文件夹,对应ue打包时添加的非资源外部文件目录。还支持外部文件的diff(可选),对于指定的外部文件和目录,会检测只有修改或者新增的文件才会打包到pak中。

2020.01.13 Update

  • 新增生成时的错误信息提示,对未Cook资源的扫描以及重复添加的外部文件进行检测,增加可以指定特定资源到Pak中,并且可选是否对该资源进行依赖分析。

2020.01.14 Update

  • 修复会扫描到Redirector会提示Redirector的资源未Cooked,为ExportRelease增加IncludeSpecifyAssets,可以指定某个资源了(如只指定某个地图)。

2020.01.19 Update

  • 增加指定要Cook的目录

Q&A

HotPatcher开源之后有一些朋友陆续找我单独聊到一些问题,有些朋友可能会遇到相同的问题,这里用于收集一些Q&A的内容。

Pak master signature table check failed for pak

  1. 使用HotPatcher打包出来的pak在挂载时Crash并具有Pak master signature table check failed for pak提示

这是由于打出本体包的时候在项目设置中设置了Signing加密,需要在HotPatcher中的UnrealPak参数中添加相同的加密参数。

IPlatformFilePak.cpp中的RegisterPakFile中,同样做了判断:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// Runtime/PakFile/Private/
uint16* RegisterPakFile(FName File, int64 PakFileSize)
{
uint16* PakIndexPtr = CachedPaks.Find(File);
if (!PakIndexPtr)
{
FString PakFilename = File.ToString();
check(CachedPakData.Num() < MAX_uint16);
IAsyncReadFileHandle* Handle = LowerLevel->OpenAsyncRead(*PakFilename);
if (!Handle)
{
return nullptr;
}
CachedPakData.Add(FPakData(Handle, File, PakFileSize));
PakIndexPtr = &CachedPaks.Add(File, CachedPakData.Num() - 1);
UE_LOG(LogPakFile, Log, TEXT("New pak file %s added to pak precacher."), *PakFilename);

FPakData& Pak = CachedPakData[*PakIndexPtr];

if (SigningKey.IsValid())
{
// Load signature data
FString SignaturesFilename = FPaths::ChangeExtension(*PakFilename, TEXT("sig"));
IFileHandle* SignaturesFile = LowerLevel->OpenRead(*SignaturesFilename);
ensure(SignaturesFile);
FArchiveFileReaderGeneric* Reader = new FArchiveFileReaderGeneric(SignaturesFile, *SignaturesFilename, SignaturesFile->Size());
Pak.Signatures.Serialize(*Reader);
delete Reader;
Pak.Signatures.DecryptSignatureAndValidate(SigningKey, PakFilename);

// Check that we have the correct match between signature and pre-cache granularity
int64 NumPakChunks = Align(PakFileSize, FPakInfo::MaxChunkDataSize) / FPakInfo::MaxChunkDataSize;
ensure(NumPakChunks == Pak.Signatures.ChunkHashes.Num());
}
}
return PakIndexPtr;
}

Pak无法被挂载

在本体包中开启signature后,打包出来的Pak无法被挂载
同样是pak的signature的错误,是因为没有为pak生成对应的.sig文件。
Log中的内容如下:

1
2
3
LogPakFile: Warning: Couldn't find pak signature file '../../../Pak/Content/Paks/1.0.3_WindowsNoEditor_P.pak'
LogPakFile: Warning: Unable to create pak "../../../Pak/Content/Paks/1.0.3_WindowsNoEditor_P.pak" handle
LogPakFile: Warning: Failed to mount pak "../../../Pak/Content/Paks/1.0.3_WindowsNoEditor_P.pak", pak is invalid

这是因为打出本体包时Project Setting-Crypto中的bEnablePakSigning被设置成了true,这样对打出来的包里的所有pak都会执行校验,目的就是为了确保只有自己打包的pak才可以被加载

相关的代码处理在:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Runtime/PakFile/Private/SignedArchiveReader.cpp
FChunkCacheWorker::FChunkCacheWorker(FArchive* InReader, const TCHAR* Filename)
: Thread(nullptr)
, Reader(InReader)
, QueuedRequestsEvent(nullptr)
, ChunkRequestAvailable(nullptr)
{
FString SigFileFilename = FPaths::ChangeExtension(Filename, TEXT("sig"));
FArchive* SigFileReader = IFileManager::Get().CreateFileReader(*SigFileFilename);

if (SigFileReader == nullptr)
{
UE_LOG(LogPakFile, Fatal, TEXT("Couldn't find pak signature file '%s'"), *SigFileFilename);
}

Signatures.Serialize(*SigFileReader);
delete SigFileReader;
Signatures.DecryptSignatureAndValidate(Filename);

const bool bEnableMultithreading = FPlatformProcess::SupportsMultithreading();
if (bEnableMultithreading)
{
QueuedRequestsEvent = FPlatformProcess::GetSynchEventFromPool();
ChunkRequestAvailable = FPlatformProcess::GetSynchEventFromPool();
Thread = FRunnableThread::Create(this, TEXT("FChunkCacheWorker"), 0, TPri_BelowNormal);
}
}

所以,如果在用HotPatcher打包pak时没有与项目指定相同的加密参数,则导致放入包内的pak会加载失败(因为验证失败了)。
解决的办法就是,在使用HotPatcher时指定与项目相同的加密信息,当直接使用UE打出本体包时,会默认在下列路径中生成一个Crypto.json文件:

1
PROJECT_DIRECTORY\Saved\Cooked\WindowsNoEditor\PROJECT_NAME\Metadata\Crypto.json

它里面的内容是根据Project Setting-Crypto中的选项生产的。
使用方法为:
在HotPatcher的UnrealPak参数项添加参数:-cryptokeys="Crypto.json"(在UE4.23+中还需要添加-sign参数):

重新生成Pak就会在Pak的目录里生成与Pak同名的.sig文件了,把paksig文件一同拷贝到挂载目录里就可以了。

UnrealPak的参数可以看我之前的一篇文章:UE4工具链配置与开发技巧#UnrealPak的参数

iOS metallib

在4.25不会重新加载shaderbytecode的问题,出iOS包尽量使用远程打包的方式,会生成ushaderbytecode,在4.25里LoadLibrary没有问题,但是如果去加载metallib就有问题。

更新计划

为了使HotPatcher更易用和更强大,我会在这里记录一些不错的更新建议。

  1. 增加支持多线程扫描资源依赖分析,目前在工程内资源数量相当大的情况下(数万个),资源的递归依赖分析会比较慢,开多个线程可以显著减少耗时。
  2. 在UnrealPak下增加直接指定Pak加密参数的选项(开放了可以指定UnrealPak的参数,自己指定加密的参数即可)
  3. 指定Cook单个资源
  4. 把打包出的Pak命名从XX_TARGET_PLATFORM_P.pak改成XX_TARGET_PLATFORM_0_P或者中间的数字可以指定。
  5. Patch和Release配置文件的导入和导出、以及快速地清理配置。
  6. 为release增加包含外部文件的选项,与UE的打包时包含外部文件同步。
  7. 增加对外部文件的Diff,不用每次打包时把所有的外部文件都打包到pak中。
  8. 在一个Patch中打包出多个pak文件,可选哪些资源打到哪个Pak中。
  9. ChunkbMonolithic模式增加多线程支持
  10. 增加Commandlet的支持
  11. 增加导出Release的Commandlet支持
  12. 增加可控制生成Pak的命名规则
  13. 支持打Patch时给特殊的平台添加NoAssets资源
本文会持续更新HotPatcher的文档。

扫描二维码,分享此文章

本文标题:UE4资源热更打包工具HotPatcher
文章作者:查利鹏
发布时间:2020年01月15日 09时41分
更新时间:2020年08月31日 17时27分
本文字数:本文一共有6.5k字
原始链接:https://imzlp.me/posts/17590/
专栏链接:https://zhuanlan.zhihu.com/p/103743690/
许可协议: CC BY-NC-SA 4.0
捐赠BTC:1CbUgUDkMdy6YRmjPJyq1hzfcpf2n36avm
转载请保留原文链接及作者信息,谢谢!
您的捐赠将鼓励我继续创作!