Get all Actors from mounted .pak

To support modding, I create .pak files manually from our ModKit. Those .pak files may contain Material/StaticMeshes/Sounds/Actors/…

I am able to mount/unmount the .pak files at runtime and pass the mount path to the AssetRegistry to add the data.

 assetRegistry.ScanPathsSynchronous({ properContentDir }, true);

I’m able to get any Data-Asset using the AssetRegistry (Materials, Sounds, StaticMeshes, …).

Trouble occours when going for blueprint generated actors. I found this tutorial for scanning a path for blueprint classes: http://kantandev.com/articles/finding-all-classes-blueprints-with-a-given-base

This works in editor and packaged build. However in a packaged build I’m only able to get Blueprint-Actors that are part of the main-pakfile. BlueprintGenerated actors from the runtime mounted pak are not returned from the asset registry. I opened the created .pak to make sure the BlueprintClasses are inside.

    FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(FName("AssetRegistry"));
    IAssetRegistry& assetRegistry = AssetRegistryModule.Get();

    if (assetRegistry.IsLoadingAssets() || bScanPath)
    {
        TArray<FString> pathsToScan;
        pathsToScan.Add(*path);
        assetRegistry.ScanPathsSynchronous(pathsToScan, true);
    }

    FARFilter filter;
    filter.bRecursivePaths = bPathRecursive;
    filter.bRecursiveClasses = true;
    filter.PackagePaths.Add(*path);
    // Note: assetClass->IsInBlueprint() returns true, if the class was implemented in blueprint.
    // It will return false for classes that are native, like AActor.
    if (bBlueprintClass)
    {
        filter.ClassNames.Add(UBlueprint::StaticClass()->GetFName());
    }
    else
    {
        filter.ClassNames.Add(*assetClass->GetClass()->GetName());
    }
    TArray<FAssetData> assetList;
    assetRegistry.GetAssets(filter, assetList);

assetList does not contain the actors from the mounted .pak, they are not known to the AssetRegistry.

Any idea why?

Don’t know why the AssetRegistry wouldn’t find the classes. So I came up with a workaround.

This function makes use of some custom functions, but you get the idea. The function itself is expensive and should be run async.

void GetBlueprintClassesInDirectory(const TSubclassOf<UObject> blueprintClass, const FString path, const bool bRecursive, TArray<UClass*>& blueprintClasses)
{
    double startTime = FPlatformTime::Seconds();

    // Convert the content path to a full path to look for files.
    FString projectContentDir = FPaths::ProjectContentDir();
    FString properContentDir = MakeProperContentDir(path); // /Game/...
    FString contentRelativeDir = properContentDir;
    properContentDir.Split(TEXT("/Game"), nullptr, &contentRelativeDir);
    FString fullDir = projectContentDir + contentRelativeDir; // File system directory

    TArray<FString> foundFiles;
    UContentMountLibrary::GetAllFilesFoldersInDir(fullDir, FString("*"), true, false, foundFiles, true); // In a packaged game, files are returned multiple times.

    for (const FString& file : foundFiles)
    {
        if (file.EndsWith(TEXT(".uasset")))
        {
            // Convert the file path back to a content path.
            FString fileContentDir = FPaths::GetPath(file);
            FPaths::MakePathRelativeTo(fileContentDir, *projectContentDir);
            fileContentDir = FString(TEXT("/Game/")) + fileContentDir;
            fileContentDir.RemoveFromEnd(FString(TEXT("/")));

            FString fileName = FPaths::GetBaseFilename(file);

            FString classReference = FString::Printf(TEXT("%s/%s.%s_C"), *fileContentDir, *fileName, *fileName);
            UClass* loadedClass = LoadObject<UClass>(nullptr, *classReference);
            if (loadedClass && loadedClass->IsChildOf(blueprintClass))
            {
                blueprintClasses.AddUnique(loadedClass); // NOTE we AddUnique as files are returned multiple times by file search function.
            }
        }
    }

    CM_LOG(Verbose, "GetBlueprintClassesInDirectory completed in %0.6f seconds", FPlatformTime::Seconds() - startTime);
}

Hey Rumbleball, we’re trying to get mod support to work over here and face a similar issue : no DataAsset object we looked for was ever found by Asset Registry, though our valid PAK file was correctly mounted. DataAssets are basic UObjects, I have no idea if they’re closer to Blueprints than they are to other resources. I’ve seen your answer and I’ll definitely try it out, but this seems like an engine issue.

Did you ever find a more relevant solution ?

As long as you do not get anything, I would not assume you mounted correctly. Make sure the MountPoint in the package is correct …/…/…/GameName/… and you mount it to that mount point. In case you package from a different project (Different project name) you need to change the GameName in the mount point.

Then use

     FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(FName("AssetRegistry"));
     IAssetRegistry& assetRegistry = AssetRegistryModule.Get();

         assetRegistry.ScanPathsSynchronous(pathsToScan, true);

After that you should be able to get the assets via the asset registry.
This is valid for things links Materials, StaticMeshes, … All assets that do not define a class.

Also make sure you actually mounted. Mounting a .pak is only possible in packaged game (Shipping/Development/Debug).

Also see my post here: Mounting pak files at runtime - Programming & Scripting - Unreal Engine Forums

I had trouble letting unreal mount the PakFile automatically by placing it in the Content/Paks folder of the packaged game. It simply did not work out for me. (UE4.19)

Mounting manually works tough…

  IPlatformFile* ExistingPakPlatformFile = FPlatformFileManager::Get().FindPlatformFile(*PakFileName);

is wrong and must be

IPlatformFile* existingPakPlatformFile = FPlatformFileManager::Get().FindPlatformFile(TEXT("PakFile"));

We are getting the PakPlatformFile here the engine already puts into the PlatformFile-Chain. The name of the PakPlatformFile is PakFile. You should not change anything about this line.

if (!PakPlatformFile->Mount(*PakFileName, 0, *PakFile->GetMountPoint()))
    {
        FLOG("Failed to mount PakPlatformFile");
    }
    else
    {
        FLOGV("Mounted PakPlatformFile %s at %s", *PakFileName, *PakFile->GetMountPoint());
    }

You are not mouning a PakPlatformFile. You use the PakPlatformFile to mount your PakFile.

Instead of doing that stuff manually, you can also use:

FCoreDelegates::OnMountPak.ExecuteIfBound(YOURPAKFILE, PAKORDER, nullptr)

The PakPlatformFile the engine creates binds to that delegate

// Bound to FCoreDelegates::OnMountPak
bool FPakPlatformFile::HandleMountPakDelegate(const FString& PakFilePath, uint32 PakOrder, IPlatformFile::FDirectoryVisitor* Visitor)

Well, I haven’t got the Pak file to mount in the packaged game by putting it in the “Paks” folder. I tried something close to the code you linked in that thread : void MountPak(const FString &PakFileName){ FPakPlatformFile* PakPlatformFi - Pastebin.com

The output confirms the validity of the pak file and the two assets I’m adding - material and data asset.

Display: Target mount point ../../../HeliumRain/Mods/ExampleMod/
Display: Mounted PakPlatformFile D:/Dock/SteamLibrary/SteamApps/workshop/content/681330/1560623971/Windows/ExampleMod.pak at ../../../HeliumRain/Mods/ExampleMod/
Display: File ../../../HeliumRain/Mods/ExampleMod/AssetRegistry.bin
Display: Dir ../../../HeliumRain/Mods/ExampleMod/Content
Display: File ../../../HeliumRain/Mods/ExampleMod/Content/M_ModMaterial.uasset
Display: File ../../../HeliumRain/Mods/ExampleMod/Content/M_ModMaterial.uexp
Display: File ../../../HeliumRain/Mods/ExampleMod/Content/ModSector.uasset
Display: File ../../../HeliumRain/Mods/ExampleMod/Content/ModSector.uexp
Display: Dir ../../../HeliumRain/Mods/ExampleMod/Metadata
Display: File ../../../HeliumRain/Mods/ExampleMod/Metadata/DevelopmentAssetRegistry.bin

Then I just search the asset registry…

	IAssetRegistry& Registry = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry").Get();
	Registry.SearchAllAssets(true);

	TArray<FAssetData> AssetList;
	Registry.GetAssetsByClass(UFlareSectorCatalogEntry::StaticClass()->GetFName(), AssetList);

Still can’t get anything. Should I try using the AssetRegistry.bin file in the pak file ?

Thanks a lot for helping, there’s really nothing out there that appears to work.

I’ve reverted the offending line, with no change. I’ve also tried the OnMountPak approach with exactly the same result - Pak mounts fine, all files present, no errors, still nothing found through AssetRegistry.

Basically my take here is that AssetRegistry is simply unable to find mod content at all. Did you get that in particular to work ? Or are you using something like SynchronousLoad instead ?

I’ve seen discussions here (What are uexp and ubulk files? - Programming & Scripting - Unreal Engine Forums) about using FPaths::GameContentDir vs FPaths::EngineContentDir, does that ring any bell ? I’ve mounted my pak to PakFile->GetMountPoint() myself, which is “…/…/…/HeliumRain/Mods/ExampleMod/”.

You want to mount to GameContentDir as it is your content and not of engine.

“…/…/…/HeliumRain/Mods/ExampleMod/”. You missing content here. “…/…/…/HeliumRain/Content/Mods/ExampleMod/”.

Ive also been attempting to mount with
OnMountPak.Execute

But it always says “Warning: Pak “TestPak.pak” does not exist”
However the directory’s appear to be identical if i auto load it by placing the pak in the content folder, or passing the directory to it. Both show as “…/…/…/Helios/Content/Paks/TestPak.pak” in the log

IIRC I’ve never used that method on my own, so can’t tell ya whats the matter. We use the PakPlatformFile direktly.

Switched out to using the manual method and providing a absolute path, i.e D:/game/content/
and it works