Loading content/maps at runtime from a .pak file?

I’m hoping someone will be able to explain loading content at runtime to me. I’ve dug endlessly over the last few days at the very scant documentation and Answer Hub questions about this, and nothing I try is able to work. As of right now, I made a .pak file by creating a new project, a new blank level, dropping a single static mesh into the assets and level, then packaging it out. Then I ran UnrealPak.exe with the following -create= manifest:


"D:\Unreal\Projects\PackageBuilder\Saved\Cooked\WindowsNoEditor\PackageBuilder\Content\dronepackmaterial.uasset" "../../../PackageBuilder/Content/dronepackmaterial.uasset"
"D:\Unreal\Projects\PackageBuilder\Saved\Cooked\WindowsNoEditor\PackageBuilder\Content\dronepackmesh.uasset" "../../../PackageBuilder/Content/dronepackmesh.uasset"
"D:\Unreal\Projects\PackageBuilder\Saved\Cooked\WindowsNoEditor\PackageBuilder\Content\emptymap.umap" "../../../PackageBuilder/Content/emptymap.umap"


This created a “testpak.pak” file which I am trying to load at runtime. To do this, I have created a plugin, which contains a class “ULevelLoaderObject”


UCLASS(ClassGroup="Level Loading Plugin", meta = (BlueprintSpawnableComponent))
class ULevelLoaderObject : public UObject
{
	GENERATED_UCLASS_BODY()

public:
	UFUNCTION(BlueprintCallable, Category = "Level Loading Plugin", meta = (WorldContext = "WorldContextObject"))
		static bool LoadLevelFromPak(UObject* WorldContextObject, const FString pakPath, UStaticMesh*& LoadedStaticMesh);

	UFUNCTION(BlueprintCallable, Category = "Level Loading Plugin", meta = (WorldContext = "WorldContextObject"))
		static bool AlternateLoadLevelFromPak(UObject* WorldContextObject, const FString pakPath, UStaticMesh*& LoadedStaticMesh);


This being static allows me to call it from my level blueprint and have a world context available to operate on GEngine/World.

So I have attempted two ways to do this. One is “LevelLoadFromPak” which is…


bool ULevelLoaderObject::LoadLevelFromPak(UObject* WorldContextObject, const FString pakPath, UStaticMesh*& LoadedStaticMesh)
{
	UWorld* World = WorldContextObject->GetWorld();

	bool bSuccessfulInitialization = false;

	IPlatformFile* LocalPlatformFile = &FPlatformFileManager::Get().GetPlatformFile();

	if (LocalPlatformFile != nullptr)
	{
		IPlatformFile* PakPlatformFile = FPlatformFileManager::Get().GetPlatformFile(TEXT("PakFile"));
		if (PakPlatformFile != nullptr)
			bSuccessfulInitialization = true;
	}

	if (bSuccessfulInitialization)
	{
		const TCHAR* cmdLine = TEXT("");
		FPakPlatformFile* PakPlatform = new FPakPlatformFile();
		IPlatformFile* InnerPlatform = LocalPlatformFile;
		PakPlatform->Initialize(InnerPlatform, cmdLine);
		FPlatformFileManager::Get().SetPlatformFile(*PakPlatform);

		FString FilePath = pakPath;

		UE_LOG(LevelPluginLog, Log, TEXT("Attempting to load pak: %s"), *FilePath);

		FPakFile PakFile(*FilePath, false);

		if (!PakFile.IsValid())
		{
			UE_LOG(LevelPluginLog, Error, TEXT("Invalid pak file: %s"), *FilePath);
			return false;
		}

		FString MountPoint(TEXT("/Engine/"));

		PakFile.SetMountPoint(*MountPoint);

		const int32 PakOrder = 0;
		if (!PakPlatform->Mount(*FilePath, PakOrder, *MountPoint))
		{
			UE_LOG(LevelPluginLog, Error, TEXT("Failed to mount pak file: %s"), *FilePath);
			return false;
		}


		TArray<FString> AssetFileNames;
		PakFile.FindFilesAtPath(AssetFileNames, *MountPoint);

		for (const FString& AssetName : AssetFileNames)
		{
			UE_LOG(LevelPluginLog, Log, TEXT("Asset Found: %s"), *AssetName);
		}
		
		FString meshFilePath = MountPoint + TEXT("dronepackmesh.dronepackmesh");
		FString meshFileContent;
		FFileHelper::LoadFileToString(meshFileContent, *meshFilePath);
		UE_LOG(LevelPluginLog, Log, TEXT("Path: %s, Content: %s"), *meshFilePath, *meshFileContent);

		FStreamableManager StreamableManager;

		FString assetName(meshFilePath);
		FStringAssetReference AssetRef(assetName);
		Cast<UStaticMesh>(StaticLoadObject(UStaticMesh::StaticClass(), nullptr, *assetName));
		UObject* object = StreamableManager.SynchronousLoad(AssetRef);

		return true;
	}
  return false;
}


I’m simply hard coding my values right now just to try to get anything functional. What happens in this scenario is output to my log

LevelPluginLog: Attempting to load pak: D:/Unreal/testpak.pak
LevelPluginLog: Asset Found: /Engine/dronepackmaterial.uasset
LevelPluginLog: Asset Found: /Engine/dronepackmesh.uasset
LevelPluginLog: Asset Found: /Engine/emptymap.umap
LevelPluginLog: Path: /Engine/dronepackmesh.dronepackmesh, Content:
LogLinker:Warning: Can’t find file ‘/Engine/dronepackmesh’
LogLinker:Warning: Can’t find file ‘/Engine/dronepackmesh’
LogUObjectGlobals:Warning: Failed to find object ‘StaticMesh /Engine/dronepackmesh.dronepackmesh’
LogLinker:Warning: Can’t find file ‘/Engine/dronepackmesh’
LogUObjectGlobals:Warning: Failed to find object ‘Object /Engine/dronepackmesh.dronepackmesh’

I’ve tried this as well with dronepackmesh.uasset which results in the same.

I also found references to people saying “OH don’t go through all that process, just use FCoreDelegates::OnMountPak.Execute instead!”. But no one really explains what to do after you have mounted this. The previous way, I have a PakFile that I can operate on to get a list of assets, but if I just OnMountPak, I cannot find anyway to actually get a list of assets in the loaded pak to get references to use.

My function “AlternateLoadLevelFromPak” is attempting to use OnMountPak:


bool ULevelLoaderObject::AlternateLoadLevelFromPak(UObject* WorldContextObject, const FString pakPath, UStaticMesh*& LoadedStaticMesh)
{
	UWorld* World = WorldContextObject->GetWorld();

	UE_LOG(LevelPluginLog, Log, TEXT("Game Content Dir: %s"), *FPaths::GameContentDir());

	if (FCoreDelegates::OnMountPak.IsBound())
	{
		if (FCoreDelegates::OnMountPak.Execute(*pakPath, 0))
		{
			FStreamableManager AssetLoader;
			FStringAssetReference AssetRef(TEXT("/Engine/dronepackmesh.dronepackmesh"));
			LoadedStaticMesh = Cast<UStaticMesh>(AssetLoader.SynchronousLoad(AssetRef));

			AStaticMeshActor* actor = World->SpawnActor<AStaticMeshActor>(FVector::ZeroVector, FRotator::ZeroRotator);
			actor->GetStaticMeshComponent()->Mobility = EComponentMobility::Movable;
			actor->GetStaticMeshComponent()->SetStaticMesh(LoadedStaticMesh);

			UE_LOG(LevelPluginLog, Log, TEXT("Pak successfully mounted"));
		}
		else
			UE_LOG(LevelPluginLog, Error, TEXT("Pak failed to mount"));
	}
	else
		UE_LOG(LevelPluginLog, Error, TEXT("OnMountPak is not bound"));

	return true;
}

This again results in:

LevelPluginLog: Game Content Dir: …/…/…/…/…/Projects/BasicProject/Content/
LogLinker:Warning: Can’t find file ‘/Engine/dronepackmesh’
LogLinker:Warning: Can’t find file ‘/Engine/dronepackmesh’
LogUObjectGlobals:Warning: Failed to find object ‘Object /Engine/dronepackmesh.dronepackmesh’
LogStreamableManager: Failed attempt to load /Engine/dronepackmesh.dronepackmesh

Apologies for the massive wall of text, but I am just simply stumped and cannot find myself able to piece together the fragments of knowledge scattered around. Most of the posts either end with no answer, or someone just saying “Oh I fixed it.” I’m trying to get a better grasp of how this all works and what exactly I am doing wrong. All I am trying to do is take a .pak file at runtime and load a map from it. I feel like this should be a non-issue with various games utilizing DLC mechanics, and I MUST be missing something super obvious. The idea for this is that we, as the developer, or even other users, will be able to just create a level utilizing their own assets, package it up, and deploy it for other users to load into their game to play with our existing GameMode/player blueprints. I can’t even get an asset loaded, let alone a map :frowning:

As a bit of additional bonus information, I verified using the OnMountPak method that I am able to mount the pak file successfully. Removing the load attempt and just going with:


	if (FCoreDelegates::OnMountPak.IsBound())
	{
		if (FCoreDelegates::OnMountPak.Execute(*pakPath, 0))
		{
			FPakFile* PakOutFile = nullptr;

			const FPakEntry* pakEntry = PakPlatformFile->FindFileInPakFiles(TEXT("../../../PackageBuilder/Content/dronepackmesh.uasset"), &PakOutFile);

provides me with a valid FPakEntry (non null). “…/…/…/PackageBuilder/Content” was the location of the content in the new project I made just to create the package. This is also the MountPoint that is in the pak file loaded by OnMountPak.

Attempting to load using this known good file path is still non functional. Either through a direct StaticObjectLoad or a SynchronousLoad as above, both are unable to correctly find the asset.

How are the asset string references supposed to be generated here so that the loading system is able to use it? They don’t seem to just take a file path, like I would use to get the pakEntry.

nice question. want to know too.

Hi,

i have the same problem. I create a .pak-file with a single sphere via command line and want to load it from a local path. With the command line I tested and extracted the .pak-file. Everything seems to be good with this file but, I cannot load the file in runtime. Maybe somebody can help?

My Output:
LogTemp:Error: VALID pak file: C:/Users/Nils/Desktop/out.pak
LogTemp:Error: Number of assets = 1
LogTemp:Error: Find asset = …/…/…/Engine/Content/Sphere.uasset
LogUObjectGlobals:Warning: Failed to find object ‘Object None…/…/…/Engine/Content/Sphere.uasset’
LogStreamableManager: Failed attempt to load …/…/…/Engine/Content/Sphere.uasset

My Code:
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
FPakPlatformFile* PakPlatform = new FPakPlatformFile();
PakPlatform->Initialize(&PlatformFile, TEXT(“”));
FPlatformFileManager::Get().SetPlatformFile( *PakPlatform );

 const FString PakFileName = TEXT("C:/Users/Nils/Desktop/out.pak");

 FPakFile PakFile(*PakFileName, false);

 if (!PakFile.IsValid())
     UE_LOG(LogTemp, Error, TEXT("INVALID pak file: %s"), *PakFileName);
 if (PakFile.IsValid())
     UE_LOG(LogTemp, Error, TEXT("VALID pak file: %s"), *PakFileName);

 PakFile.SetMountPoint(*FPaths::EngineContentDir());

 const int32 PakOrder = 0;
 if ( !PakPlatform-&gt;Mount(*PakFileName, PakOrder, *FPaths::EngineContentDir()))
     UE_LOG(LogTemp, Error, TEXT("Failed to mount pak file: %s"), *PakFileName);

 TSet&lt;FString&gt; FileList;
 PakFile.FindFilesAtPath(FileList, *PakFile.GetMountPoint(), true, false, true);

 UE_LOG(LogTemp, Error, TEXT("Number of assets = %ld"), FileList.Num());
 
 StreamedAssets.Empty();
 for (TSet&lt;FString&gt;::TConstIterator FileItem(FileList); FileItem; ++FileItem)
 {
     FString AssetName = *FileItem;
     UE_LOG(LogTemp, Error, TEXT("Find asset = %s"), *AssetName);

     if (AssetName.EndsWith(FPackageName::GetAssetPackageExtension()) || AssetName.EndsWith(FPackageName::GetMapPackageExtension()))
         StreamedAssets.Add(AssetName);
 }

 UObject *object = Cast&lt;UStaticMesh&gt;( AssetLoader-&gt;SynchronousLoad(StreamedAssets[0]));