UE Hot update: Questions & Answers

UE热更新:Questions & Answers

HotPatcher project has been open source for over a year, undergoing numerous updates and optimizations. It has increasingly been chosen by developers as a hot update solution for their projects. During this time, many have inquired about common issues related to UE4 hot updates, which I noticed frequently repeated, so I decided to organize these common questions to help newcomers quickly troubleshoot the UE4 hot update solution.

This article will continually update the Q&A content related to UE4 hot updates and HotPatcher. If you have any questions, feel free to comment directly on this article. I will periodically consolidate responses. You can also join my UE4 hot update group to discuss encountered issues (QQ group 958363331).

  1. Can it be used in commercial projects?

The open-source license of this software: allows free use of functionality in commercial projects, but does not permit any third party to charge fees in any form based on this plugin, including but not limited to charging for recorded courses, redistributing the plugin and code, etc.

  1. Can C++ be hot updated?
    No, it can only be used to update uasset and Non-Asset (lua/db/json, etc.).

  2. Does it support hot updating on mobile platforms?
    Yes, HotPatcher has no platform restrictions and can package and manage any platform supported by UE.

Note: When using HotPatcher for packaging, avoid a directory containing both uasset and non-asset files, as this will result in un-cooked uasset packaging.

Series of Articles on Hot Updates

The series of articles I wrote on UE4 hot updates can serve as a reference for project practices:

Automatic Mounting Directory for Pak

Pak files in the following three paths will be automatically mounted when the engine starts:

  • Engine/Content/Paks
  • GAME_DIR/Content/Paks
  • GAME_DIR/Saved/Paks
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Runtime\PakFile\Private\IPlatformFilePak.cpp
void FPakPlatformFile::GetPakFolders(const TCHAR* CmdLine, TArray<FString>& OutPakFolders)
{
#if !UE_BUILD_SHIPPING
// Command line folders
FString PakDirs;
if (FParse::Value(CmdLine, TEXT("-pakdir="), PakDirs))
{
TArray<FString> CmdLineFolders;
PakDirs.ParseIntoArray(CmdLineFolders, TEXT("*"), true);
OutPakFolders.Append(CmdLineFolders);
}
#endif

// @todo plugin urgent: Needs to handle plugin Pak directories, too
// Hardcoded locations
OutPakFolders.Add(FString::Printf(TEXT("%sPaks/"), *FPaths::ProjectContentDir()));
OutPakFolders.Add(FString::Printf(TEXT("%sPaks/"), *FPaths::ProjectSavedDir()));
OutPakFolders.Add(FString::Printf(TEXT("%sPaks/"), *FPaths::EngineContentDir()));
}

The default priority of the pak files in these three paths varies (unless named in the form _1_P.pak):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Runtime\PakFile\Private\IPlatformFilePak.cpp
int32 FPakPlatformFile::GetPakOrderFromPakFilePath(const FString& PakFilePath)
{
if (PakFilePath.StartsWith(FString::Printf(TEXT("%sPaks/%s-"), *FPaths::ProjectContentDir(), FApp::GetProjectName())))
{
return 4;
}
else if (PakFilePath.StartsWith(FPaths::ProjectContentDir()))
{
return 3;
}
else if (PakFilePath.StartsWith(FPaths::EngineContentDir()))
{
return 2;
}
else if (PakFilePath.StartsWith(FPaths::ProjectSavedDir()))
{
return 1;
}

return 0;
}

Purpose of Mount Point

When mounting a Pak, there is a parameter that can specify the MountPoint:

1
2
3
4
5
6
7
/**
* Mounts a pak file at the specified path.
*
* @param InPakFilename Pak filename.
* @param InPath Path to mount the pak at.
*/
bool Mount(const TCHAR* InPakFilename, uint32 PakOrder, const TCHAR* InPath = NULL, bool bLoadIndex = true);

So, what does it do?
Starting from the Mount function:

1
2
3
4
if (InPath != NULL)
{
Pak->SetMountPoint(InPath);
}

If InPath is passed during the call to Mount, it sets InPath for the Pak’s FPakFile instance using SetMountPoint. Actually, in FPakFile, the MountPath has a default value (read from the Pak file). The constructor of FPakFile calls Initialize(Reader, bLoadIndex);, and in Initialize, it calls LoadIndex, where the logic for reading the Pak’s Mount Point from the Pak is implemented:

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
// Runtime/PakFile/Private/IPlatformFilePak.cpp
void FPakFile::LoadIndex(FArchive* Reader)
{
if (CachedTotalSize < (Info.IndexOffset + Info.IndexSize))
{
UE_LOG(LogPakFile, Fatal, TEXT("Corrupted index offset in pak file."));
}
else
{
if (Info.Version >= FPakInfo::PakFile_Version_FrozenIndex && Info.bIndexIsFrozen)
{
SCOPED_BOOT_TIMING("PakFile_LoadFrozen");

// read frozen data
Reader->Seek(Info.IndexOffset);
int32 FrozenSize = Info.IndexSize;

// read in the index, etc data in one lump
void* DataMemory = FMemory::Malloc(FrozenSize);
Reader->Serialize(DataMemory, FrozenSize);
Data = TUniquePtr<FPakFileData>((FPakFileData*)DataMemory);

// cache the number of entries
NumEntries = Data->Files.Num();
// @todo loadtime: it is nice to serialize the mountpoint right into the Data so that IndexSize is right here
// but it takes this to copy it out, because it's too painful for the string manipulation when dealing with
// MemoryImageString everywhere MountPoint is used
MountPoint = Data->MountPoint;
}
// ...
}
// ...
}

In simple terms, if the Mount Point is not passed during mounting, it will be read from the Pak file. If passed, it will set it to the provided value (the MountPoint in the Pak represents the common path for all files in the Pak).

So, what is the purpose of setting the MountPoint for the Pak?
The real purpose is to check whether the file to be loaded exists in the current Pak! Since the default meaning of the Pak’s Mount Point is the common path of all files within it, all we need to do is check if the file to be read starts with this path, allowing us to eliminate paths that are incorrect (if the base path is wrong, it means the file does not exist in the Pak as well).

The specific logic can be seen in the implementation of this function:

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
38
39
40
41
42
43
44
45
46
// Runtime/PakFile/Public/IPlatformFilePak.h
/**
* Finds a file in the specified pak files.
*
* @param Paks Pak files to find the file in.
* @param Filename File to find in pak files.
* @param OutPakFile Optional pointer to a pak file where the filename was found.
* @return Pointer to pak entry if the file was found, NULL otherwise.
*/
static bool FindFileInPakFiles(TArray<FPakListEntry>& Paks, const TCHAR* Filename, FPakFile** OutPakFile, FPakEntry* OutEntry = nullptr)
{
FString StandardFilename(Filename);
FPaths::MakeStandardFilename(StandardFilename);

int32 DeletedReadOrder = -1;

for (int32 PakIndex = 0; PakIndex < Paks.Num(); PakIndex++)
{
int32 PakReadOrder = Paks[PakIndex].ReadOrder;
if (DeletedReadOrder != -1 && DeletedReadOrder > PakReadOrder)
{
//found a delete record in a higher priority patch level, but now we're at a lower priority set - don't search further back or we'll find the original, old file.
UE_LOG(LogPakFile, Verbose, TEXT("Delete Record: Accepted a delete record for %s"), Filename);
return false;
}

FPakFile::EFindResult FindResult = Paks[PakIndex].PakFile->Find(*StandardFilename, OutEntry);
if (FindResult == FPakFile::EFindResult::Found)
{
if (OutPakFile != NULL)
{
*OutPakFile = Paks[PakIndex].PakFile;
}
UE_CLOG(DeletedReadOrder != -1, LogPakFile, Verbose, TEXT("Delete Record: Ignored delete record for %s - found it in %s instead (asset was moved between chunks)"), Filename, *Paks[PakIndex].PakFile->GetFilename());
return true;
}
else if (FindResult == FPakFile::EFindResult::FoundDeleted)
{
DeletedReadOrder = PakReadOrder;
UE_LOG(LogPakFile, Verbose, TEXT("Delete Record: Found a delete record for %s in %s"), Filename, *Paks[PakIndex].PakFile->GetFilename());
}
}

UE_CLOG(DeletedReadOrder != -1, LogPakFile, Warning, TEXT("Delete Record: No lower priority pak files looking for %s. (maybe not downloaded?)"), Filename);
return false;
}

When we read a file from a Pak, it calls the Find function across all mounted Paks, and the FPakFile::Find function implements the logic I just described:

1
2
3
4
5
6
7
8
9
10
11
// Runtime/PakFile/Private/IPlatformFilePak.cpp
FPakFile::EFindResult FPakFile::Find(const FString& Filename, FPakEntry* OutEntry) const
{
QUICK_SCOPE_CYCLE_COUNTER(PakFileFind);
if (Filename.StartsWith(MountPoint))
{
FString Path(FPaths::GetPath(Filename));
// ...
}
// ...
}

Therefore, the role of the MountPoint is to first determine if the file’s path matches the foundational path of all files in the Pak during file lookup. If it doesn’t exist, the process will not proceed further.

Pak Fails to Mount

In the main package, if signature is enabled, the resulting Pak will fail to mount.
The same signature error occurs due to the absence of the corresponding .sig file for the pak.
The log looks like this:

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

This occurs because bEnablePakSigning in Project Setting - Crypto was set to true when creating the primary package, which will enforce validation on all pak files within the created package to ensure that only pak files packaged by oneself can be loaded.

The related code handling is in:

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);
}
}

Therefore, if the pak created using HotPatcher does not specify the same encryption parameters as the project, the pak placed within the package will fail to load (due to validation failure).
The solution is to specify the same encryption information in HotPatcher as in the project. When directly using UE to create the primary package, a Crypto.json file will be generated by default in the following path:

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

Its content is generated based on the options in Project Setting - Crypto.
The method to use is:
Add the parameter -cryptokeys="Crypto.json" to the UnrealPak parameters in HotPatcher (in UE4.23+ also add the -sign parameter):

Regenerating the Pak will create a corresponding .sig file named after the Pak in the Pak directory, and copying both the pak and sig files to the mounting directory will work.

The UnrealPak parameters can be found in my previous article: UE4 Toolchain Configuration and Development Tips # Parameters of UnrealPak

Pak master signature table check failed for pak

  1. The pak generated by HotPatcher crashes when mounted, with the message Pak master signature table check failed for pak.

This is due to the signing encryption set in the project settings when creating the primary package, and identical encryption parameters must be added in the UnrealPak parameters of HotPatcher.

In IPlatformFilePak.cpp within RegisterPakFile, checks are also performed:

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;
}

iOS Hot Update metallib Issue

In 4.25, there is an issue where shader bytecode is not reloaded. The engine internally handles the loading of metallib in a separate process and cannot reuse the shader bytecode process. Consequently, for iOS packages, it is advisable to use remote packaging, which will generate ushaderbytecode. In 4.25 the LoadLibrary doesn’t show issues, but loading metallib does.

You can refer to my previous article on UE hot update regarding shaders: UE Hot Update: Create Shader Patch

UE4.25+ ShaderPatch Crash

This issue arises due to a bug in the engine 4.25+, and you can find a modification plan in this article: UE Hot Update: Create Shader Patch#4.25+ ShaderPatch Crash.

Hot Updating Resources Not Present in a Plugin

After packaging, the engine will read the current project’s plugin names from the upluginmanifest. When loading resources from plugins, it checks for the existence of the plugin to achieve a coarse filtering effect.

Thus, if trying to package a plugin that doesn’t exist in the base package into the pak, you must synchronize the project’s upluginmanifest file during resource packaging, mounted at:

1
../../../PROJECT_NAME/Plugins/PROJECT_NAME.upluginmanifest

For more information on upluginmanifest, you can refer to my previous notes: UE4#upluginmanifest.

Hot Update Resources Not Taking Effect

If the hot updated Blueprints logic remains unchanged, check whether the resources have been cooked. You can manually execute cooking on selected resources via the features provided in HotPatcher in the Content Browser. Alternatively, ensure the bCookAsset option is checked when packaging the patch.

Hot Update Material Loss

If new resources/materials have been hot updated and they have no effect, check whether shader bytecode has been packaged. Failing to package shader bytecode for newly added materials will result in the engine using the default material due to shader retrieval failures.

The log error would be:

If you used bSharedShaderLibrary during packaging, you must manually reload the shader bytecode after mounting pak files that contain new shader bytecode, allowing the engine to retrieve the latest Shader when loading materials:

1
2
3
#include "ShaderCodeLibrary.h"

FShaderCodeLibrary::OpenLibrary("NewShaderLib", FPaths::ProjectContentDir());

You should call this according to the actual path and naming of the ShaderLib after mounting.

If bSharedShaderLibrary was not enabled, no further action is required, as the engine will default to using the inline shader code within the resource.

Is Hot Updating AssetRegistry Necessary?

It depends on your needs. If the code at runtime references resource relationships or checks for resource existence using the AssetRegistry module, then it needs to be hot updated. However, the AssetRegistry is not essential for the engine, and if you’re sure it won’t be used at runtime, you can remove it, which will save some memory.

For more specific information, you can refer to my previous notes: UE4#Control AssetRegistry Serialization.

Android Prompts Not Found uproject

There’s a bug in UE that can be reproduced in version 4.25.1 with the following steps:

  1. Install the apk and launch the game for the first time.
  2. Open UE’s sandbox data directory UE4Game/PROJECTNAME and create a Content/Paks directory in this location.
  3. Restart the game.

The log also mentions Project file not found: ../../../FGame/FGame.uproject.

Pak files that automatically mount on Android can be placed in the Saved/Paks directory. I will analyze this issue in detail when I have time.

Control Resources Not Packed in Base Package

Practices on splitting base packages can be found in my two articles:

Analyze Resources in the Package of a Specific Platform

You can use the Asset Audit tool provided by UE. Be sure to back up the DevelopmentAssetRegistry.bin file in the Cooked/PLATFORM/PROJECT_NAME/Metadata directory each time packaging.

Alternatively, you can use UnrealPakViewer to directly load Pak files.

You can refer to the asset audit section in this article: UE Hot Update: Asset Management and Audit Tool#Asset Audit.

UMG Child Widgets Hot Update Not Effective

If UMG is referenced in an instanced form, changes to child UMG must recursively include all UMG resources referenced in the form of child controls. I previously recorded this issue in my notes: UE4#UMG Child Widget Reference Hot Update Issue.

Solution: HotPatcher includes an option to recursively analyze the UMG parent widget (bRecursiveWidgetTree), which can be enabled.

A detailed analysis of this issue was presented in my 2020 UOD speech, and you can watch the video and view the PPT here:

Note: Instanced UMG only affects the serialization of controls and does not relate to logical changes within child widgets.

In the following scenario, only packaging a child widget UMG will be effective:

  1. UMG_Child has a Button associated with a Button event that outputs TEST01.
  2. UMG_Main embeds UMG_Child.
  3. A version is built.
  4. Modify the output of the Button event in UMG_Child to TEST02.
  5. Only package UMG_Child.
  6. Create UMG_Main and click the Button in its UMG_Child to correctly output TEST02.

Can Pak be Used Across Engine Versions?

No, the engine versions for packaging and usage must be consistent.

Pak Packaged from Project A for Use in Project B

HotPatcher has implemented a functionality to replace the pak command, which can be specified with the following parameters:

Note: Both From and To must include a ../../../ prefix; otherwise, the absolute path of the file will be replaced.

Plugin HotPatcher Failed

After packaging, the following prompt appears:

This may be caused by packaging a purely Blueprint project. Create a C++ class in the project, transforming it into a C++ project and repackage it.

Packaging Original uasset Resources

Currently, there is no direct feature in the plugin to select original uasset resources for packaging, but you can implement it using a clever workaround.
You can set ReplacePakCommandTexts to replace the Cooked directory with the project’s Content directory:

Although the *pakcommand.txt will still contain records of uexp files, they won’t be available in the project’s Content, meaning they won’t be packed into the pak, skipping nonexistent files and producing the following log output:

1
LogPakFile: Warning: Missing file "D:/Client/Content/Assets/Scene/Map/LookDev/DemoAssets/Mesh/FFXV/000.uexp" will not be added to PAK file.

This is a clever approach, but it works.

Export Cross-Machine Universal Configuration File

Q: Some configurations exported from HotPatcher depend on absolute file paths, such as BaseVersion, Non-Asset files, SavePath, etc. These absolute paths may not be consistent across different machines. Can configurations be based on relative paths?

A: Yes. All configurable items in HotPatcher can replace absolute paths with marked symbols, and the following symbols can be used to replace absolute paths.

1
2
3
4
5
6
[ENGINEDIR]
[ENGINE_CONTENT_DIR]
[PROJECTDIR]
[PROJECT_CONTENT_DIR]
[PROJECT_SAVED_DIR]
[PROJECT_CONFIG_DIR]

During packaging, these will automatically be replaced with the absolute path of the current machine, making the configuration file fully universal.

Analysis of Dependency Time Consumption

When a project has a considerable amount of resources, the dependency analysis provided by the plugin can consume substantial time. Its primary purpose is to analyze dependent resources to prevent dependency on resources that haven’t been packaged.

If dependencies on engine or plugin resources exist, not conducting dependency analysis will result in mismanagement (or manually specifying), and if you wish to exclude unreferenced resources, it cannot be accomplished without analysis. If not needed, you can disable it by setting bAnalysisFilterDependencies.

This will limit the analysis to only the resources specified in the configuration and those individually designated, comparing them with the base version. This can significantly reduce the time consumed in dependency analysis (if your resources are numerously substantial and sure all resource dependencies reside in /Game, you can skip enabling dependency analysis).

Memory Mapped File Has the Wrong Alignment

Note: The latest plugin has already been adapted to be supported, and no manual specifications are required.

The following crash occurs in the iOS package:

1
IsAligned(MappedRegion->GetMappedPtr(), FPlatformProperties::GetMemoryMappingAlignment()), TEXT("Memory mapped file has the wrong alignment!")

This is caused because the alignformemorymapping value was not set when packaging for iOS, which defaults to 0. However, for iOS, it should be set to 16384:

1
2
3
4
5
Engine/Source/Runtime/Core/Public/IOS/IOSPlatformProperties.h
static FORCEINLINE int64 GetMemoryMappingAlignment()
{
return 16384;
}

Specify this parameter during pak packaging: -alignformemorymapping=16384.

During default UE packaging, this parameter has also been added:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Source\Programs\AutomationTool\Scripts\CopyBuildToStagingDirectory.Automation.cs
private static void CreatePaks(ProjectParams Params, DeploymentContext SC, List<CreatePakParams> PakParamsList, EncryptionAndSigning.CryptoSettings CryptoSettings, FileReference CryptoKeysCacheFilename)
{
// ...
string BulkOption = "";
ConfigHierarchy PlatformEngineConfig = null;
if (Params.EngineConfigs.TryGetValue(SC.StageTargetPlatform.PlatformType, out PlatformEngineConfig))
{
bool bMasterEnable = false;
PlatformEngineConfig.GetBool("MemoryMappedFiles", "MasterEnable", out bMasterEnable);
if (bMasterEnable)
{
int Value = 0;
PlatformEngineConfig.GetInt32("MemoryMappedFiles", "Alignment", out Value);
if (Value > 0)
{
BulkOption = String.Format(" -AlignForMemoryMapping={0}", Value);
}
}
}
//...
}

It reads from the MemoryMappedFiles Section of Config/PLATFORM/*Engine.ini, where the Alignment value is specified, such as for iOS:

1
2
3
4
Config/IOS/IOSEngine.ini
[MemoryMappedFiles]
MasterEnable=true
Alignment=16384

This value corresponds to the one defined in the code and is the only platform for iOS that specifies this item: MemoryMappedFiles.

Missing Some Plugin Resources in Release

Please ensure that the plugin path name matches exactly with the plugin name. For example:

1
Plugins\HDiffPatchUE4\HDiffPatch.uplugin

This situation is not allowed; the path must match the plugin name exactly.

Resource Packaging and Real Machine Load Process in the Editor

As in-game resources increase in number, the package size grows, but mobile package sizes have limits and cannot indefinitely include all resources in the base package. The Android limit is 2G (you can enable Allow Large Obb files to support 4G), and the iOS limit for submission to the App Store is usually 4G, which typically maintains uniform resource allocation across iOS/Android basic packages, with the remaining resources updated to devices through hot updates or dynamic downloads.

So, effectively in the development phase, resources packed in the basic package are merely a part of the project. Often, satisfying the verification needs of planners, artists, and testers can prove difficult.

  1. New maps and resources need to be viewed on actual devices
  2. Issues with resources in the package; submission waits for lengthy full package build periods
  3. Base package resource volumes exceed limits, which may prevent additional resource additions

Based on this necessity, a basic package + patch form can be utilized during the development phase.

Resource Packaging

In our current project, within the editor, you can right-click directly on the resource or directory and select Cook And Pak Actions, choose the corresponding platform, and also select AnalysisDependencies (Analyze Dependencies).

Different platforms require different choices (select based on your project’s needs):

  • Android: Android_ASTC
  • iOS: iOS
  • PCVersion: WindowsNoEditor

After execution, a prompt will appear at the bottom right:

You need to wait until the execution is complete. This process will not freeze the editor; other editing operations can continue.

The duration of execution depends on the number of referenced resources and whether the selected resources have been packaged previously (Data Driven Content).

After execution, a corresponding .pak file will be created in the project’s Saved/HotPatcher/Paks/{EXECUTE_TIME}/Android_ASTC directory. This pak file is the patch for the packaged resources, with the file name including the platform name. The pak files for different platforms cannot be mixed.

Placement in Real Machines

PCVersion

For PCVersion, place the pak file in the following directory:

1
WindowsNoEditor\PROJECT_NAME\Content\Paks

If the Paks directory doesn’t exist, you may manually create it.

Android

On Android, the pak file needs to be placed in the following directory:

1
UE4Game/PROJECT_NAME/PROJECT_NAME/Saved/Paks

If the data directory has been modified to the sandbox path, it will be:

1
Android/data/com.xxxx.yyyy/files/UE4Game/PROJECT_NAME/PROJECT_NAME/Saved/Paks

If the Paks directory doesn’t exist, you may manually create one.

iOS

The iOS process is more intricate; you need to use tools like iCareFone or iMazing to transfer the files. Place it in Documents - PROJECT_NAME - Saved - Paks. If the Paks directory doesn’t exist, you may manually create it.

Check Mounting Status

After placing the pak file in the above directory, you can start the game.

To confirm whether the pak file is effective, you can check the Log:

1
2
LogPakFile: Display: Found Pak file ../../../PROJECT_NAME/Saved/Paks/2022.10.18-13.02.53_IOS_001_P.pak attempting to mount.
LogPakFile: Display: Mounting pak file ../../../PROJECT_NAME/Saved/Paks/2022.10.18-13.02.53_IOS_001_P.pak.

If you see such Log, it means the resource package has been successfully read by the engine and can be used directly, just like it was included in the base package.### Notes

The priority of manually packing the Pak is higher than that of the resources in the base package, which means if a resource exists in both the patch package and the base package, it will replace the one in the base package, similar to hot update logic.

  1. Different platform pak files cannot be mixed!
  2. After testing, you need to delete the pak files from the device; otherwise, newly created packages may have resource display errors.

Troubleshooting UE5+

Note: Starting from v82.0, it is compatible with UE5.2 and 5.3.

If you find that the packaged Pak has issues in UE5:

  1. Pak cannot mount: Check if the project has IoStore enabled. After disabling it in project settings, repackage the base package.
本篇文章的内容会持续更新。

Scan the QR code on WeChat and follow me.

Title:UE Hot update: Questions & Answers
Author:LIPENGZHA
Publish Date:2021/03/12 10:45
Word Count:16k Words
Link:https://en.imzlp.com/posts/16895/
License: CC BY-NC-SA 4.0
Reprinting of the full article is prohibited.
Your donation will encourage me to keep creating!