# Knowledge Base: Primer: Loading Content and Pak Files at Runtime

**URL:** <https://forums.unrealengine.com/t/knowledge-base-primer-loading-content-and-pak-files-at-runtime/536669>\
**Category:** Platform & Builds\
**Tags:** unreal-engine\
**Created:** [April 22, 2022, 12:27pm UTC](https://forums.unrealengine.com/t/knowledge-base-primer-loading-content-and-pak-files-at-runtime/536669 "2022-04-22T12:27:03Z")\
**Posts on this page:** 1\
**Showing post:** 7

<div class="post-metadata">

**Author:** ![Chatouille](https://d1ap1mz92jnks1.cloudfront.net/user_avatar/forums.unrealengine.com/chatouille/32/1194622_2.png) [@Chatouille](https://forums.unrealengine.com/u/Chatouille)\
**Post date:** [November 7, 2023, 5:22pm UTC](https://forums.unrealengine.com/t/knowledge-base-primer-loading-content-and-pak-files-at-runtime/536669/7 "2023-11-07T17:22:49Z")

</div>

I’ve been experimenting around this for mod support considerations.

My goal was to set up a BP project that supports mods, relying entirely on default behavior of plugin manager & asset registry, with no C++ involved.

I finally got it to work properly, after running into a few traps and working them out, which I’m gonna document below. This was on UE 5.1

Note that for UE4 projects, a BP-only implementation will be severely restricted due to limited asset registry methods. More precisely, UE5 provides a node `GetBlueprintAssets` which is designed to find blueprint subclasses, and the ability to cast an UObject to UClass which is necessary after sync/async loading a BP Asset. UE4 blueprints come short on both aspects.

* * *

# Basegame Setup

* * *

I made a base game project, with a stub blueprint base class (BP\_Base), a basic level and a material for it.  
Then I created BP\_Spawner blueprint which uses asset registry to find all blueprints child of BP\_Base, and spawn them. This is the spawner BP code :

 ![image](https://d3kjluh73b9h9o.cloudfront.net/original/4X/5/2/c/52c4858d1f571520d7162d1c0f29b68808d49751.png)

I added BP\_GameDerived as a child of BP\_Base, which doesn’t do much other than confirming my spawner works.  
Base game is packaged with default settings (minus IoStore, I haven’t experimented with that yet).  
UPDATE: Added a couple notes about IoStore below.

* * *

# Mod setup

* * *

Then I made another project in which I copied BP\_Base. This is intended to be the “SDK” for mod support.  
Using project launcher I packaged with a first profile “SDK” which will contain all “shared” assets, that are in the base game, and should not be cooked in mods. It will serve as the reference version :

 ![image](https://d3kjluh73b9h9o.cloudfront.net/original/4X/4/0/7/407caa8551e632bc432cba5d4830136d528020f0.png)  
also in Packaging settings :  
 ![image](https://d3kjluh73b9h9o.cloudfront.net/original/4X/f/1/e/f1e51ff1e792155f996884de22844d939d626694.png)

Then I created a new Content-Only plugin called Mod1, via the standard plugin creation window.  
In plugin contents I created a new material, and another child of BP\_Base (BP\_ModDerived).  
In the new class I set up a timer that selects all meshes and makes them blink between the new material and default material every second.

Using Project Launcher I set up second profile “Mod1” which in turn uses SDK as a reference version to package a DLC

 ![image](https://d3kjluh73b9h9o.cloudfront.net/original/4X/4/7/c/47c932cf21a8a4b3b797c0059626e5a245a160e0.png)  
_note: error in image, should be “SDK” instead of “DLC” in the field above_

After packaging, I can confirm the PAK file only contains intended content and does not conflict with any main game file :

```auto
Display: Mount point ../../../ModProject/Plugins/Mod1/
Display: "AssetRegistry.bin"
Display: "Content/BP_ModDerived.uasset"
Display: "Content/BP_ModDerived.uexp"
Display: "Content/M_Mat.uasset"
Display: "Content/M_Mat.uexp"

```

* * *

# Shipping

* * *

All of this was pretty straightforward. After this I ran into multiple “traps” before successfully getting the basegame to detect the mod (and its contents) properly.

The packaged game file structure looks like this

```auto
GameProject.exe
Engine/...
GameProject/
  - Binaries/...
  - Content/Paks/GameProject-Windows.pak

```

The packaged mod file structure looks like this

```auto
ModProject/
  - Plugins/
    - Mod1/
      - Mod1.uplugin
      - Content/Paks/Windows/Mod1ModProject-Windows.pak

```

There are a few things to note.

Assets in the mod pak lie at `../../../ModProject/Plugins/Mod1/Content/...` which means they can only be loaded if the base game mounts a custom path towards them. They are not in /Game or any other default virtual path. If you simply drop the PAK alongside main game’s PAK, it will be loaded, but its assets won’t be reachable.

It may cross your mind that, if your ModProject has the same name as GameProject, and you package mod assets in the /Game folder, then you can drop the mod PAK alongside main game PAK and the paths would work out. That is partially true. Assets may be reachable with a StaticLoadObject, but mod pak asset registry is still at `/Plugins/Mod1/AssetRegistry.bin` and will not be loaded. You might also want to try not using a plugin at all, so that the asset registry ends up in `/Game/AssetRegistry.bin`. In this case it will conflict with the main game’s asset registry and only one of them will be loaded, so this is not a good option.

PluginManager automatically mounts virtual paths for plugins contents, in the following form :  
`/PluginName` → `../../../GameProject/Plugins/PluginName/Content`  
So if we follow a proper plugin approach, we should get a proper path towards both the mod asset registry and its assets.

PluginManager only enables plugins specified in the `.upluginmanifest` file, which is generated and paked automatically for the basegame (not modifiable). However you can manually create additional .upluginmanifest files in the packaged GameProject/Plugins/ folder and they will be read as well.

Note: I tried to use the -DLCPakPluginFile switch to create a .upluginmanifest automatically as mentioned in the above primer, however I never managed to make it work.

PluginManager will also automatically load all plugins located in the GameProject/Mods/ folder, however when doing so it will expect asset paths in the Pak to match asset paths on disk. This removes the need to create .upluginmanifest, but you must package the mod as a mod instead of plugin and use the same project name as the base game (more details below).

Mods must be packaged without shared shader archive  
 ![image](https://d3kjluh73b9h9o.cloudfront.net/original/4X/6/f/a/6faa18c00cedf5d4a9feb8249dfcdfc4378cdb75.png)  
The base game can have this setting enabled or not, it should not make any difference.

In the end, I’ve got two working approaches, detailed below.

* * *

# 1. Mods as plugins

The advantage of this approach (compared to #2) is that you can work around the ModProject having a different name than the GameProject.

If you simply drop the packaged plugin into the basegame’s plugin folder, it will not load your plugin, as the game relies on a `.upluginmanifest` file that does not specify to load this specific plugin.

You can manually create one (or multiple) new manifest(s) in the Plugins/ folder. They must not be in a subfolder. They must not conflict with the base game’s upluginmanifest, which is in the base game’s PAK at path `GameProject/Plugins/GameProject.upluginmanifest`.

Create for example `GameProject/Plugins/Mod1.upluginmanifest` with the following format :

```json
{
	"Contents": [
		{
			"File": "../../../ModProject/Plugins/Mod1/Mod1.uplugin",
			"Descriptor": {
				"FileVersion": 3,
				"CanContainContent": true
			}
		}
	]
}

```

`Descriptor` is essentially a copy of what you have in the .uplugin file. I only included what I believe is necessary. The other fields that are usually present do not seem necessary.

The `File` field is very important here, as this tells the PluginManager where to find the plugin files, and this is where the plugin virtual path will point to.

If your ModProject has the exact same name as GameProject, then plugin should be in the GameProject/Plugins/ folder.  
Packaged file structure will look like this :

```auto
Engine
GameProject/
  - Binaries
  - Content/Paks/GameProject-Windows.pak
  - Plugins/
    - Mod1.upluginmanifest
    - Mod1/
      - Mod1.uplugin
      - Content/Paks/Windows/Mod1GameProject-Windows.pak

```

In this case, the `File` field can be shortened to `../../Plugins/Mod1/Mod1.uplugin`.  
The virtual path will be automatically created at such :  
`/Mod1` → `../../Plugins/Mod1/Content`

If however the ModProject has a different name, then you must create additional folders to match the paths of assets paked into the plugin PAK.  
Packaged file structure must look like this :

```auto
Engine
GameProject/
  - Binaries
  - Content/Paks/GameProject-Windows.pak
  - Plugins/
    - Mod1.upluginmanifest
ModProject/
  - Plugins/
    - Mod1/
      - Mod1.uplugin
      - Content/Paks/Windows/Mod1GameProject-Windows.pak

```

The File field like this :  
`"File": "../../../ModProject/Plugins/Mod1/Mod1.uplugin"`  
And the virtual path will be created as such :  
`/Mod1` → `../../../ModProject/Plugins/Mod1/Content`

* * *

# 2. Mods as mods

As mentioned earlier, this approach does not require to manually create a .upluginmanifest file, however it comes with other constraints.

The PluginManager loads mods in GameProject/Mods folder so your packaged file structure must look like this :

```auto
Engine
GameProject/
  - Binaries
  - Content/Paks/GameProject-Windows.pak
  - Mods/
    - Mod1/
      - Mod1.uplugin
      - Content/Paks/Windows/Mod1GameProject-Windows.pak

```

PluginManager will detect the .uplugin, and automatically mount the pak, and will automatically create the following virtual path  
`/Mod1` → `../../../GameProject/Mods/Mod1/Content`

As seen earlier, when packaging our mod as a plugin we ended up with a PAK with the following contents :

```auto
Display: Mount point ../../../ModProject/Plugins/Mod1/
Display: "AssetRegistry.bin"
Display: "Content/BP_ModDerived.uasset"
Display: "Content/BP_ModDerived.uexp"
Display: "Content/M_Mat.uasset"
Display: "Content/M_Mat.uexp"

```

So there is clearly a mismatch as the PAK contains stuff in `ModProject/Plugins/Mod1` while the plugin manager creates a path towards `GameProject/Mods/Mod1`.

Luckily, fixing this is pretty straightforward :

- First, the mod project must be exact same name as the game project.

- Second, Plugins must be Mods instead. Fortunately, the two seem to be interchangeable. Editor and cooker seem to support mods just the same. So after creating your content plugin via editor UI, all you have to do is hop into the folders, create a Mods folder, and move the plugin from Plugins folder into the Mods folder, then reload editor. Nothing should have changed, but now after packaging the PAK will have its contents in `ModProject/Mods/Mod1` instead.

After packaging, simply drop the packaged Mods/ folder directly into the packaged GameProject/ folder and it should work just fine.

* * *

# IoStore and PAK chunking :

The game being shipped with PAK chunks or not does not seem to have any impact on any of this. You do not have to enable PAK chunking for your mod PAKs.

If the game is shipped with IoStore however, then you must package your mods with IoStore enabled as well. Here’s what I discovered (UE 5.5.4) : if you don’t enable IoStore and package your mod as a simple PAK, the game will still discover the plugin and load your PAK, and it will even read its AssetRegistry and integrate it. However when you try to load objects within (StaticLoadObject or whatever references), it will fail to find them. The linker successfully reads and loads packages (as UPackage\*), but never loads the objects within packages (eg. UClass and CDO). To fix that, package your mod with IoStore enabled, which should give you .pak, .utoc, and .ucas files. The rest works the same.

* * *

* * *

# Conclusion

As a final note, all of this only works at game launch. As stated in the primer above, PluginManager will only look for plugins and PAKs on startup.

For any more dynamic solution, a custom C++ implementation will most likely be required.

I just wanted to share what is possible to do with builtin tools already, and where problems can easily arise - ie. virtual path vs. paked path issues for the most part. Most of these traps would likely be present when dealing with dynamic pak loading as well.

If you are struggling with similar issues as I did, here are some debugging methods I used, that will dump info out of shipping builds (no logs) :

```cpp
#include "Interfaces/IPluginManager.h"
#include "IPlatformFilePak.h"
#include "AssetRegistry/IAssetRegistry.h"
#include "AssetRegistry/AssetRegistryHelpers.h"

void DebugPlugins()
{
	TArray<FString> Output;
	auto& PluginManager = IPluginManager::Get();
	auto AllPlugins = PluginManager.GetDiscoveredPlugins();
	for (const auto& Plugin : AllPlugins)
	{
		Output.Emplace(FString::Printf(TEXT("plugin:%s, enabled:%i, hasContent:%i, mount:%s, explicitlyLoaded:%i"), *Plugin->GetName(), Plugin->IsEnabled(), Plugin->CanContainContent(), *Plugin->GetMountedAssetPath(), Plugin->GetDescriptor().bExplicitlyLoaded));
	}
	FFileHelper::SaveStringArrayToFile(Output, *FPaths::Combine(FPaths::ProjectDir(), TEXT("plugins.log")));
}

void DebugPaks()
{
	TArray<FString> Output;
	auto PakPlatform = (FPakPlatformFile*)FPlatformFileManager::Get().FindPlatformFile(TEXT("PakFile"));
	if (PakPlatform != nullptr)
	{
		TArray<FString> Paks;
		PakPlatform->GetMountedPakFilenames(Paks);
		for (const auto& Pak : Paks)
		{
			Output.Emplace(FString::Printf(TEXT("pak: %s"), *Pak));
		}
	}
	else
	{
		Output.Emplace("PakPlatformFile = nullptr");
	}
	FFileHelper::SaveStringArrayToFile(Output, *FPaths::Combine(FPaths::ProjectDir(), TEXT("paks.log")));
}

void DebugVirtualPaths()
{
	TArray<FString> Output;
	TArray<FString> RootPaths;
	FPackageName::QueryRootContentPaths(RootPaths);
	for (const auto& RootPath : RootPaths)
	{
		FString ContentPath;
		FPackageName::TryConvertLongPackageNameToFilename(RootPath, ContentPath);
		Output.Emplace(FString::Printf(TEXT("RootPath: %s --> %s"), *RootPath, *ContentPath));
	}
	FFileHelper::SaveStringArrayToFile(Output, *FPaths::Combine(FPaths::ProjectDir(), TEXT("paths.log")));
}

void DumpAssets()
{
	TArray<FString> Output;
	auto AR = IAssetRegistry::Get();
	auto DumpAssetsInPath = [&AR, &Output](const FString& Path) {
		Output.Emplace(FString::Printf(TEXT("--- %s"), *Path));
		TArray<FAssetData> Assets;
		AR->GetAssetsByPath(*Path, Assets, true);
		for (const auto& Asset : Assets)
		{
			Output.Emplace(FString::Printf(TEXT("Asset: %s"), *Asset.GetFullName()));
		}
	};
	DumpAssetsInPath("/Game");
	DumpAssetsInPath("/Mod1");
	FFileHelper::SaveStringArrayToFile(Output, *FPaths::Combine(FPaths::ProjectDir(), TEXT("assets.log")));
}

void DebugChildBlueprints()
{
	TArray<FString> Output;
	auto AR = IAssetRegistry::Get();
	TArray<FAssetData> Assets;
	FARFilter Filter;
	Filter.TagsAndValues.Add("ParentClass", FString("/Script/Engine.BlueprintGeneratedClass'/Game/BP_Base.BP_Base_C'"));
	AR->GetAssets(Filter, Assets);
	for (const auto& Asset : Assets)
	{
		Output.Emplace(FString::Printf(TEXT("Asset: %s"), *Asset.GetFullName()));
	}
	FFileHelper::SaveStringArrayToFile(Output, *FPaths::Combine(FPaths::ProjectDir(), TEXT("childbp.log")));
}

void DebugStaticLoad()
{
	TArray<FString> Output;
	auto StaticLoad = [&Output](const FString& Path) {
		UObject* Obj = StaticLoadObject(UObject::StaticClass(), nullptr, *Path);
		Output.Emplace(FString::Printf(TEXT("StaticLoad('%s'): %s"), *Path, Obj ? *Obj->GetFullName() : TEXT("NULL")));
	};
	StaticLoad("/Game/BP_GameDerived.BP_GameDerived_C");
	StaticLoad("/Game/Mod1/BP_ModDerived.BP_ModDerived_C");
	StaticLoad("/Mod1/BP_ModDerived.BP_ModDerived_C");
	FFileHelper::SaveStringArrayToFile(Output, *FPaths::Combine(FPaths::ProjectDir(), TEXT("staticload.log")));
}

```

---

_[View the full topic](https://forums.unrealengine.com/t/knowledge-base-primer-loading-content-and-pak-files-at-runtime/536669)._
