UE4 4.9版本 实现 DLC 重新挂载来解决UE4加载pak包的BUG

pak DLC 挂载

Posted by     Trump on October 22, 2016

并不适合阅读的个人文档。

UE4 4.9版本 实现 DLC 重新挂载来解决UE4加载pak包的BUG

当工程中添加了3个以上的pak包,例如2016_10_20_1.pak、2016_10_20_2.pak、2016_10_20_3.pak,这三个包里面都存在相同的资源(同样路径下,同样名字)…/a.ussat,错误的现象是:UE4编辑器将该资源加载到资源注册表中,读取该资源并不是版本号最新的哪一个,即2016_10_20_3.pak中a.usset读取的,该资源读取是混乱的(经过测试当目录下面只有2个pak包时是对的,3个以上就是出问题了)。

据了解4.17版本这个bug依然存在…

出错原因分析:

先上引擎代码

IPlatformFilePak.cpp

bool FPakPlatformFile::Initialize(IPlatformFile* Inner, const TCHAR* CmdLine) 截取部分代码:  

for (int32 PakFileIndex = 0; PakFileIndex < FoundPakFiles.Num(); PakFileIndex++)
		{
			const FString& PakFilename = FoundPakFiles[PakFileIndex];
			bool bLoadPak = true;
			if (PaksToLoad.Num() && !PaksToLoad.Contains(FPaths::GetBaseFilename(PakFilename)))
			{
				bLoadPak = false;
			}
			if (bLoadPak)
			{
				// hardcode default load ordering of game main pak -> game content -> engine content -> saved dir
				// would be better to make this config but not even the config system is initialized here so we can't do that
				uint32 PakOrder = 0;
				if (PakFilename.StartsWith(FString::Printf(TEXT("%sPaks/%s-"), *FPaths::GameContentDir(), FApp::GetGameName())))
				{
					PakOrder = 4;
				}
				else if (PakFilename.StartsWith(FPaths::GameContentDir()))
				{
					PakOrder = 3;
				}
				else if (PakFilename.StartsWith(FPaths::EngineContentDir()))
				{
					PakOrder = 2;
				}
				else if (PakFilename.StartsWith(FPaths::GameSavedDir()))
				{
					PakOrder = 1;
				}

				Mount(*PakFilename, PakOrder);
			}
		} 

IPlatformFilePak.cpp

bool FPakPlatformFile::Mount(const TCHAR* InPakFilename, uint32 PakOrder, const TCHAR* InPath /= NULL/)

bool FPakPlatformFile::Mount(const TCHAR* InPakFilename, uint32 PakOrder, const TCHAR* InPath /*= NULL*/)
{
	bool bSuccess = false;
	TSharedPtr<IFileHandle> PakHandle = MakeShareable(LowerLevel->OpenRead(InPakFilename));
	if (PakHandle.IsValid())
	{
		FPakFile* Pak = new FPakFile(LowerLevel, InPakFilename, bSigned);
		if (Pak->IsValid())
		{
			if (InPath != NULL)
			{
				Pak->SetMountPoint(InPath);
			}
			FString PakFilename = InPakFilename;
			if ( PakFilename.EndsWith(TEXT("_P.pak")) )
			{
				PakOrder += 100;
			}
			{
				// Add new pak file
				FScopeLock ScopedLock(&PakListCritical);
				FPakListEntry Entry;
				Entry.ReadOrder = PakOrder;
				Entry.PakFile = Pak;
				PakFiles.Add(Entry);
				PakFiles.StableSort();
			}
			bSuccess = true;
		}
		else
		{
			UE_LOG(LogPakFile, Warning, TEXT("Failed to mount pak \"%s\", pak is invalid."), InPakFilename);
		}
	}
	else
	{
		UE_LOG(LogPakFile, Warning, TEXT("Pak \"%s\" does not exist!"), InPakFilename);
	}
	return bSuccess;
}

分析:

引擎官方规定pak的默认加载顺序: Game/Content/Paks的文件夹 - >Game/Content - >Engine/Content - >Game/Save,定义了个PakOrder来指定不同的加载顺序。所以问题来了,如果多个pak包放在同一个级别的目录下如:都放在Game/Content/Paks的文件夹下,这几个pak的PakOrder都是4。再看Mount代码:在Mount的时候PakOrder都是一样的,Entry.ReadOrder = PakOrder; 这样问题就出现了,导致当加载到资源注册表的时候(详情请看 AssetRegistry.cpp void FAssetRegistry::Serialize(FArchive& Ar))如果读取到了相同的资源 a.ussset,他们的ReadOrder是一样的,所以出现了混乱。

解决方案

本文在不改变引擎代码的前提下修改这个问题。即重新挂载pak,读入到资源注册表中,定义了个最大索引数PakMaxOrder来取代PakOrder,设了个很大的值(感觉不太好,但管用 –!),当然需要在游戏启动的时候GameInstance中加载如下。

	LoadPakResourse = NewObject<ULoadPakResourse>(this, TEXT("LoadPakResourse"));
	LoadPakResourse->LoadPakFile();

.h

#pragma once

#include "../PakFile/Public/IPlatformFilePak.h"
#include "LoadPakResourse.generated.h"

UCLASS()
class ULoadPakResourse : public UObject
{
	GENERATED_BODY()

	void GetPaksUnderGameContentFolder(TArray<FString>& OutPakFolders);

	void FindAllPakFiles(IPlatformFile* LowLevelFile, const TArray<FString>& PakFolders, TArray<FString>& OutPakFiles);

	void FindPakFilesInDirectory(IPlatformFile* LowLevelFile, const TCHAR* Directory, TArray<FString>& OutPakFiles);

	bool Mount(const TCHAR* InPakFilename, uint32 PakOrder, const TCHAR* InPath /*= NULL*/);
	//自己添加了个最大索引数
	int32 PakMaxOrder = 100000000;

	TArray<FString> AllMountFileList;

public:
	ULoadPakResourse();

	~ULoadPakResourse();

	void LoadPakFile();

	TArray<FString> GetAllMountFile();

};

.cpp

#include "VRLogicPluginPrivatePCH.h"
#include "LoadPakResourse.h"
#include "Kismet/GameplayStatics.h"
#include "AssetRegistryModule.h"
#include "IAssetRegistry.h"
#include "GenericPlatformChunkInstall.h"


ULoadPakResourse::ULoadPakResourse()
{
}

ULoadPakResourse::~ULoadPakResourse()
{
}

TArray<FString> ULoadPakResourse::GetAllMountFile()
{
	return AllMountFileList;
}

void ULoadPakResourse::LoadPakFile()
{
	IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
	FPakPlatformFile* PakPlatformFile = new FPakPlatformFile();
	PakPlatformFile->Initialize(&PlatformFile, TEXT(""));

	TArray<FString> PakFolders;
	GetPaksUnderGameContentFolder(PakFolders);
	TArray<FString> FoundPakFiles;
	FindAllPakFiles(&PlatformFile, PakFolders, FoundPakFiles);
	//排序降序
	FoundPakFiles.Sort(TGreater<FString>());
	// Mount pak 包	
	for (int32 PakFileIndex = 0; PakFileIndex < FoundPakFiles.Num(); PakFileIndex++)
	{
		//Pak文件路径  
		const FString PakFilename = FoundPakFiles[PakFileIndex];     
		if (FPaths::FileExists(PakFilename))
		{
			FPakFile PakFile(&PlatformFile, *PakFilename, false);
			if (PakPlatformFile->Mount(*PakFilename, PakMaxOrder--, NULL))
			{
				TArray<FString> FileList;
				PakFile.FindFilesAtPath(FileList, *PakFile.GetMountPoint(), true, false, true);
				AllMountFileList.Append(FileList);

				UE_LOG(LogClass, Error, TEXT("Mount Success"));
			}
			else
			{
				UE_LOG(LogClass, Error, TEXT("Mount Failed"));
			}
		}
	}
}

void ULoadPakResourse::GetPaksUnderGameContentFolder(TArray<FString>& OutPakFolders)
{
	FString GameContentPath = FPaths::GameContentDir();
	GameContentPath = FPaths::ConvertRelativePathToFull(GameContentPath + TEXT("ResourcePackage"));
	OutPakFolders.Add(GameContentPath);
}

void ULoadPakResourse::FindAllPakFiles(IPlatformFile* LowLevelFile, const TArray<FString>& PakFolders, TArray<FString>& OutPakFiles)
{
	// Find pak files from the specified directories.
	for (int32 FolderIndex = 0; FolderIndex < PakFolders.Num(); ++FolderIndex)
	{
		FindPakFilesInDirectory(LowLevelFile, *PakFolders[FolderIndex], OutPakFiles);
	}
}

void ULoadPakResourse::FindPakFilesInDirectory(IPlatformFile* LowLevelFile, const TCHAR* Directory, TArray<FString>& OutPakFiles)
{
	// Helper class to find all pak files.
	class FPakSearchVisitor : public IPlatformFile::FDirectoryVisitor
	{
		TArray<FString>& FoundPakFiles;
		IPlatformChunkInstall* ChunkInstall;
	public:
		FPakSearchVisitor(TArray<FString>& InFoundPakFiles, IPlatformChunkInstall* InChunkInstall)
			: FoundPakFiles(InFoundPakFiles)
			, ChunkInstall(InChunkInstall)
		{}
		virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory)
		{
			if (bIsDirectory == false)
			{
				FString Filename(FilenameOrDirectory);
				if (FPaths::GetExtension(Filename) == TEXT("pak"))
				{
					// if a platform supports chunk style installs, make sure that the chunk a pak file resides in is actually fully installed before accepting pak files from it
					if (ChunkInstall)
					{
						FString ChunkIdentifier(TEXT("pakchunk"));
						FString BaseFilename = FPaths::GetBaseFilename(Filename);
						if (BaseFilename.StartsWith(ChunkIdentifier))
						{
							int32 DelimiterIndex = 0;
							int32 StartOfChunkIndex = ChunkIdentifier.Len();

							BaseFilename.FindChar(TEXT('-'), DelimiterIndex);
							FString ChunkNumberString = BaseFilename.Mid(StartOfChunkIndex, DelimiterIndex - StartOfChunkIndex);
							int32 ChunkNumber = 0;
							TTypeFromString<int32>::FromString(ChunkNumber, *ChunkNumberString);
							if (ChunkInstall->GetChunkLocation(ChunkNumber) == EChunkLocation::NotAvailable)
							{
								return true;
							}
						}
					}
					FoundPakFiles.Add(Filename);
				}
			}
			return true;
		}
	};
	// Find all pak files.
	FPakSearchVisitor Visitor(OutPakFiles, FPlatformMisc::GetPlatformChunkInstall());
	LowLevelFile->IterateDirectoryRecursively(Directory, Visitor);
}

版权声明:本文为博主原创文章,未经博主允许不得转载。