3.通過調試ue4源碼了解ue4打包功能


打包命令的構成 

首先要了解到,與pak包相關的操作,使用的命令為unrealpak,該命令最終調用了ue4引擎工程內的unrealpak.exe程序
UnrealPak <PakFilename> -Create=<paklist> [Options]
其中Create表示打包,PakList表示要打包的資源
還有一些其他的常見選項:
Options:
-blocksize=<BlockSize>
-bitwindow=<BitWindow>
-compress
-encrypt
-order=<OrderingFile>
-diff (requires 2 filenames first)
-enginedir (specify engine dir for when using ini encryption configs)
-projectdir (specify project dir for when using ini encryption configs)
-encryptionini (specify ini base name to gather encryption settings from)
-encryptindex (encrypt the pak file index, making it unusable in unrealpak without supplying the key)
本次所使用的mypaklist.txt內容
E:\unreal projects\test 4.27\Content\StarterContent\Maps\StarterMap.umap
E:\unreal projects\test 4.27\Content\StarterContent\Maps\Minimal_Default.umap
E:\unreal projects\test 4.27\Content\StarterContent\Maps\Advanced_Lighting.umap
構造命令:
UnrealPak.exe "E:\unreal projects\test 4.27\pak\mypak.pak" -Create= "E:\unreal projects\test 4.27\mypaklist.txt"
命令執行后會在目標路徑生成目標pak文件
 

功能源碼的調試分析

這里通過調試一次打包過程的代碼運行,了解了打包流程的實現,細節的地方就寫在注釋中
先進入的是UnrealPak.cpp,在初始化模塊后通過PakFileUtilities.cpp中的ExecuteUnrealPak函數完成打包功能。此后計算執行時間並退出。
INT32_MAIN_INT32_ARGC_TCHAR_ARGV() { // 照順序加載游戲相關的引擎模塊、項目和第三方插件等模塊
 GEngineLoop.PreInit(ArgC, ArgV); //獲取時間戳,記錄開始執行的時間點
       double StartTime = FPlatformTime::Seconds(); //獲取命令行傳入的參數並傳入關鍵函數ExecuteUnrealPak,該函數完成pak相關功能
       int32 Result = ExecuteUnrealPak(FCommandLine::Get())? 0 : 1; //打印日志:命令執行所耗時間
       UE_LOG(LogPakFile, Display, TEXT("Unreal pak executed in %f seconds"),  FPlatformTime::Seconds() - StartTime ); GLog->Flush(); //退出
       RequestEngineExit(TEXT("UnrealPak Exiting")); FEngineLoop::AppPreExit(); FModuleManager::Get().UnloadModulesAtShutdown(); FEngineLoop::AppExit(); return Result; }
分析源碼PakFileUtilities.cpp
PakFileUtilities.cpp中包含了pak相關的功能代碼
bool ExecuteUnrealPak(const TCHAR* CmdLine) //循環解析傳入的參數,
       TArray<FString> NonOptionArguments; for (const TCHAR* CmdLineEnd = CmdLine; *CmdLineEnd != 0;) { FString Argument = FParse::Token(CmdLineEnd, false); if (Argument.Len() > 0 && !Argument.StartsWith(TEXT("-"))) { NonOptionArguments.Add(Argument); } }
然后依次是處理option為-Project,-Batch時的情況,注意打包所使用的option為-Create,所以其他option的處理代碼不會執行
代碼看起來是加解密相關  
 //一個加解密結構
 FKeyChain KeyChain; LoadKeyChain(CmdLine, KeyChain); KeyChainUtilities::ApplyEncryptionKeys(KeyChain);
跟蹤后看到此處KeyChain屬性為空或0,說明沒有加解密,那是因為一開始命令就沒有指定加密
接下來檢查option中是否有-Test和-Vertify
bool IsTestCommand = FParse::Param(CmdLine, TEXT("Test")); bool IsVerifyCommand = FParse::Param(CmdLine, TEXT("Verify")); 如果有-Test或-Vertify就進行相關處理 if (IsTestCommand || IsVerifyCommand) { …… }
后面依次是對-List,Info,Diff,Extract,AuditFiles,WhatsAtOffset,CalcCompressionBlockCRCs,GeneratePIXMappingFile,Repack等等可選的處理
if (FParse::Param(CmdLine, TEXT("List"))) { …… } if (FParse::Param(CmdLine, TEXT("Diff"))) { …… } if (FParse::Param(CmdLine, TEXT("Extract"))) { …… } if (FParse::Param(CmdLine, TEXT("AuditFiles"))) { …… } if (FParse::Param(CmdLine, TEXT("WhatsAtOffset"))) { …… } if (FParse::Param(CmdLine, TEXT("CalcCompressionBlockCRCs"))) { …… } if (FParse::Param(CmdLine, TEXT("GeneratePIXMappingFile"))) { …… } if (FParse::Param(CmdLine, TEXT("Repack"))) { …… }
結束這些可選項的處理代碼后,如果有傳入參數則執行CheckAndReallocThreadPool,此時傳入的參數有兩個,一個是pakfilename,一個是paklist
if(NonOptionArguments.Num() > 0) { CheckAndReallocThreadPool();

其中CheckAndReallocThreadPool函數獲取線程數和最大線程容量,並給線程分配內存空間

void CheckAndReallocThreadPool() { if (FPlatformProcess::SupportsMultithreading()) { //獲取線程池中的線程數
              const int32 ThreadsSpawned = GThreadPool->GetNumThreads(); //獲取機器cpu的核心數
              const int32 DesiredThreadCount = NumberOfWorkerThreadsDesired(); //判斷是否還有空間再分配給新線程
              if (ThreadsSpawned < DesiredThreadCount) { UE_LOG(LogPakFile, Log, TEXT("Engine only spawned %d worker threads, bumping up to %d!"), ThreadsSpawned, DesiredThreadCount); GThreadPool->Destroy(); GThreadPool = FQueuedThreadPool::Allocate(); verify(GThreadPool->Create(DesiredThreadCount, 128 * 1024)); } else { UE_LOG(LogPakFile, Log, TEXT("Continuing with %d spawned worker threads."), ThreadsSpawned); } } }
后面進行pak前的准備
//獲取pak文件指定的文件名
FString PakFilename = GetPakPath(*NonOptionArguments[0], true); //生成FPakInputPair結構,存儲被打包文件的信息
TArray<FPakInputPair> Entries; //創建命令行結構
FPakCommandLineParameters CmdLineParameters; //列出所有要打包進pak文件的文件,解析傳入參數,並依次設置上文命令行的屬性
ProcessCommandLine(CmdLine, NonOptionArguments, Entries, CmdLineParameters); ProcessCommandLine函數中,先設置了傳入的命令行對象的屬性 if (FParse::Value(CmdLine, TEXT("-compressionblocksize="), CompBlockSizeString) && FParse::Value(CmdLine, TEXT("-compressionblocksize="), CmdLineParameters.CompressionBlockSize)) { …… } if (!FParse::Value(CmdLine, TEXT("-patchpaddingalign="), CmdLineParameters.PatchFilePadAlign)) { …… } if (!FParse::Value(CmdLine, TEXT("-AlignForMemoryMapping="), CmdLineParameters.AlignForMemoryMapping)) { …… } if (FParse::Param(CmdLine, TEXT("encryptindex"))) { …… } if (FParse::Param(CmdLine, TEXT("sign"))) { …… } if (FParse::Param(CmdLine, TEXT("AlignFilesLargerThanBlock"))) { …… } if (FParse::Param(CmdLine, TEXT("ForceCompress"))) { …… } if (FParse::Param(CmdLine, TEXT("FileRegions"))) { …… }
如果傳入參數中有-compressionformats 或-compressionformat,就 默認使用 ZLib 壓縮算法壓縮
if (FParse::Value(CmdLine, TEXT("-compressionformats="), DesiredCompressionFormats) ||  FParse::Value(CmdLine, TEXT("-compressionformat="), DesiredCompressionFormats)) { TArray<FString> Formats; DesiredCompressionFormats.ParseIntoArray(Formats, TEXT(",")); for (FString& Format : Formats) { // look until we have a valid format
                      FName FormatName = *Format; if (FCompression::IsFormatValid(FormatName)) { CmdLineParameters.CompressionFormats.Add(FormatName); break; } else { UE_LOG(LogPakFile, Warning, TEXT("Compression format %s is not recognized"), *Format); } } }
判斷是否要壓縮和加密
if (FParse::Value(CmdLine, TEXT("-create="), ResponseFile)) { …… bool bCompress = FParse::Param(CmdLine, TEXT("compress")); if ( bCompress ) …… bool bEncrypt = FParse::Param(CmdLine, TEXT("encrypt")); if (CmdLineParameters.GeneratePatch) ……
結束ProcessCommandLine函數后回到主程序,按參數依次判斷執行剛剛的命令
if (FParse::Value(CmdLine, TEXT("-order="), GameOpenOrderStr, false)) { ……} if (FParse::Value(CmdLine, TEXT("-secondaryOrder="), SecondOrderStr, false)) { …… } if ( CmdLineParameters.GeneratePatch ) { if (!FParse::Value(CmdLine, TEXT("TempFiles="), OutputPath)) { …… } if (FParse::Value(FCommandLine::Get(), TEXT("PatchCryptoKeys="), PatchReferenceCryptoKeysFilename)) { …… } …… }
接下來創建一個空數組FilesToAdd,並通過函數CollectFilesToAdd開始收集需要打包的文件。先打印日志和記錄開始時間
TArray<FPakInputPair> FilesToAdd; CollectFilesToAdd(FilesToAdd, Entries, OrderMap, CmdLineParameters); UE_LOG(LogPakFile, Display, TEXT("Collecting files to add to pak file...")); const double StartTime = FPlatformTime::Seconds();
用for循環遍歷FPakInputPair內容,也就是每個文件的信息
for (int32 Index = 0; Index < InEntries.Num(); Index++)
從input中解析參數,確定是否壓縮和加密。
const FPakInputPair& Input = InEntries[Index]; const FString& Source = Input.Source; bool bCompression = Input.bNeedsCompression; bool bEncryption = Input.bNeedEncryption;

獲取文件名和文件路徑,並進行檢查和格式化處理

FString Filename = FPaths::GetCleanFilename(Source); FString Directory = FPaths::GetPath(Source); FPaths::MakeStandardFilename(Directory); FPakFile::MakeDirectoryFromPath(Directory);

因為paklist文件中可以使用通配符,所以接着是對通配符的相應解析代碼

if (Filename.IsEmpty()) { Filename = TEXT("*.*"); } if ( Filename.Contains(TEXT("*")) ) { ……//通配符*匹配多個文件
 } else { ……//不包含*則匹配特定單個文件
              }
其中對多個文件的處理,分為格式化文件名,文件排序,壓縮,加密等
// Add multiple files
                      TArray<FString> FoundFiles; IFileManager::Get().FindFilesRecursive(FoundFiles, *Directory,  *Filename, true, false); for (int32 FileIndex = 0; FileIndex < FoundFiles.Num();  FileIndex++) { //格式化文件名和路徑
 FPakInputPair FileInput; FileInput.Source = FoundFiles[FileIndex]; FPaths::MakeStandardFilename(FileInput.Source); FileInput.Dest = FileInput.Source.Replace(*Directory,  *Input.Dest, ESearchCase::IgnoreCase); //對文件進行排序
                             uint64 FileOrder = OrderMap.GetFileOrder(FileInput.Dest,  false, &FileInput.bIsInPrimaryOrder); if(FileOrder != MAX_uint64) { FileInput.SuggestedOrder = FileOrder; } else { // we will put all unordered files at 1 << 28 so that they are before any uexp or ubulk files we assign orders to here
                                    FileInput.SuggestedOrder = (1 << 28); // if this is a cook order or an old order it will not have uexp files in it, so we put those in the same relative order after all of the normal files, but before any ubulk files
                                    if (FileInput.Dest.EndsWith(TEXT("uexp")) ||  FileInput.Dest.EndsWith(TEXT("ubulk"))) { FileOrder =  OrderMap.GetFileOrder(FPaths::GetBaseFilename(FileInput.Dest, false) +  TEXT(".uasset"), false, &FileInput.bIsInPrimaryOrder); if (FileOrder == MAX_uint64) { FileOrder =  OrderMap.GetFileOrder(FPaths::GetBaseFilename(FileInput.Dest, false) + TEXT(".umap"),   false, &FileInput.bIsInPrimaryOrder); } if (FileInput.Dest.EndsWith(TEXT("uexp"))) { FileInput.SuggestedOrder =  ((FileOrder != MAX_uint64) ? FileOrder : 0) + (1 << 29); } else { FileInput.SuggestedOrder =  ((FileOrder != MAX_uint64) ? FileOrder : 0) + (1 << 30); } } } //是否壓縮和加密
                             FileInput.bNeedsCompression = bCompression; FileInput.bNeedEncryption = bEncryption; if (!AddedFiles.Contains(FileInput.Source)) { OutFilesToAdd.Add(FileInput); AddedFiles.Add(FileInput.Source); } else { int32 FoundIndex; OutFilesToAdd.Find(FileInput,FoundIndex); OutFilesToAdd[FoundIndex].bNeedEncryption |= bEncryption; OutFilesToAdd[FoundIndex].bNeedsCompression |= bCompression; OutFilesToAdd[FoundIndex].SuggestedOrder =  FMath::Min<uint64>(OutFilesToAdd[FoundIndex].SuggestedOrder, FileInput.SuggestedOrder); } }
如果paklist中使用的不是*這樣的通配符,而是具體的文件名,就對單獨文件進行文件名格式化,排序,壓縮,加密的處理
// Add single file
 FPakInputPair FileInput; FileInput.Source = Input.Source; FPaths::MakeStandardFilename(FileInput.Source); FileInput.Dest = FileInput.Source.Replace(*Directory,  *Input.Dest, ESearchCase::IgnoreCase); uint64 FileOrder = OrderMap.GetFileOrder(FileInput.Dest,  CmdLineParameters.bFallbackOrderForNonUassetFiles, &FileInput.bIsInPrimaryOrder); if (FileOrder != MAX_uint64) { FileInput.SuggestedOrder = FileOrder; } FileInput.bNeedEncryption = bEncryption; FileInput.bNeedsCompression = bCompression; if (AddedFiles.Contains(FileInput.Source)) { int32 FoundIndex; OutFilesToAdd.Find(FileInput, FoundIndex); OutFilesToAdd[FoundIndex].bNeedEncryption |= bEncryption; OutFilesToAdd[FoundIndex].bNeedsCompression |= bCompression; OutFilesToAdd[FoundIndex].SuggestedOrder =  FMath::Min<uint64>(OutFilesToAdd[FoundIndex].SuggestedOrder, FileInput.SuggestedOrder); } else { OutFilesToAdd.Add(FileInput); AddedFiles.Add(FileInput.Source); }
最后這些文件放在字符串集合AddedFiles中,按要求的順序排序,然后按字母順序排序
struct FInputPairSort { FORCEINLINE bool operator()(const FPakInputPair& A, const FPakInputPair&  B) const { return  A.bIsDeleteRecord == B.bIsDeleteRecord ?  (A.SuggestedOrder == B.SuggestedOrder ? A.Dest < B.Dest : A.SuggestedOrder <  B.SuggestedOrder) : A.bIsDeleteRecord < B.bIsDeleteRecord; } }; OutFilesToAdd.Sort(FInputPairSort());
再在日志中輸出用時
UE_LOG(LogPakFile, Display, TEXT("Collected %d files in %.2lfs."),  OutFilesToAdd.Num(), FPlatformTime::Seconds() - StartTime);
到這里就結束了CollectFilesToAdd函數,返回后調用CreatePakFile創建pak文件,最后返回create的成敗並退出        
bool bResult = CreatePakFile(*PakFilename, FilesToAdd, CmdLineParameters, KeyChain);
              if (CmdLineParameters.GeneratePatch)
              {
                      ……
              }
              GetDerivedDataCacheRef().WaitForQuiescence(true);
這時控制台打印出了日志,顯示已成功讀取list文件,並獲取三個預備打包的文件
繼續來看CreatePakFile函數,先實現了傳入文件屬性的解析,包含文件路徑,是否壓縮,是否加密等l 
 //創建字符串集合來存儲要打包的文件名
       TSet<FString> AddedFiles;    
        //遍歷傳入的OutFilesToAdd字符串集合,也就是讀取list的結果
       for (int32 Index = 0; Index < InEntries.Num(); Index++)
       {
                //解析出文件路徑,是否壓縮,是否加密等屬性
              const FPakInputPair& Input = InEntries[Index];
              const FString& Source = Input.Source;
              bool bCompression = Input.bNeedsCompression;
              bool bEncryption = Input.bNeedEncryption;
再通過文件的bIsDeleteRecord屬性判斷該文件在新的補丁處是否要被刪除,補丁可以理解為對舊的pak的更新
if (Input.bIsDeleteRecord) { OutFilesToAdd.Add(Input); continue; } 獲取文件的文件名,路徑。並將其格式化,判斷是否為空 FString Filename = FPaths::GetCleanFilename(Source); FString Directory = FPaths::GetPath(Source); FPaths::MakeStandardFilename(Directory); FPakFile::MakeDirectoryFromPath(Directory); if (Filename.IsEmpty()) { Filename = TEXT("*.*"); }
這時同樣對文件名做兩種情況處理,通配符情況,和具體文件名情況。
先處理通配符描述的情況,主要進行了文件屬性解析,排序(按照指定排序,未指定則按照字母表順序排序。),判斷文件是否重復。
if ( Filename.Contains(TEXT("*")) ) { TArray<FString> FoundFiles; IFileManager::Get().FindFilesRecursive(FoundFiles, *Directory,  *Filename, true, false); for (int32 FileIndex = 0; FileIndex < FoundFiles.Num();  FileIndex++) { //解析文件各項屬性
 FPakInputPair FileInput; FileInput.Source = FoundFiles[FileIndex]; FPaths::MakeStandardFilename(FileInput.Source); FileInput.Dest = FileInput.Source.Replace(*Directory,  *Input.Dest, ESearchCase::IgnoreCase); //定義文件的排序序號
                             uint64 FileOrder = OrderMap.GetFileOrder(FileInput.Dest,  false, &FileInput.bIsInPrimaryOrder); //如果有指定序號,按照指定的序號排序;否則默認MAX_uint64,再按照字母表順序排序。
                             if(FileOrder != MAX_uint64) { FileInput.SuggestedOrder = FileOrder; } else { // we will put all unordered files at 1 << 28 so that they are before any uexp or ubulk files we assign orders to here
                                    FileInput.SuggestedOrder = (1 << 28); // if this is a cook order or an old order it will not have uexp files in it, so we put those in the same relative order after all of the normal files, but before any ubulk files
                                    if (FileInput.Dest.EndsWith(TEXT("uexp")) ||  FileInput.Dest.EndsWith(TEXT("ubulk"))) { FileOrder =  OrderMap.GetFileOrder(FPaths::GetBaseFilename(FileInput.Dest, false) +  TEXT(".uasset"), false, &FileInput.bIsInPrimaryOrder); if (FileOrder == MAX_uint64) { FileOrder =  OrderMap.GetFileOrder(FPaths::GetBaseFilename(FileInput.Dest, false) + TEXT(".umap"),   false, &FileInput.bIsInPrimaryOrder); } if (FileInput.Dest.EndsWith(TEXT("uexp"))) { FileInput.SuggestedOrder =  ((FileOrder != MAX_uint64) ? FileOrder : 0) + (1 << 29); } else { FileInput.SuggestedOrder =  ((FileOrder != MAX_uint64) ? FileOrder : 0) + (1 << 30); } } } //解析加密、壓縮屬性。判斷文件是否重復,未重復就直接加入文件集合,重復就進行屬性比對。
                             FileInput.bNeedsCompression = bCompression; FileInput.bNeedEncryption = bEncryption; if (!AddedFiles.Contains(FileInput.Source)) { OutFilesToAdd.Add(FileInput); AddedFiles.Add(FileInput.Source); } else { int32 FoundIndex; OutFilesToAdd.Find(FileInput,FoundIndex); OutFilesToAdd[FoundIndex].bNeedEncryption |= bEncryption; OutFilesToAdd[FoundIndex].bNeedsCompression |= bCompression; OutFilesToAdd[FoundIndex].SuggestedOrder =  FMath::Min<uint64>(OutFilesToAdd[FoundIndex].SuggestedOrder, FileInput.SuggestedOrder); } } }
同上,完成特定文件情況下的排序處理,屬性解析,重復文件的屬性確認
else { //獲取預備打包文件的源地址和目的地址,以及排序方式
 FPakInputPair FileInput; FileInput.Source = Input.Source; FPaths::MakeStandardFilename(FileInput.Source); FileInput.Dest = FileInput.Source.Replace(*Directory,  *Input.Dest, ESearchCase::IgnoreCase); uint64 FileOrder = OrderMap.GetFileOrder(FileInput.Dest,  CmdLineParameters.bFallbackOrderForNonUassetFiles, &FileInput.bIsInPrimaryOrder); //指定了排序序號的情況
                      if (FileOrder != MAX_uint64) { FileInput.SuggestedOrder = FileOrder; } //壓縮、加密屬性的解析
                      FileInput.bNeedEncryption = bEncryption; FileInput.bNeedsCompression = bCompression; //如果要加入的文件已經存在於集合中,就不再重復加入,但是要比對屬性無誤
                      if (AddedFiles.Contains(FileInput.Source)) { int32 FoundIndex; OutFilesToAdd.Find(FileInput, FoundIndex); OutFilesToAdd[FoundIndex].bNeedEncryption |= bEncryption; OutFilesToAdd[FoundIndex].bNeedsCompression |= bCompression; OutFilesToAdd[FoundIndex].SuggestedOrder =  FMath::Min<uint64>(OutFilesToAdd[FoundIndex].SuggestedOrder, FileInput.SuggestedOrder); } //不重復就直接加入
                      else { OutFilesToAdd.Add(FileInput); AddedFiles.Add(FileInput.Source); } }
最后進行排序,結束pak文件的生成
// Sort by suggested order then alphabetically
       struct FInputPairSort { FORCEINLINE bool operator()(const FPakInputPair& A, const FPakInputPair&  B) const { return  A.bIsDeleteRecord == B.bIsDeleteRecord ?  (A.SuggestedOrder == B.SuggestedOrder ? A.Dest < B.Dest : A.SuggestedOrder <  B.SuggestedOrder) : A.bIsDeleteRecord < B.bIsDeleteRecord; } }; OutFilesToAdd.Sort(FInputPairSort()); UE_LOG(LogPakFile, Display, TEXT("Collected %d files in %.2lfs."),  OutFilesToAdd.Num(), FPlatformTime::Seconds() - StartTime);
收尾工作,通過文件名的路徑找到臨時文件夾並刪除
if (CmdLineParameters.GeneratePatch) { FString OutputPath = FPaths::GetPath(PakFilename) /  FString(TEXT("TempFiles")); IFileManager::Get().DeleteDirectory(*OutputPath, false, true); } GetDerivedDataCacheRef().WaitForQuiescence(true);
到這里打包功能代碼執行完畢,可以看到控制台打印出日志,從數據角度描述了剛剛的執行過程
最后計算用時並退出  
UE_LOG(LogPakFile, Display, TEXT("Unreal pak executed in %f seconds"),  FPlatformTime::Seconds() - StartTime );
GLog->Flush();
RequestEngineExit(TEXT("UnrealPak Exiting"));
FEngineLoop::AppPreExit();
FModuleManager::Get().UnloadModulesAtShutdown();
FEngineLoop::AppExit();

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM