How to override and refresh loaded asset at runtime by mounting pak file manually?

What is the goal

I am working on a demo of replacing textures by loading pak at runtime as a phased verification of a larger part of my work

The assets in the game are divided into two packages. The texture assets in the main pak package A.uasset are called the old version. The texture assets in the pack pak package A.uasset are called the new version. They are in the same path under the mount point and have the same name. I hope that the game does not need the pack pak file when it starts, but downloads the pack pak file later to replace the texture. So, I need to load the pak file manually and implement something to update the texture.

What is the problem

After loading the backup pak during game runtime, the mount method returns true, indicating that the mount is successful. The priority is also set to 1000, and AssetRegistry.ScanPathsSynchronous is also called, but the texture is still not refreshed to the content in the backup pak

However, if the backup pak is renamed to _P.pak before loading the game for the first time and placed in the directory where the main pak is located, that is, the two are at the same level, then according to the mechanism already written by UE, the backup pak can be loaded first. At this time, the image asset of the game when it is started is the content in the backup pak.

This also shows that there is no problem with my backup pak. It is simply a problem of loading method.

There are two possibilities

  1. After the texture asset has been loaded, no matter how high the priority of the new pak file is when mounting, as long as the texture asset path is the same, the loaded asset will no longer be replaced.

  2. Because the image resources have been loaded into memory, although the assets of the new pak have been loaded, this loading only updates the index structure and does not update the memory. Therefore, scanning the AssetRegistry is useless.

What I have try

I have read UE docs about loading pak file:

Primer: Loading Content and Pak Files at Runtime | Epic Developer Community

The docs lets me know about AsssetRegistry, but that is not enough.

I have read others’ question:

How to load assets from a pak file at runtime? - Development / Rendering - Epic Developer Community Forums

The question is about loading a new object that haved be instanced or loaded. But my question is how to load newer version asset from new pak, to replace older version of asset. The two version of asset have same path and same name.

How do I debugging

Package the game as I say, call UnrealPAK to gain my desired main.pak and pack.pak

Replace original pak file by my main.pak, then start packaged game.

When game is running, I call my custom pak loading function. Parameters are: PakFilename is the absolute path of pack.pak, PakOrder is 1000, MountPoint is \Game\. My desired asset to replace is definitely in /Game/, because I have checked in editor.

Edit: \Game\ is error

Now I wish the texture I loaded is overrided and refreshed, but it didn’t.

My coding

#include "PakBlueprintLibrary.h"

#include "IPlatformFilePak.h"
#include "AssetRegistry/AssetRegistryModule.h"

bool UPakBlueprintLibrary::LoadPakFile(const FString& PakFilename, int PakOrder, const FString& MountPoint)
{
	IPlatformFile& InnerPlatform = FPlatformFileManager::Get().GetPlatformFile();
	FPakPlatformFile* PakPlatformFile = new FPakPlatformFile();
	PakPlatformFile->Initialize(&InnerPlatform, TEXT(""));
	FPlatformFileManager::Get().SetPlatformFile(*PakPlatformFile);
	
	FPakFile* Pak = new FPakFile(&InnerPlatform, *PakFilename, false);
	if (!Pak->IsValid())
	{
		return false;
	}

	if(!PakPlatformFile->Mount(*PakFilename, PakOrder, *MountPoint))
	{
		return false;
	}

	FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
	IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();
	AssetRegistry.ScanPathsSynchronous({MountPoint}, true);

	return true;
}

Platform

OS: Windows11

UE: 4.27.2

Anyone knows some information? Or some guidance about which part of UE source code should I investiagte? Thanks!

1 Like

Another problem appear: I try to load blueprint after mounting pack pak file and fail.

My loading process can run in PIE mode, can run in packaged mode when having main pak and pack pak together. Error only happens when I launch game with main pak only and try to mount pack pak file.

It shows that the problem of texture doesn’t refresh is hidden. Now the biggest problem is, why I can’t load blueprint when pak is mounted successfully.

My loading blueprint code is

bool UPakBlueprintLibrary::SpawnActorFromBPPath(const FString& BPPath, UObject* WorldContextObject, AActor*& OutActor)
{
	OutActor = nullptr;

	if (!WorldContextObject)
	{
		UE_LOG(LogTemp, Warning, TEXT("SpawnActorFromBPPath: WorldContextObject is null"));
		return false;
	}

	UWorld* World = WorldContextObject->GetWorld();
	if (!World)
	{
		UE_LOG(LogTemp, Warning, TEXT("SpawnActorFromBPPath: Cannot get UWorld from WorldContextObject"));
		return false;
	}

	// Load the Blueprint class
	UClass* BPClass = LoadObject<UClass>(nullptr, *BPPath);
	if (!BPClass)
	{
		UE_LOG(LogTemp, Warning, TEXT("SpawnActorFromBPPath: Failed to load BP class at path: %s"), *BPPath);
		return false;
	}

	// Spawn parameters
	FActorSpawnParameters SpawnParams;
	SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
	
	AActor* NewActor = World->SpawnActor<AActor>(BPClass, FVector::ZeroVector, FRotator::ZeroRotator, SpawnParams);
	if (!NewActor)
	{
		UE_LOG(LogTemp, Warning, TEXT("SpawnActorFromBPPath: Failed to spawn actor from BP class"));
		return false;
	}

	OutActor = NewActor;
	return true;
}

Fine, the problem is my mount point parameter is error.
My pack pak file has same folder structure of main pak.
And main pak is mounted when game initialize.
I add break point when game initialize and then see FPakPlatformFile::MountAllPakFiles is loading the initial pak.
And FPakPlatformFile::MountAllPakFiles use empty mount point.
So I follow the same empty mount point to mount my pack pak, then I can load blueprint class from pack pak successfully.

But the question of replacing loaded asset is still here.

Essentially this.
Assets (packages) are loaded from disk (=PAK) into memory, and stay in memory until unloaded. Adding a PAK with higher priority will only override assets that need to be loaded from disk AFTER mounting the new pak. On startup, all available paks are mounted at once, before loading assets, so when assets are loaded they are automagically retrieved from the higher priority pak.
For what you want to do, it seems you’d have to manually patch everything on the fly, which I’m not sure is even possible for static level actors/geometry. Better course of action might be to write a solid savegame system, reload the level, and restore state.

1 Like

Thanks!
However, what I want is that the level does not reload, only individual assets, is this possible?
Theoretically, an object has a loading process when the game starts, which must have some function calls. If I call these functions to load objects again at runtime and require reading from the updated pak file system this time, can I update resources at runtime?
I am still looking at the UE source code, but I am worried that I can’t find such a loading function.

Temporarily, I only wish to replace texture at runtime. It looks simple because it will not be modified by runtime gameplay logic.

Static level objects are deserialized from the level (umap), so reloading them is essentially reloading the level. Dynamic objects are spawned by gameplay code and yes, you could replace them by destroying and respawning them. However you will lose all runtime state/changes.

In that case yes it may be possible to work out a simpler system. Look into function LoadReplacementPackage from PackageReload.cpp, it is not exported but it has a whole lot of interesting logic in it. Renaming the old package and triggering a load (StaticLoadObject or such) of the old path should make it load from disk again (fetching from new pak). Then it’s a matter of iterating all textureable objects (UMeshComponent subclasses for the most part) and re-assigning their materials.

Maybe you can simply use function ReloadPackages from PackageReload.h which is exported. If you are in luck, it could just do all the job for you. I haven’t dug that far in to see what it does exactly, but would be interesting to know the results.

1 Like

A direction is valuable, thank you!

I accomplish my goal with ReloadPackages. Thank you so much!

Nice!
I’m curious, does it just do all the replacements automatically for you ? If so, that’s huge

Yes, it “do all the replacements automatically”.
More clearly, it iterates all UObjects and replace old package reference with new package reference, meanwhile, deserialize the new data from new package to UObject.
It maybe huge, but the performance analyzation is late work :stuck_out_tongue: