You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
4857 lines
160 KiB
C++
4857 lines
160 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "IoStoreUtilities.h"
|
|
#include "HAL/FileManager.h"
|
|
#include "HAL/PlatformFileManager.h"
|
|
#include "Hash/CityHash.h"
|
|
#include "Interfaces/IPluginManager.h"
|
|
#include "Interfaces/ITargetPlatformManagerModule.h"
|
|
#include "Interfaces/ITargetPlatform.h"
|
|
#include "IO/IoDispatcher.h"
|
|
#include "Misc/App.h"
|
|
#include "Misc/CommandLine.h"
|
|
#include "Misc/ConfigCacheIni.h"
|
|
#include "Misc/PackageName.h"
|
|
#include "Misc/Paths.h"
|
|
#include "Misc/PackageName.h"
|
|
#include "Misc/Base64.h"
|
|
#include "Misc/AES.h"
|
|
#include "Misc/CoreDelegates.h"
|
|
#include "Misc/IEngineCrypto.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "Serialization/Archive.h"
|
|
#include "Serialization/BulkDataManifest.h"
|
|
#include "Serialization/JsonReader.h"
|
|
#include "Serialization/JsonSerializer.h"
|
|
#include "Serialization/BufferWriter.h"
|
|
#include "Serialization/LargeMemoryWriter.h"
|
|
#include "Serialization/MemoryReader.h"
|
|
#include "Serialization/AsyncLoading2.h"
|
|
#include "Serialization/ArrayReader.h"
|
|
#include "UObject/Class.h"
|
|
#include "UObject/NameBatchSerialization.h"
|
|
#include "UObject/PackageFileSummary.h"
|
|
#include "UObject/ObjectResource.h"
|
|
#include "UObject/Package.h"
|
|
#include "UObject/UObjectHash.h"
|
|
#include "Algo/Find.h"
|
|
#include "Misc/FileHelper.h"
|
|
#include "Misc/ScopeLock.h"
|
|
#include "Async/ParallelFor.h"
|
|
#include "Async/AsyncFileHandle.h"
|
|
#include "Async/Async.h"
|
|
#include "RSA.h"
|
|
#include "Misc/AssetRegistryInterface.h"
|
|
#include "AssetRegistryState.h"
|
|
|
|
//PRAGMA_DISABLE_OPTIMIZATION
|
|
|
|
IMPLEMENT_MODULE(FDefaultModuleImpl, IoStoreUtilities);
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogIoStore, Log, All);
|
|
|
|
#define IOSTORE_CPU_SCOPE(NAME) TRACE_CPUPROFILER_EVENT_SCOPE(IoStore##NAME);
|
|
#define IOSTORE_CPU_SCOPE_DATA(NAME, DATA) TRACE_CPUPROFILER_EVENT_SCOPE(IoStore##NAME);
|
|
|
|
#define OUTPUT_CHUNKID_DIRECTORY 0
|
|
#define OUTPUT_NAMEMAP_CSV 0
|
|
#define OUTPUT_DEBUG_PACKAGE_HASHES 0
|
|
#define OUTPUT_DEBUG_PACKAGE_EXPORT_BUNDLES 0
|
|
|
|
static const FName DefaultCompressionMethod = NAME_Zlib;
|
|
static const uint64 DefaultCompressionBlockSize = 64 << 10;
|
|
static const uint64 DefaultCompressionBlockAlignment = 64 << 10;
|
|
static const uint64 DefaultMemoryMappingAlignment = 16 << 10;
|
|
|
|
struct FReleasedPackages
|
|
{
|
|
TSet<FName> PackageNames;
|
|
TMap<FPackageId, FName> PackageIdToName;
|
|
};
|
|
|
|
struct FNamedAESKey
|
|
{
|
|
FString Name;
|
|
FGuid Guid;
|
|
FAES::FAESKey Key;
|
|
|
|
bool IsValid() const
|
|
{
|
|
return Key.IsValid();
|
|
}
|
|
};
|
|
|
|
struct FKeyChain
|
|
{
|
|
FRSAKeyHandle SigningKey = InvalidRSAKeyHandle;
|
|
TMap<FGuid, FNamedAESKey> EncryptionKeys;
|
|
const FNamedAESKey* MasterEncryptionKey = nullptr;
|
|
};
|
|
|
|
static void ApplyEncryptionKeys(const FKeyChain& KeyChain)
|
|
{
|
|
if (KeyChain.EncryptionKeys.Contains(FGuid()))
|
|
{
|
|
FAES::FAESKey DefaultKey = KeyChain.EncryptionKeys[FGuid()].Key;
|
|
FCoreDelegates::GetPakEncryptionKeyDelegate().BindLambda([DefaultKey](uint8 OutKey[32]) { FMemory::Memcpy(OutKey, DefaultKey.Key, sizeof(DefaultKey.Key)); });
|
|
}
|
|
|
|
for (const TMap<FGuid, FNamedAESKey>::ElementType& Key : KeyChain.EncryptionKeys)
|
|
{
|
|
if (Key.Key.IsValid())
|
|
{
|
|
// Deprecated version
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
FCoreDelegates::GetRegisterEncryptionKeyDelegate().ExecuteIfBound(Key.Key, Key.Value.Key);
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
|
|
// New version
|
|
FCoreDelegates::GetRegisterEncryptionKeyMulticastDelegate().Broadcast(Key.Key, Key.Value.Key);
|
|
}
|
|
}
|
|
}
|
|
|
|
static FRSAKeyHandle ParseRSAKeyFromJson(TSharedPtr<FJsonObject> InObj)
|
|
{
|
|
TSharedPtr<FJsonObject> PublicKey = InObj->GetObjectField(TEXT("PublicKey"));
|
|
TSharedPtr<FJsonObject> PrivateKey = InObj->GetObjectField(TEXT("PrivateKey"));
|
|
|
|
FString PublicExponentBase64, PrivateExponentBase64, PublicModulusBase64, PrivateModulusBase64;
|
|
|
|
if ( PublicKey->TryGetStringField("Exponent", PublicExponentBase64)
|
|
&& PublicKey->TryGetStringField("Modulus", PublicModulusBase64)
|
|
&& PrivateKey->TryGetStringField("Exponent", PrivateExponentBase64)
|
|
&& PrivateKey->TryGetStringField("Modulus", PrivateModulusBase64))
|
|
{
|
|
check(PublicModulusBase64 == PrivateModulusBase64);
|
|
|
|
TArray<uint8> PublicExponent, PrivateExponent, Modulus;
|
|
FBase64::Decode(PublicExponentBase64, PublicExponent);
|
|
FBase64::Decode(PrivateExponentBase64, PrivateExponent);
|
|
FBase64::Decode(PublicModulusBase64, Modulus);
|
|
|
|
return FRSA::CreateKey(PublicExponent, PrivateExponent, Modulus);
|
|
}
|
|
else
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
static void LoadKeyChainFromFile(const FString& InFilename, FKeyChain& OutCryptoSettings)
|
|
{
|
|
TUniquePtr<FArchive> File;
|
|
File.Reset(IFileManager::Get().CreateFileReader(*InFilename));
|
|
|
|
UE_CLOG(File == nullptr, LogIoStore, Fatal, TEXT("Specified crypto keys cache '%s' does not exist!"), *InFilename);
|
|
TSharedPtr<FJsonObject> RootObject;
|
|
TSharedRef<TJsonReader<char>> Reader = TJsonReaderFactory<char>::Create(File.Get());
|
|
if (FJsonSerializer::Deserialize(Reader, RootObject))
|
|
{
|
|
const TSharedPtr<FJsonObject>* EncryptionKeyObject;
|
|
if (RootObject->TryGetObjectField(TEXT("EncryptionKey"), EncryptionKeyObject))
|
|
{
|
|
FString EncryptionKeyBase64;
|
|
if ((*EncryptionKeyObject)->TryGetStringField(TEXT("Key"), EncryptionKeyBase64))
|
|
{
|
|
if (EncryptionKeyBase64.Len() > 0)
|
|
{
|
|
TArray<uint8> Key;
|
|
FBase64::Decode(EncryptionKeyBase64, Key);
|
|
check(Key.Num() == sizeof(FAES::FAESKey::Key));
|
|
FNamedAESKey NewKey;
|
|
NewKey.Name = TEXT("Default");
|
|
NewKey.Guid = FGuid();
|
|
FMemory::Memcpy(NewKey.Key.Key, &Key[0], sizeof(FAES::FAESKey::Key));
|
|
OutCryptoSettings.EncryptionKeys.Add(NewKey.Guid, NewKey);
|
|
}
|
|
}
|
|
}
|
|
|
|
const TSharedPtr<FJsonObject>* SigningKey = nullptr;
|
|
if (RootObject->TryGetObjectField(TEXT("SigningKey"), SigningKey))
|
|
{
|
|
OutCryptoSettings.SigningKey = ParseRSAKeyFromJson(*SigningKey);
|
|
}
|
|
|
|
const TArray<TSharedPtr<FJsonValue>>* SecondaryEncryptionKeyArray = nullptr;
|
|
if (RootObject->TryGetArrayField(TEXT("SecondaryEncryptionKeys"), SecondaryEncryptionKeyArray))
|
|
{
|
|
for (TSharedPtr<FJsonValue> EncryptionKeyValue : *SecondaryEncryptionKeyArray)
|
|
{
|
|
FNamedAESKey NewKey;
|
|
TSharedPtr<FJsonObject> SecondaryEncryptionKeyObject = EncryptionKeyValue->AsObject();
|
|
FGuid::Parse(SecondaryEncryptionKeyObject->GetStringField(TEXT("Guid")), NewKey.Guid);
|
|
NewKey.Name = SecondaryEncryptionKeyObject->GetStringField(TEXT("Name"));
|
|
FString KeyBase64 = SecondaryEncryptionKeyObject->GetStringField(TEXT("Key"));
|
|
|
|
TArray<uint8> Key;
|
|
FBase64::Decode(KeyBase64, Key);
|
|
check(Key.Num() == sizeof(FAES::FAESKey::Key));
|
|
FMemory::Memcpy(NewKey.Key.Key, &Key[0], sizeof(FAES::FAESKey::Key));
|
|
|
|
check(!OutCryptoSettings.EncryptionKeys.Contains(NewKey.Guid) || OutCryptoSettings.EncryptionKeys[NewKey.Guid].Key == NewKey.Key);
|
|
OutCryptoSettings.EncryptionKeys.Add(NewKey.Guid, NewKey);
|
|
}
|
|
}
|
|
UE_LOG(LogIoStore, Display, TEXT("Parsed '%d' crypto keys from '%s'"), OutCryptoSettings.EncryptionKeys.Num(), *InFilename);
|
|
}
|
|
FGuid EncryptionKeyOverrideGuid;
|
|
OutCryptoSettings.MasterEncryptionKey = OutCryptoSettings.EncryptionKeys.Find(EncryptionKeyOverrideGuid);
|
|
}
|
|
|
|
static void LoadKeyChain(const TCHAR* CmdLine, FKeyChain& OutCryptoSettings)
|
|
{
|
|
OutCryptoSettings.SigningKey = InvalidRSAKeyHandle;
|
|
OutCryptoSettings.EncryptionKeys.Empty();
|
|
|
|
// First, try and parse the keys from a supplied crypto key cache file
|
|
FString CryptoKeysCacheFilename;
|
|
if (FParse::Value(CmdLine, TEXT("cryptokeys="), CryptoKeysCacheFilename))
|
|
{
|
|
UE_LOG(LogIoStore, Display, TEXT("Parsing crypto keys from a crypto key cache file '%s'"), *CryptoKeysCacheFilename);
|
|
LoadKeyChainFromFile(CryptoKeysCacheFilename, OutCryptoSettings);
|
|
}
|
|
else if (FParse::Param(CmdLine, TEXT("encryptionini")))
|
|
{
|
|
FString ProjectDir, EngineDir, Platform;
|
|
|
|
if (FParse::Value(CmdLine, TEXT("projectdir="), ProjectDir, false)
|
|
&& FParse::Value(CmdLine, TEXT("enginedir="), EngineDir, false)
|
|
&& FParse::Value(CmdLine, TEXT("platform="), Platform, false))
|
|
{
|
|
UE_LOG(LogIoStore, Warning, TEXT("A legacy command line syntax is being used for crypto config. Please update to using the -cryptokey parameter as soon as possible as this mode is deprecated"));
|
|
|
|
FConfigFile EngineConfig;
|
|
|
|
FConfigCacheIni::LoadExternalIniFile(EngineConfig, TEXT("Engine"), *FPaths::Combine(EngineDir, TEXT("Config\\")), *FPaths::Combine(ProjectDir, TEXT("Config/")), true, *Platform);
|
|
bool bDataCryptoRequired = false;
|
|
EngineConfig.GetBool(TEXT("PlatformCrypto"), TEXT("PlatformRequiresDataCrypto"), bDataCryptoRequired);
|
|
|
|
if (!bDataCryptoRequired)
|
|
{
|
|
return;
|
|
}
|
|
|
|
FConfigFile ConfigFile;
|
|
FConfigCacheIni::LoadExternalIniFile(ConfigFile, TEXT("Crypto"), *FPaths::Combine(EngineDir, TEXT("Config\\")), *FPaths::Combine(ProjectDir, TEXT("Config/")), true, *Platform);
|
|
bool bSignPak = false;
|
|
bool bEncryptPakIniFiles = false;
|
|
bool bEncryptPakIndex = false;
|
|
bool bEncryptAssets = false;
|
|
bool bEncryptPak = false;
|
|
|
|
if (ConfigFile.Num())
|
|
{
|
|
UE_LOG(LogIoStore, Display, TEXT("Using new format crypto.ini files for crypto configuration"));
|
|
|
|
static const TCHAR* SectionName = TEXT("/Script/CryptoKeys.CryptoKeysSettings");
|
|
|
|
ConfigFile.GetBool(SectionName, TEXT("bEnablePakSigning"), bSignPak);
|
|
ConfigFile.GetBool(SectionName, TEXT("bEncryptPakIniFiles"), bEncryptPakIniFiles);
|
|
ConfigFile.GetBool(SectionName, TEXT("bEncryptPakIndex"), bEncryptPakIndex);
|
|
ConfigFile.GetBool(SectionName, TEXT("bEncryptAssets"), bEncryptAssets);
|
|
bEncryptPak = bEncryptPakIniFiles || bEncryptPakIndex || bEncryptAssets;
|
|
|
|
if (bSignPak)
|
|
{
|
|
FString PublicExpBase64, PrivateExpBase64, ModulusBase64;
|
|
ConfigFile.GetString(SectionName, TEXT("SigningPublicExponent"), PublicExpBase64);
|
|
ConfigFile.GetString(SectionName, TEXT("SigningPrivateExponent"), PrivateExpBase64);
|
|
ConfigFile.GetString(SectionName, TEXT("SigningModulus"), ModulusBase64);
|
|
|
|
TArray<uint8> PublicExp, PrivateExp, Modulus;
|
|
FBase64::Decode(PublicExpBase64, PublicExp);
|
|
FBase64::Decode(PrivateExpBase64, PrivateExp);
|
|
FBase64::Decode(ModulusBase64, Modulus);
|
|
|
|
OutCryptoSettings.SigningKey = FRSA::CreateKey(PublicExp, PrivateExp, Modulus);
|
|
|
|
UE_LOG(LogIoStore, Display, TEXT("Parsed signature keys from config files."));
|
|
}
|
|
|
|
if (bEncryptPak)
|
|
{
|
|
FString EncryptionKeyString;
|
|
ConfigFile.GetString(SectionName, TEXT("EncryptionKey"), EncryptionKeyString);
|
|
|
|
if (EncryptionKeyString.Len() > 0)
|
|
{
|
|
TArray<uint8> Key;
|
|
FBase64::Decode(EncryptionKeyString, Key);
|
|
check(Key.Num() == sizeof(FAES::FAESKey::Key));
|
|
FNamedAESKey NewKey;
|
|
NewKey.Name = TEXT("Default");
|
|
NewKey.Guid = FGuid();
|
|
FMemory::Memcpy(NewKey.Key.Key, &Key[0], sizeof(FAES::FAESKey::Key));
|
|
OutCryptoSettings.EncryptionKeys.Add(NewKey.Guid, NewKey);
|
|
UE_LOG(LogIoStore, Display, TEXT("Parsed AES encryption key from config files."));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
static const TCHAR* SectionName = TEXT("Core.Encryption");
|
|
|
|
UE_LOG(LogIoStore, Display, TEXT("Using old format encryption.ini files for crypto configuration"));
|
|
|
|
FConfigCacheIni::LoadExternalIniFile(ConfigFile, TEXT("Encryption"), *FPaths::Combine(EngineDir, TEXT("Config\\")), *FPaths::Combine(ProjectDir, TEXT("Config/")), true, *Platform);
|
|
ConfigFile.GetBool(SectionName, TEXT("SignPak"), bSignPak);
|
|
ConfigFile.GetBool(SectionName, TEXT("EncryptPak"), bEncryptPak);
|
|
|
|
if (bSignPak)
|
|
{
|
|
FString RSAPublicExp, RSAPrivateExp, RSAModulus;
|
|
ConfigFile.GetString(SectionName, TEXT("rsa.publicexp"), RSAPublicExp);
|
|
ConfigFile.GetString(SectionName, TEXT("rsa.privateexp"), RSAPrivateExp);
|
|
ConfigFile.GetString(SectionName, TEXT("rsa.modulus"), RSAModulus);
|
|
|
|
//TODO: Fix me!
|
|
//OutSigningKey.PrivateKey.Exponent.Parse(RSAPrivateExp);
|
|
//OutSigningKey.PrivateKey.Modulus.Parse(RSAModulus);
|
|
//OutSigningKey.PublicKey.Exponent.Parse(RSAPublicExp);
|
|
//OutSigningKey.PublicKey.Modulus = OutSigningKey.PrivateKey.Modulus;
|
|
|
|
UE_LOG(LogIoStore, Display, TEXT("Parsed signature keys from config files."));
|
|
}
|
|
|
|
if (bEncryptPak)
|
|
{
|
|
FString EncryptionKeyString;
|
|
ConfigFile.GetString(SectionName, TEXT("aes.key"), EncryptionKeyString);
|
|
FNamedAESKey NewKey;
|
|
NewKey.Name = TEXT("Default");
|
|
NewKey.Guid = FGuid();
|
|
if (EncryptionKeyString.Len() == 32 && TCString<TCHAR>::IsPureAnsi(*EncryptionKeyString))
|
|
{
|
|
for (int32 Index = 0; Index < 32; ++Index)
|
|
{
|
|
NewKey.Key.Key[Index] = (uint8)EncryptionKeyString[Index];
|
|
}
|
|
OutCryptoSettings.EncryptionKeys.Add(NewKey.Guid, NewKey);
|
|
UE_LOG(LogIoStore, Display, TEXT("Parsed AES encryption key from config files."));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogIoStore, Display, TEXT("Using command line for crypto configuration"));
|
|
|
|
FString EncryptionKeyString;
|
|
FParse::Value(CmdLine, TEXT("aes="), EncryptionKeyString, false);
|
|
|
|
if (EncryptionKeyString.Len() > 0)
|
|
{
|
|
UE_LOG(LogIoStore, Warning, TEXT("A legacy command line syntax is being used for crypto config. Please update to using the -cryptokey parameter as soon as possible as this mode is deprecated"));
|
|
|
|
FNamedAESKey NewKey;
|
|
NewKey.Name = TEXT("Default");
|
|
NewKey.Guid = FGuid();
|
|
const uint32 RequiredKeyLength = sizeof(NewKey.Key);
|
|
|
|
// Error checking
|
|
if (EncryptionKeyString.Len() < RequiredKeyLength)
|
|
{
|
|
UE_LOG(LogIoStore, Fatal, TEXT("AES encryption key must be %d characters long"), RequiredKeyLength);
|
|
}
|
|
|
|
if (EncryptionKeyString.Len() > RequiredKeyLength)
|
|
{
|
|
UE_LOG(LogIoStore, Warning, TEXT("AES encryption key is more than %d characters long, so will be truncated!"), RequiredKeyLength);
|
|
EncryptionKeyString.LeftInline(RequiredKeyLength);
|
|
}
|
|
|
|
if (!FCString::IsPureAnsi(*EncryptionKeyString))
|
|
{
|
|
UE_LOG(LogIoStore, Fatal, TEXT("AES encryption key must be a pure ANSI string!"));
|
|
}
|
|
|
|
ANSICHAR* AsAnsi = TCHAR_TO_ANSI(*EncryptionKeyString);
|
|
check(TCString<ANSICHAR>::Strlen(AsAnsi) == RequiredKeyLength);
|
|
FMemory::Memcpy(NewKey.Key.Key, AsAnsi, RequiredKeyLength);
|
|
OutCryptoSettings.EncryptionKeys.Add(NewKey.Guid, NewKey);
|
|
UE_LOG(LogIoStore, Display, TEXT("Parsed AES encryption key from command line."));
|
|
}
|
|
}
|
|
|
|
FString EncryptionKeyOverrideGuidString;
|
|
FGuid EncryptionKeyOverrideGuid;
|
|
if (FParse::Value(CmdLine, TEXT("EncryptionKeyOverrideGuid="), EncryptionKeyOverrideGuidString))
|
|
{
|
|
UE_LOG(LogIoStore, Display, TEXT("Using encryption key override '%s'"), *EncryptionKeyOverrideGuidString);
|
|
FGuid::Parse(EncryptionKeyOverrideGuidString, EncryptionKeyOverrideGuid);
|
|
}
|
|
OutCryptoSettings.MasterEncryptionKey = OutCryptoSettings.EncryptionKeys.Find(EncryptionKeyOverrideGuid);
|
|
}
|
|
|
|
class FNameMapBuilder
|
|
{
|
|
public:
|
|
void SetNameMapType(FMappedName::EType InNameMapType)
|
|
{
|
|
NameMapType = InNameMapType;
|
|
}
|
|
|
|
void AddName(const FName& Name)
|
|
{
|
|
const FNameEntryId ComparisonIndex = Name.GetComparisonIndex();
|
|
const FNameEntryId DisplayIndex = Name.GetDisplayIndex();
|
|
NameMap.Add(DisplayIndex);
|
|
int32 Index = NameMap.Num();
|
|
NameIndices.Add(ComparisonIndex, Index);
|
|
}
|
|
|
|
void MarkNamesAsReferenced(const TArray<FName>& Names, TArray<int32>& OutNameIndices)
|
|
{
|
|
for (const FName& Name : Names)
|
|
{
|
|
const FNameEntryId ComparisonIndex = Name.GetComparisonIndex();
|
|
const FNameEntryId DisplayIndex = Name.GetDisplayIndex();
|
|
int32& Index = NameIndices.FindOrAdd(ComparisonIndex);
|
|
if (Index == 0)
|
|
{
|
|
NameMap.Add(DisplayIndex);
|
|
Index = NameMap.Num();
|
|
}
|
|
|
|
OutNameIndices.Add(Index - 1);
|
|
}
|
|
}
|
|
|
|
void MarkNameAsReferenced(const FName& Name)
|
|
{
|
|
const FNameEntryId ComparisonIndex = Name.GetComparisonIndex();
|
|
const FNameEntryId DisplayIndex = Name.GetDisplayIndex();
|
|
int32& Index = NameIndices.FindOrAdd(ComparisonIndex);
|
|
if (Index == 0)
|
|
{
|
|
NameMap.Add(DisplayIndex);
|
|
Index = NameMap.Num();
|
|
}
|
|
#if OUTPUT_NAMEMAP_CSV
|
|
// debug counts
|
|
{
|
|
const int32 Number = Name.GetNumber();
|
|
TTuple<int32, int32, int32>& Counts = DebugNameCounts.FindOrAdd(ComparisonIndex);
|
|
|
|
if (Number == 0)
|
|
{
|
|
++Counts.Get<0>();
|
|
}
|
|
else
|
|
{
|
|
++Counts.Get<1>();
|
|
if (Number > Counts.Get<2>())
|
|
{
|
|
Counts.Get<2>() = Number;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
FMappedName MapName(const FName& Name) const
|
|
{
|
|
const FNameEntryId Id = Name.GetComparisonIndex();
|
|
const int32* Index = NameIndices.Find(Id);
|
|
check(Index);
|
|
return FMappedName::Create(*Index - 1, Name.GetNumber(), NameMapType);
|
|
}
|
|
|
|
const TArray<FNameEntryId>& GetNameMap() const
|
|
{
|
|
return NameMap;
|
|
}
|
|
|
|
friend FArchive& operator<<(FArchive& Ar, FNameMapBuilder& NameMapBuilder)
|
|
{
|
|
if (Ar.IsSaving())
|
|
{
|
|
int32 NameCount = NameMapBuilder.NameMap.Num();
|
|
Ar << NameCount;
|
|
for (FNameEntryId NameEntryId : NameMapBuilder.NameMap)
|
|
{
|
|
const FNameEntry* NameEntry = FName::GetEntry(NameEntryId);
|
|
NameEntry->Write(Ar);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int32 NameCount;
|
|
Ar << NameCount;
|
|
for (int32 NameIndex = 0; NameIndex < NameCount; ++NameIndex)
|
|
{
|
|
FNameEntrySerialized NameEntrySerialized(ENAME_LinkerConstructor);
|
|
Ar << NameEntrySerialized;
|
|
FName Name(NameEntrySerialized);
|
|
NameMapBuilder.NameMap.Add(Name.GetDisplayIndex());
|
|
NameMapBuilder.NameIndices.Add(Name.GetComparisonIndex(), NameIndex + 1);
|
|
}
|
|
}
|
|
return Ar;
|
|
}
|
|
|
|
#if OUTPUT_NAMEMAP_CSV
|
|
void SaveCsv(const FString& CsvFilePath)
|
|
{
|
|
{
|
|
TUniquePtr<FArchive> CsvArchive(IFileManager::Get().CreateFileWriter(*CsvFilePath));
|
|
if (CsvArchive)
|
|
{
|
|
TCHAR Name[FName::StringBufferSize];
|
|
ANSICHAR Line[MAX_SPRINTF + FName::StringBufferSize];
|
|
ANSICHAR Header[] = "Length\tMaxNumber\tNumberCount\tBaseCount\tTotalCount\tFName\n";
|
|
CsvArchive->Serialize(Header, sizeof(Header) - 1);
|
|
for (auto& Counts : DebugNameCounts)
|
|
{
|
|
const int32 NameLen = FName::CreateFromDisplayId(Counts.Key, 0).ToString(Name);
|
|
FCStringAnsi::Sprintf(Line, "%d\t%d\t%d\t%d\t%d\t",
|
|
NameLen, Counts.Value.Get<2>(), Counts.Value.Get<1>(), Counts.Value.Get<0>(), Counts.Value.Get<0>() + Counts.Value.Get<1>());
|
|
ANSICHAR* L = Line + FCStringAnsi::Strlen(Line);
|
|
const TCHAR* N = Name;
|
|
while (*N)
|
|
{
|
|
*L++ = CharCast<ANSICHAR,TCHAR>(*N++);
|
|
}
|
|
*L++ = '\n';
|
|
CsvArchive.Get()->Serialize(Line, L - Line);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void Empty()
|
|
{
|
|
NameIndices.Empty();
|
|
NameMap.Empty();
|
|
#if OUTPUT_NAMEMAP_CSV
|
|
DebugNameCounts.Empty();
|
|
#endif
|
|
}
|
|
private:
|
|
TMap<FNameEntryId, int32> NameIndices;
|
|
TArray<FNameEntryId> NameMap;
|
|
#if OUTPUT_NAMEMAP_CSV
|
|
TMap<FNameEntryId, TTuple<int32,int32,int32>> DebugNameCounts; // <Number0Count,OtherNumberCount,MaxNumber>
|
|
#endif
|
|
FMappedName::EType NameMapType = FMappedName::EType::Package;
|
|
};
|
|
|
|
class FNameReaderProxyArchive
|
|
: public FArchiveProxy
|
|
{
|
|
const TArray<FNameEntryId>& NameMap;
|
|
|
|
public:
|
|
using FArchiveProxy::FArchiveProxy;
|
|
|
|
FNameReaderProxyArchive(FArchive& InAr, const TArray<FNameEntryId>& InNameMap)
|
|
: FArchiveProxy(InAr)
|
|
, NameMap(InNameMap)
|
|
{
|
|
// Replicate the filter editor only state of the InnerArchive as FArchiveProxy will
|
|
// not intercept it.
|
|
FArchive::SetFilterEditorOnly(InAr.IsFilterEditorOnly());
|
|
}
|
|
|
|
FArchive& operator<<(FName& Name)
|
|
{
|
|
int32 NameIndex, Number;
|
|
InnerArchive << NameIndex << Number;
|
|
|
|
if (!NameMap.IsValidIndex(NameIndex))
|
|
{
|
|
UE_LOG(LogStreaming, Fatal, TEXT("Bad name index %i/%i"), NameIndex, NameMap.Num());
|
|
}
|
|
|
|
const FNameEntryId MappedName = NameMap[NameIndex];
|
|
Name = FName::CreateFromDisplayId(MappedName, Number);
|
|
|
|
return *this;
|
|
}
|
|
};
|
|
|
|
struct FContainerSourceFile
|
|
{
|
|
FString NormalizedPath;
|
|
bool bNeedsCompression = false;
|
|
bool bNeedsEncryption = false;
|
|
};
|
|
|
|
struct FContainerSourceSpec
|
|
{
|
|
FName Name;
|
|
FString OutputPath;
|
|
TArray<FContainerSourceFile> SourceFiles;
|
|
FString PatchTargetFile;
|
|
TArray<FString> PatchSourceContainerFiles;
|
|
FGuid EncryptionKeyOverrideGuid;
|
|
bool bGenerateDiffPatch = false;
|
|
};
|
|
|
|
struct FCookedFileStatData
|
|
{
|
|
enum EFileExt { UMap, UAsset, UExp, UBulk, UPtnl, UMappedBulk };
|
|
enum EFileType { PackageHeader, PackageData, BulkData };
|
|
|
|
int64 FileSize = 0;
|
|
EFileType FileType = PackageHeader;
|
|
EFileExt FileExt = UMap;
|
|
};
|
|
|
|
using FCookedFileStatMap = TMap<FString, FCookedFileStatData>;
|
|
|
|
struct FPackage;
|
|
struct FContainerTargetSpec;
|
|
|
|
struct FContainerTargetFile
|
|
{
|
|
FContainerTargetSpec* ContainerTarget = nullptr;
|
|
FPackage* Package = nullptr;
|
|
FString NormalizedSourcePath;
|
|
FString TargetPath;
|
|
uint64 SourceSize = 0;
|
|
uint64 TargetSize = 0;
|
|
uint64 Offset = 0;
|
|
FIoChunkId ChunkId;
|
|
FIoChunkHash ChunkHash;
|
|
TArray<uint8> PackageHeaderData;
|
|
TArray<int32> NameIndices;
|
|
FNameMapBuilder* NameMapBuilder = nullptr;
|
|
bool bIsBulkData = false;
|
|
bool bIsOptionalBulkData = false;
|
|
bool bIsMemoryMappedBulkData = false;
|
|
bool bForceUncompressed = false;
|
|
|
|
int64 UGraphSize = 0;
|
|
int64 NameMapSize = 0;
|
|
int64 ImportMapSize = 0;
|
|
int64 ExportMapSize = 0;
|
|
int64 ExportBundlesHeaderSize = 0;
|
|
|
|
uint64 HeaderSerialSize = 0;
|
|
};
|
|
|
|
struct FIoStoreArguments
|
|
{
|
|
FString GlobalContainerPath;
|
|
FString CookedDir;
|
|
ITargetPlatform* TargetPlatform = nullptr;
|
|
FString MetaInputDir;
|
|
FString MetaOutputDir;
|
|
TArray<FContainerSourceSpec> Containers;
|
|
FCookedFileStatMap CookedFileStatMap;
|
|
TMap<FName, uint64> GameOrderMap;
|
|
TMap<FName, uint64> CookerOrderMap;
|
|
FKeyChain KeyChain;
|
|
FKeyChain PatchKeyChain;
|
|
FString DLCPluginPath;
|
|
FString DLCName;
|
|
FString BasedOnReleaseVersionPath;
|
|
FAssetRegistryState ReleaseAssetRegistry;
|
|
FReleasedPackages ReleasedPackages;
|
|
bool bSign = false;
|
|
bool bRemapPluginContentToGame = false;
|
|
|
|
bool ShouldCreateContainers() const
|
|
{
|
|
return GlobalContainerPath.Len() > 0 || DLCPluginPath.Len() > 0;
|
|
}
|
|
|
|
bool IsDLC() const
|
|
{
|
|
return DLCPluginPath.Len() > 0;
|
|
}
|
|
};
|
|
|
|
struct FContainerTargetSpec
|
|
{
|
|
FContainerHeader Header;
|
|
FName Name;
|
|
FGuid EncryptionKeyGuid;
|
|
FString OutputPath;
|
|
FIoStoreWriter* IoStoreWriter;
|
|
TArray<FContainerTargetFile> TargetFiles;
|
|
TUniquePtr<FIoStoreEnvironment> IoStoreEnv;
|
|
TArray<TUniquePtr<FIoStoreReader>> PatchSourceReaders;
|
|
FNameMapBuilder LocalNameMapBuilder;
|
|
FNameMapBuilder* NameMapBuilder = &LocalNameMapBuilder;
|
|
EIoContainerFlags ContainerFlags = EIoContainerFlags::None;
|
|
uint32 PackageCount = 0;
|
|
bool bUseLocalNameMap = false;
|
|
bool bGenerateDiffPatch = false;
|
|
};
|
|
|
|
struct FPackageAssetData
|
|
{
|
|
TArray<FObjectImport> ObjectImports;
|
|
TArray<FObjectExport> ObjectExports;
|
|
TArray<FPackageIndex> PreloadDependencies;
|
|
};
|
|
|
|
struct FPackage;
|
|
using FPackageNameMap = TMap<FName, FPackage*>;
|
|
using FPackageIdMap = TMap<FPackageId, FPackage*>;
|
|
using FSourceToLocalizedPackageMultimap = TMultiMap<FPackage*, FPackage*>;
|
|
using FLocalizedToSourceImportIndexMap = TMap<FPackageObjectIndex, FPackageObjectIndex>;
|
|
|
|
static constexpr TCHAR L10NPrefix[] = TEXT("/Game/L10N/");
|
|
static constexpr TCHAR ScriptPrefix[] = TEXT("/Script/");
|
|
|
|
// modified copy from PakFileUtilities
|
|
static FString RemapLocalizationPathIfNeeded(const FString& Path, FString* OutRegion)
|
|
{
|
|
static constexpr int32 L10NPrefixLength = sizeof(L10NPrefix)/sizeof(TCHAR) - 1;
|
|
|
|
int32 FoundIndex = Path.Find(L10NPrefix, ESearchCase::IgnoreCase);
|
|
if (FoundIndex >= 0)
|
|
{
|
|
// Validate the content index is the first one
|
|
int32 ContentIndex = Path.Find(TEXT("/Game/"), ESearchCase::IgnoreCase);
|
|
if (ContentIndex == FoundIndex)
|
|
{
|
|
int32 EndL10NOffset = ContentIndex + L10NPrefixLength;
|
|
int32 NextSlashIndex = Path.Find(TEXT("/"), ESearchCase::IgnoreCase, ESearchDir::FromStart, EndL10NOffset);
|
|
int32 RegionLength = NextSlashIndex - EndL10NOffset;
|
|
if (RegionLength >= 2)
|
|
{
|
|
FString NonLocalizedPath = Path.Mid(0, ContentIndex) + TEXT("/Game") + Path.Mid(NextSlashIndex);
|
|
if (OutRegion)
|
|
{
|
|
*OutRegion = Path.Mid(EndL10NOffset, RegionLength);
|
|
OutRegion->ToLowerInline();
|
|
}
|
|
return NonLocalizedPath;
|
|
}
|
|
}
|
|
}
|
|
return Path;
|
|
}
|
|
|
|
#if OUTPUT_CHUNKID_DIRECTORY
|
|
class FChunkIdCsv
|
|
{
|
|
public:
|
|
|
|
~FChunkIdCsv()
|
|
{
|
|
if (OutputArchive)
|
|
{
|
|
OutputArchive->Flush();
|
|
}
|
|
}
|
|
|
|
void CreateOutputFile(const FString& RootPath)
|
|
{
|
|
const FString OutputFilename = RootPath / TEXT("chunkid_directory.csv");
|
|
OutputArchive.Reset(IFileManager::Get().CreateFileWriter(*OutputFilename));
|
|
if (OutputArchive)
|
|
{
|
|
const ANSICHAR* Output = "NameIndex,NameNumber,ChunkIndex,ChunkType,ChunkIdHash,DebugString\n";
|
|
OutputArchive->Serialize((void*)Output, FPlatformString::Strlen(Output));
|
|
}
|
|
}
|
|
|
|
void AddChunk(uint32 NameIndex, uint32 NameNumber, uint16 ChunkIndex, uint8 ChunkType, uint32 ChunkIdHash, const TCHAR* DebugString)
|
|
{
|
|
ANSICHAR Buffer[MAX_SPRINTF + 1] = { 0 };
|
|
int32 NumOfCharacters = FCStringAnsi::Sprintf(Buffer, "%u,%u,%u,%u,%u,%s\n", NameIndex, NameNumber, ChunkIndex, ChunkType, ChunkIdHash, TCHAR_TO_ANSI(DebugString));
|
|
OutputArchive->Serialize(Buffer, NumOfCharacters);
|
|
}
|
|
|
|
private:
|
|
TUniquePtr<FArchive> OutputArchive;
|
|
};
|
|
FChunkIdCsv ChunkIdCsv;
|
|
|
|
#endif
|
|
|
|
static FIoChunkId CreateChunkId(FPackageId GlobalPackageId, uint16 ChunkIndex, EIoChunkType ChunkType, const TCHAR* DebugString)
|
|
{
|
|
FIoChunkId ChunkId = CreateIoChunkId(GlobalPackageId.Value(), ChunkIndex, ChunkType);
|
|
#if OUTPUT_CHUNKID_DIRECTORY
|
|
ChunkIdCsv.AddChunk(GlobalPackageId, 0, ChunkIndex, (uint8)ChunkType, GetTypeHash(ChunkId), DebugString);
|
|
#endif
|
|
return ChunkId;
|
|
}
|
|
|
|
enum EPreloadDependencyType
|
|
{
|
|
PreloadDependencyType_Create,
|
|
PreloadDependencyType_Serialize,
|
|
};
|
|
|
|
struct FArc
|
|
{
|
|
uint32 FromNodeIndex;
|
|
uint32 ToNodeIndex;
|
|
|
|
bool operator==(const FArc& Other) const
|
|
{
|
|
return ToNodeIndex == Other.ToNodeIndex && FromNodeIndex == Other.FromNodeIndex;
|
|
}
|
|
};
|
|
|
|
struct FExportGraphNode;
|
|
|
|
struct FExportBundle
|
|
{
|
|
TArray<FExportGraphNode*> Nodes;
|
|
uint32 LoadOrder;
|
|
};
|
|
|
|
struct FPackageGraphNode
|
|
{
|
|
FPackage* Package = nullptr;
|
|
mutable bool bTemporaryMark = false;
|
|
mutable bool bPermanentMark = false;
|
|
};
|
|
|
|
class FPackageGraph
|
|
{
|
|
public:
|
|
FPackageGraph()
|
|
{
|
|
|
|
}
|
|
|
|
~FPackageGraph()
|
|
{
|
|
for (FPackageGraphNode* Node : Nodes)
|
|
{
|
|
delete Node;
|
|
}
|
|
}
|
|
|
|
FPackageGraphNode* AddNode(FPackage* Package)
|
|
{
|
|
FPackageGraphNode* Node = new FPackageGraphNode();
|
|
Node->Package = Package;
|
|
Nodes.Add(Node);
|
|
return Node;
|
|
}
|
|
|
|
void AddImportDependency(FPackageGraphNode* FromNode, FPackageGraphNode* ToNode)
|
|
{
|
|
Edges.Add(FromNode, ToNode);
|
|
}
|
|
|
|
TArray<FPackage*> TopologicalSort() const;
|
|
|
|
private:
|
|
TArray<FPackageGraphNode*> Nodes;
|
|
TMultiMap<FPackageGraphNode*, FPackageGraphNode*> Edges;
|
|
};
|
|
|
|
struct FExportGraphNode
|
|
{
|
|
FPackage* Package;
|
|
FExportBundleEntry BundleEntry;
|
|
TSet<FExportGraphNode*> ExternalDependencies;
|
|
TSet<FPackageId> BaseGamePackageDependencies;
|
|
uint64 NodeIndex;
|
|
};
|
|
|
|
class FExportGraph
|
|
{
|
|
public:
|
|
FExportGraph(int32 NumExports, int32 NumPreloadDependencies)
|
|
{
|
|
Nodes.Reserve(NumExports * 2);
|
|
Edges.Reserve(NumExports + NumPreloadDependencies);
|
|
}
|
|
|
|
~FExportGraph()
|
|
{
|
|
for (FExportGraphNode* Node : Nodes)
|
|
{
|
|
delete Node;
|
|
}
|
|
}
|
|
|
|
FExportGraphNode* AddNode(FPackage* Package, const FExportBundleEntry& BundleEntry)
|
|
{
|
|
FExportGraphNode* Node = new FExportGraphNode();
|
|
Node->Package = Package;
|
|
Node->BundleEntry = BundleEntry;
|
|
Node->NodeIndex = Nodes.Num();
|
|
Nodes.Add(Node);
|
|
return Node;
|
|
}
|
|
|
|
void AddInternalDependency(FExportGraphNode* FromNode, FExportGraphNode* ToNode)
|
|
{
|
|
AddEdge(FromNode, ToNode);
|
|
}
|
|
|
|
void AddExternalDependency(FExportGraphNode* FromNode, FExportGraphNode* ToNode)
|
|
{
|
|
AddEdge(FromNode, ToNode);
|
|
ToNode->ExternalDependencies.Add(FromNode);
|
|
}
|
|
|
|
TArray<FExportGraphNode*> ComputeLoadOrder(const TArray<FPackage*>& Packages) const;
|
|
|
|
private:
|
|
void AddEdge(FExportGraphNode* FromNode, FExportGraphNode* ToNode)
|
|
{
|
|
Edges.Add(FromNode, ToNode);
|
|
}
|
|
|
|
TArray<FExportGraphNode*> TopologicalSort() const;
|
|
|
|
TArray<FExportGraphNode*> Nodes;
|
|
TMultiMap<FExportGraphNode*, FExportGraphNode*> Edges;
|
|
};
|
|
|
|
#if OUTPUT_DEBUG_PACKAGE_HASHES
|
|
struct FPackageHashes
|
|
{
|
|
FSHAHash UAssetHash;
|
|
FSHAHash UExpHash;
|
|
FIoChunkHash ExportBundleHash;
|
|
};
|
|
#endif
|
|
|
|
struct FPackage
|
|
{
|
|
FName Name;
|
|
FName SourcePackageName; // for localized packages
|
|
FString FileName;
|
|
FPackageId GlobalPackageId;
|
|
FString Region; // for localized packages
|
|
FPackageId SourceGlobalPackageId; // for localized packages
|
|
FPackageId RedirectedPackageId;
|
|
uint32 PackageFlags = 0;
|
|
uint32 CookedHeaderSize = 0;
|
|
int32 NameCount = 0;
|
|
int32 ImportCount = 0;
|
|
int32 PreloadDependencyCount = 0;
|
|
int32 ExportCount = 0;
|
|
int32 ImportIndexOffset = -1;
|
|
int32 ExportIndexOffset = -1;
|
|
int32 PreloadIndexOffset = -1;
|
|
int64 UExpSize = 0;
|
|
int64 UAssetSize = 0;
|
|
int64 SummarySize = 0;
|
|
uint64 ExportsSerialSize = 0;
|
|
bool bIsLocalizedAndConformed = false;
|
|
|
|
TArray<FPackage*> ImportedPackages;
|
|
TArray<FPackageId> ImportedPackageIds;
|
|
|
|
TArray<FName> SummaryNames;
|
|
FNameMapBuilder LocalNameMapBuilder;
|
|
|
|
TArray<FPackageObjectIndex> Imports;
|
|
TArray<int32> Exports;
|
|
TMap<FPackageId, TArray<FArc>> ExternalArcs;
|
|
|
|
TArray<FExportBundle> ExportBundles;
|
|
TMap<FExportGraphNode*, uint32> ExportBundleMap;
|
|
|
|
TArray<FExportGraphNode*> CreateExportNodes;
|
|
TArray<FExportGraphNode*> SerializeExportNodes;
|
|
|
|
TArray<FExportGraphNode*> NodesWithNoIncomingEdges;
|
|
FPackageGraphNode* Node = nullptr;
|
|
|
|
uint64 DiskLayoutOrder = MAX_uint64;
|
|
#if OUTPUT_DEBUG_PACKAGE_HASHES
|
|
FPackageHashes Hashes;
|
|
#endif
|
|
};
|
|
|
|
struct FCircularImportChain
|
|
{
|
|
TArray<FName> SortedNames;
|
|
TArray<FPackage*> Packages;
|
|
uint32 Hash = 0;
|
|
|
|
FCircularImportChain()
|
|
{
|
|
Packages.Reserve(128);
|
|
}
|
|
|
|
void Add(FPackage* Package)
|
|
{
|
|
Packages.Add(Package);
|
|
}
|
|
|
|
void Pop()
|
|
{
|
|
Packages.Pop();
|
|
}
|
|
|
|
int32 Num()
|
|
{
|
|
return Packages.Num();
|
|
}
|
|
|
|
void SortAndGenerateHash()
|
|
{
|
|
SortedNames.Empty(Packages.Num());
|
|
for (FPackage* Package : Packages)
|
|
{
|
|
SortedNames.Emplace(Package->Name);
|
|
}
|
|
SortedNames.Sort(FNameLexicalLess());
|
|
Hash = CityHash32((char*)SortedNames.GetData(), SortedNames.Num() * SortedNames.GetTypeSize());
|
|
}
|
|
|
|
FString ToString()
|
|
{
|
|
FString Result = FString::Printf(TEXT("%d:%u: "), SortedNames.Num(), Hash);
|
|
for (const FName& Name : SortedNames)
|
|
{
|
|
Result.Append(Name.ToString());
|
|
Result.Append(TEXT(" -> "));
|
|
}
|
|
Result.Append(SortedNames[0].ToString());
|
|
return Result;
|
|
}
|
|
|
|
bool operator==(const FCircularImportChain& Other) const
|
|
{
|
|
return Hash == Other.Hash && SortedNames == Other.SortedNames;
|
|
}
|
|
|
|
friend FORCEINLINE uint32 GetTypeHash(const FCircularImportChain& In)
|
|
{
|
|
return In.Hash;
|
|
}
|
|
};
|
|
|
|
TArray<FPackage*> FPackageGraph::TopologicalSort() const
|
|
{
|
|
TMap<const FPackageGraphNode*, TArray<const FPackageGraphNode*>> SortedEdges;
|
|
for (const auto& KV : Edges)
|
|
{
|
|
const FPackageGraphNode* Source = KV.Key;
|
|
const FPackageGraphNode* Target = KV.Value;
|
|
TArray<const FPackageGraphNode*>& SourceArray = SortedEdges.FindOrAdd(Source);
|
|
SourceArray.Add(Target);
|
|
}
|
|
for (auto& KV : SortedEdges)
|
|
{
|
|
TArray<const FPackageGraphNode*>& SourceArray = KV.Value;
|
|
Algo::Sort(SourceArray, [](const FPackageGraphNode* A, const FPackageGraphNode* B)
|
|
{
|
|
return A->Package->GlobalPackageId < B->Package->GlobalPackageId;
|
|
});
|
|
}
|
|
|
|
TArray<FPackage*> Result;
|
|
Result.Reserve(Nodes.Num());
|
|
|
|
struct
|
|
{
|
|
void Visit(const FPackageGraphNode* Node)
|
|
{
|
|
if (Node->bPermanentMark || Node->bTemporaryMark)
|
|
{
|
|
return;
|
|
}
|
|
Node->bTemporaryMark = true;
|
|
TArray<const FPackageGraphNode*>* TargetNodes = Edges.Find(Node);
|
|
if(TargetNodes)
|
|
{
|
|
for (const FPackageGraphNode* ToNode : *TargetNodes)
|
|
{
|
|
Visit(ToNode);
|
|
}
|
|
}
|
|
Node->bTemporaryMark = false;
|
|
Node->bPermanentMark = true;
|
|
Result.Add(Node->Package);
|
|
}
|
|
|
|
TMap<const FPackageGraphNode*, TArray<const FPackageGraphNode*>>& Edges;
|
|
TArray<FPackage*>& Result;
|
|
|
|
} Visitor{ SortedEdges, Result };
|
|
|
|
for (FPackageGraphNode* Node : Nodes)
|
|
{
|
|
Visitor.Visit(Node);
|
|
}
|
|
check(Result.Num() == Nodes.Num());
|
|
|
|
Algo::Reverse(Result);
|
|
return Result;
|
|
}
|
|
|
|
TArray<FExportGraphNode*> FExportGraph::ComputeLoadOrder(const TArray<FPackage*>& Packages) const
|
|
{
|
|
IOSTORE_CPU_SCOPE(ComputeLoadOrder);
|
|
FPackageGraph PackageGraph;
|
|
{
|
|
for (FPackage* Package : Packages)
|
|
{
|
|
Package->Node = PackageGraph.AddNode(Package);
|
|
}
|
|
for (FPackage* Package : Packages)
|
|
{
|
|
for (FPackage* ImportedPackage : Package->ImportedPackages)
|
|
{
|
|
PackageGraph.AddImportDependency(ImportedPackage->Node, Package->Node);
|
|
}
|
|
}
|
|
}
|
|
|
|
TArray<FPackage*> SortedPackages = PackageGraph.TopologicalSort();
|
|
|
|
int32 NodeCount = Nodes.Num();
|
|
TArray<uint32> NodesIncomingEdgeCount;
|
|
NodesIncomingEdgeCount.AddZeroed(NodeCount);
|
|
|
|
TMultiMap<FExportGraphNode*, FExportGraphNode*> EdgesCopy = Edges;
|
|
for (auto& KV : Edges)
|
|
{
|
|
FExportGraphNode* ToNode = KV.Value;
|
|
++NodesIncomingEdgeCount[ToNode->NodeIndex];
|
|
}
|
|
|
|
TArray<FExportGraphNode*> LoadOrder;
|
|
LoadOrder.Reserve(NodeCount);
|
|
|
|
auto NodeSorter = [](const FExportGraphNode& A, const FExportGraphNode& B)
|
|
{
|
|
if (A.BundleEntry.LocalExportIndex == B.BundleEntry.LocalExportIndex)
|
|
{
|
|
return A.BundleEntry.CommandType < B.BundleEntry.CommandType;
|
|
}
|
|
return A.BundleEntry.LocalExportIndex < B.BundleEntry.LocalExportIndex;
|
|
};
|
|
|
|
for (int32 NodeIndex = 0; NodeIndex < NodeCount; ++NodeIndex)
|
|
{
|
|
if (NodesIncomingEdgeCount[NodeIndex] == 0)
|
|
{
|
|
FExportGraphNode* Node = Nodes[NodeIndex];
|
|
Node->Package->NodesWithNoIncomingEdges.HeapPush(Node, NodeSorter);
|
|
}
|
|
}
|
|
while (LoadOrder.Num() < NodeCount)
|
|
{
|
|
for (FPackage* Package : SortedPackages)
|
|
{
|
|
while (Package->NodesWithNoIncomingEdges.Num() > 0)
|
|
{
|
|
FExportGraphNode* RemovedNode;
|
|
Package->NodesWithNoIncomingEdges.HeapPop(RemovedNode, NodeSorter, false);
|
|
LoadOrder.Add(RemovedNode);
|
|
for (auto EdgeIt = EdgesCopy.CreateKeyIterator(RemovedNode); EdgeIt; ++EdgeIt)
|
|
{
|
|
FExportGraphNode* ToNode = EdgeIt.Value();
|
|
if (--NodesIncomingEdgeCount[ToNode->NodeIndex] == 0)
|
|
{
|
|
ToNode->Package->NodesWithNoIncomingEdges.HeapPush(ToNode, NodeSorter);
|
|
}
|
|
EdgeIt.RemoveCurrent();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return LoadOrder;
|
|
}
|
|
|
|
static void AddInternalExportArc(FExportGraph& ExportGraph, FPackage& Package, uint32 FromExportIndex, EPreloadDependencyType FromPhase, uint32 ToExportIndex, EPreloadDependencyType ToPhase)
|
|
{
|
|
FExportGraphNode* FromNode = FromPhase == PreloadDependencyType_Create ? Package.CreateExportNodes[FromExportIndex] : Package.SerializeExportNodes[FromExportIndex];
|
|
FExportGraphNode* ToNode = ToPhase == PreloadDependencyType_Create ? Package.CreateExportNodes[ToExportIndex] : Package.SerializeExportNodes[ToExportIndex];
|
|
ExportGraph.AddInternalDependency(FromNode, ToNode);
|
|
}
|
|
|
|
static void AddExternalExportArc(FExportGraph& ExportGraph, FPackage& FromPackage, uint32 FromExportIndex, EPreloadDependencyType FromPhase, FPackage& ToPackage, uint32 ToExportIndex, EPreloadDependencyType ToPhase)
|
|
{
|
|
FExportGraphNode* FromNode = FromPhase == PreloadDependencyType_Create ? FromPackage.CreateExportNodes[FromExportIndex] : FromPackage.SerializeExportNodes[FromExportIndex];
|
|
FExportGraphNode* ToNode = ToPhase == PreloadDependencyType_Create ? ToPackage.CreateExportNodes[ToExportIndex] : ToPackage.SerializeExportNodes[ToExportIndex];
|
|
ExportGraph.AddExternalDependency(FromNode, ToNode);
|
|
}
|
|
|
|
static void AddBaseGamePackageArc(FExportGraph& ExportGraph, FPackageId FromPackageId, FPackage& ToPackage, uint32 ToExportIndex, EPreloadDependencyType ToPhase)
|
|
{
|
|
FExportGraphNode* ToNode = ToPhase == PreloadDependencyType_Create ? ToPackage.CreateExportNodes[ToExportIndex] : ToPackage.SerializeExportNodes[ToExportIndex];
|
|
ToNode->BaseGamePackageDependencies.Add(FromPackageId);
|
|
}
|
|
|
|
static void AddUniqueExternalBundleArc(FPackageId FromPackageId, uint32 FromBundleIndex, FPackage& ToPackage, uint32 ToBundleIndex)
|
|
{
|
|
TArray<FArc>& ExternalArcs = ToPackage.ExternalArcs.FindOrAdd(FromPackageId);
|
|
ExternalArcs.AddUnique({ FromBundleIndex, ToBundleIndex });
|
|
}
|
|
|
|
static void BuildBundles(FExportGraph& ExportGraph, const TArray<FPackage*>& Packages)
|
|
{
|
|
IOSTORE_CPU_SCOPE(BuildBundles)
|
|
UE_LOG(LogIoStore, Display, TEXT("Building bundles..."));
|
|
|
|
TArray<FExportGraphNode*> ExportLoadOrder = ExportGraph.ComputeLoadOrder(Packages);
|
|
FPackage* LastPackage = nullptr;
|
|
uint32 BundleLoadOrder = 0;
|
|
for (FExportGraphNode* Node : ExportLoadOrder)
|
|
{
|
|
FPackage* Package = Node->Package;
|
|
check(Package);
|
|
if (!Package)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
uint32 BundleIndex;
|
|
FExportBundle* Bundle;
|
|
if (Package != LastPackage)
|
|
{
|
|
BundleIndex = Package->ExportBundles.Num();
|
|
Bundle = &Package->ExportBundles.AddDefaulted_GetRef();
|
|
Bundle->LoadOrder = BundleLoadOrder++;
|
|
LastPackage = Package;
|
|
}
|
|
else
|
|
{
|
|
BundleIndex = Package->ExportBundles.Num() - 1;
|
|
Bundle = &Package->ExportBundles[BundleIndex];
|
|
}
|
|
for (FExportGraphNode* ExternalDependency : Node->ExternalDependencies)
|
|
{
|
|
uint32* FindDependentBundleIndex = ExternalDependency->Package->ExportBundleMap.Find(ExternalDependency);
|
|
check(FindDependentBundleIndex);
|
|
check(*FindDependentBundleIndex < uint32(ExternalDependency->Package->ExportBundles.Num()));
|
|
AddUniqueExternalBundleArc(ExternalDependency->Package->GlobalPackageId, *FindDependentBundleIndex, *Package, BundleIndex);
|
|
}
|
|
for (FPackageId FromPackageId : Node->BaseGamePackageDependencies)
|
|
{
|
|
AddUniqueExternalBundleArc(FromPackageId, MAX_uint32, *Package, BundleIndex);
|
|
}
|
|
Bundle->Nodes.Add(Node);
|
|
Package->ExportBundleMap.Add(Node, BundleIndex);
|
|
}
|
|
}
|
|
|
|
static void AssignPackagesDiskOrder(
|
|
const TArray<FPackage*>& Packages,
|
|
const TMap<FName, uint64> GameOrderMap,
|
|
const TMap<FName, uint64>& CookerOrderMap)
|
|
{
|
|
struct FCluster
|
|
{
|
|
TArray<FPackage*> Packages;
|
|
};
|
|
|
|
TArray<FCluster*> Clusters;
|
|
TSet<FPackage*> AssignedPackages;
|
|
TArray<FPackage*> ProcessStack;
|
|
|
|
struct FPackageAndOrder
|
|
{
|
|
FPackage* Package;
|
|
uint64 GameOpenOrder;
|
|
uint64 CookerOpenOrder;
|
|
|
|
bool operator<(const FPackageAndOrder& Other) const
|
|
{
|
|
if (GameOpenOrder != Other.GameOpenOrder)
|
|
{
|
|
return GameOpenOrder < Other.GameOpenOrder;
|
|
}
|
|
if (CookerOpenOrder != Other.CookerOpenOrder)
|
|
{
|
|
return CookerOpenOrder < Other.CookerOpenOrder;
|
|
}
|
|
// Fallback to reverse bundle order (so that packages are considered before their imports)
|
|
return Package->ExportBundles[0].LoadOrder > Other.Package->ExportBundles[0].LoadOrder;
|
|
}
|
|
};
|
|
|
|
TArray<FPackageAndOrder> SortedPackages;
|
|
SortedPackages.Reserve(Packages.Num());
|
|
for (FPackage* Package : Packages)
|
|
{
|
|
FPackageAndOrder& Entry = SortedPackages.AddDefaulted_GetRef();
|
|
Entry.Package = Package;
|
|
const uint64* FindGameOpenOrder = GameOrderMap.Find(Package->Name);
|
|
Entry.GameOpenOrder = FindGameOpenOrder ? *FindGameOpenOrder : MAX_uint64;
|
|
const uint64* FindCookerOpenOrder = CookerOrderMap.Find(Package->Name);
|
|
/*if (!FindCookerOpenOrder)
|
|
{
|
|
UE_LOG(LogIoStore, Warning, TEXT("Missing cooker order for package: %s"), *Package->Name.ToString());
|
|
}*/
|
|
Entry.CookerOpenOrder = FindCookerOpenOrder ? *FindCookerOpenOrder : MAX_uint64;
|
|
}
|
|
bool bHasGameOrder = true;
|
|
bool bHasCookerOrder = true;
|
|
int32 LastAssignedCount = 0;
|
|
Algo::Sort(SortedPackages);
|
|
for (FPackageAndOrder& Entry : SortedPackages)
|
|
{
|
|
if (bHasGameOrder && Entry.GameOpenOrder == MAX_uint64)
|
|
{
|
|
UE_LOG(LogIoStore, Display, TEXT("Ordered %d/%d packages using game open order"), AssignedPackages.Num(), Packages.Num());
|
|
LastAssignedCount = AssignedPackages.Num();
|
|
bHasGameOrder = false;
|
|
}
|
|
if (!bHasGameOrder && bHasCookerOrder && Entry.CookerOpenOrder == MAX_uint64)
|
|
{
|
|
UE_LOG(LogIoStore, Display, TEXT("Ordered %d/%d packages using cooker open order"), AssignedPackages.Num() - LastAssignedCount, Packages.Num() - LastAssignedCount);
|
|
LastAssignedCount = AssignedPackages.Num();
|
|
bHasCookerOrder = false;
|
|
}
|
|
if (!AssignedPackages.Contains(Entry.Package))
|
|
{
|
|
FCluster* Cluster = new FCluster();
|
|
Clusters.Add(Cluster);
|
|
ProcessStack.Push(Entry.Package);
|
|
|
|
while (ProcessStack.Num())
|
|
{
|
|
FPackage* PackageToProcess = ProcessStack.Pop(false);
|
|
if (!AssignedPackages.Contains(PackageToProcess))
|
|
{
|
|
AssignedPackages.Add(PackageToProcess);
|
|
if (PackageToProcess->ExportBundles.Num())
|
|
{
|
|
Cluster->Packages.Add(PackageToProcess);
|
|
}
|
|
for (FPackage* ImportedPackage : PackageToProcess->ImportedPackages)
|
|
{
|
|
ProcessStack.Push(ImportedPackage);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
UE_LOG(LogIoStore, Display, TEXT("Ordered %d packages using fallback bundle order"), AssignedPackages.Num() - LastAssignedCount);
|
|
|
|
check(AssignedPackages.Num() == Packages.Num());
|
|
|
|
for (FCluster* Cluster : Clusters)
|
|
{
|
|
Algo::Sort(Cluster->Packages, [](const FPackage* A, const FPackage* B)
|
|
{
|
|
return A->ExportBundles[0].LoadOrder < B->ExportBundles[0].LoadOrder;
|
|
});
|
|
}
|
|
|
|
uint64 LayoutIndex = 0;
|
|
for (FCluster* Cluster : Clusters)
|
|
{
|
|
for (FPackage* Package : Cluster->Packages)
|
|
{
|
|
Package->DiskLayoutOrder = LayoutIndex++;
|
|
}
|
|
delete Cluster;
|
|
}
|
|
}
|
|
|
|
static void CreateDiskLayout(
|
|
const TArray<FContainerTargetSpec*>& ContainerTargets,
|
|
const TArray<FPackage*>& Packages,
|
|
const TMap<FName, uint64> PackageOrderMap,
|
|
const TMap<FName, uint64>& CookerOrderMap)
|
|
{
|
|
IOSTORE_CPU_SCOPE(CreateDiskLayout);
|
|
|
|
struct FLayoutEntry
|
|
{
|
|
FLayoutEntry* Prev = nullptr;
|
|
FLayoutEntry* Next = nullptr;
|
|
uint64 Size = 0;
|
|
uint64 IdealOrder = 0;
|
|
FIoChunkHash Hash;
|
|
FContainerTargetFile* TargetFile = nullptr;
|
|
};
|
|
|
|
AssignPackagesDiskOrder(Packages, PackageOrderMap, CookerOrderMap);
|
|
|
|
for (FContainerTargetSpec* ContainerTarget : ContainerTargets)
|
|
{
|
|
TArray<FLayoutEntry*> Entries;
|
|
FLayoutEntry* EntriesHead = new FLayoutEntry();
|
|
Entries.Add(EntriesHead);
|
|
TMap<int64, FLayoutEntry*> EntriesByOrderMap;
|
|
FLayoutEntry* LastAddedEntry = EntriesHead;
|
|
TMap<FIoChunkId, FLayoutEntry*> PreviousBuildFileByChunkId;
|
|
FLayoutEntry* PrevEntryLink = EntriesHead;
|
|
|
|
for (const TUniquePtr<FIoStoreReader>& PatchSourceReader : ContainerTarget->PatchSourceReaders)
|
|
{
|
|
PatchSourceReader->EnumerateChunks([&PrevEntryLink, &Entries, &PreviousBuildFileByChunkId](const FIoStoreTocChunkInfo& ChunkInfo)
|
|
{
|
|
FLayoutEntry* FileEntry = new FLayoutEntry();
|
|
FileEntry->Size = ChunkInfo.Size;
|
|
FileEntry->Hash = ChunkInfo.Hash;
|
|
PrevEntryLink->Next = FileEntry;
|
|
FileEntry->Prev = PrevEntryLink;
|
|
PrevEntryLink = FileEntry;
|
|
Entries.Add(FileEntry);
|
|
PreviousBuildFileByChunkId.Add(ChunkInfo.Id, FileEntry);
|
|
return true;
|
|
});
|
|
|
|
if (!ContainerTarget->bGenerateDiffPatch)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
FLayoutEntry* EntriesTail = new FLayoutEntry();
|
|
Entries.Add(EntriesTail);
|
|
PrevEntryLink->Next = EntriesTail;
|
|
EntriesTail->Prev = PrevEntryLink;
|
|
|
|
Algo::Sort(ContainerTarget->TargetFiles, [](const FContainerTargetFile& A, const FContainerTargetFile& B)
|
|
{
|
|
if (A.bIsMemoryMappedBulkData != B.bIsMemoryMappedBulkData)
|
|
{
|
|
return B.bIsMemoryMappedBulkData;
|
|
}
|
|
if (A.bIsBulkData != B.bIsBulkData)
|
|
{
|
|
return B.bIsBulkData;
|
|
}
|
|
|
|
return A.Package->DiskLayoutOrder < B.Package->DiskLayoutOrder;
|
|
});
|
|
|
|
uint64 DiffFileCount = 0;
|
|
uint64 DiffFileSize = 0;
|
|
uint64 AddedFileCount = 0;
|
|
uint64 AddedFileSize = 0;
|
|
int64 IdealOrder = 0;
|
|
TArray<FLayoutEntry*> UnassignedEntries;
|
|
for (FContainerTargetFile& ContainerTargetFile : ContainerTarget->TargetFiles)
|
|
{
|
|
bool bIsAddedOrModified = false;
|
|
FLayoutEntry* FindPreviousEntry = PreviousBuildFileByChunkId.FindRef(ContainerTargetFile.ChunkId);
|
|
if (FindPreviousEntry)
|
|
{
|
|
if (FindPreviousEntry->Hash != ContainerTargetFile.ChunkHash)
|
|
{
|
|
//UE_LOG(LogIoStore, Display, TEXT("Diffing: %s"), *ContainerTargetFile.TargetPath);
|
|
++DiffFileCount;
|
|
DiffFileSize += ContainerTargetFile.TargetSize;
|
|
bIsAddedOrModified = true;
|
|
}
|
|
else
|
|
{
|
|
FindPreviousEntry->TargetFile = &ContainerTargetFile;
|
|
FindPreviousEntry->IdealOrder = IdealOrder;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//UE_LOG(LogIoStore, Display, TEXT("Added: %s"), *ContainerTargetFile.TargetPath);
|
|
++AddedFileCount;
|
|
AddedFileSize += ContainerTargetFile.TargetSize;
|
|
bIsAddedOrModified = true;
|
|
}
|
|
if (bIsAddedOrModified)
|
|
{
|
|
FLayoutEntry* NewEntry = new FLayoutEntry();
|
|
NewEntry->Size = ContainerTargetFile.TargetSize;
|
|
NewEntry->TargetFile = &ContainerTargetFile;
|
|
NewEntry->IdealOrder = IdealOrder;
|
|
Entries.Add(NewEntry);
|
|
UnassignedEntries.Add(NewEntry);
|
|
}
|
|
++IdealOrder;
|
|
}
|
|
UE_LOG(LogIoStore, Display, TEXT("%s: %d/%d modified entries (%fMB)"), *ContainerTarget->Name.ToString(), DiffFileCount, ContainerTarget->TargetFiles.Num(), DiffFileSize / 1024.0 / 1024.0);
|
|
UE_LOG(LogIoStore, Display, TEXT("%s: %d/%d added entries (%fMB)"), *ContainerTarget->Name.ToString(), AddedFileCount, ContainerTarget->TargetFiles.Num(), AddedFileSize / 1024.0 / 1024.0);
|
|
|
|
if (ContainerTarget->bGenerateDiffPatch)
|
|
{
|
|
EntriesHead->Next = EntriesTail;
|
|
EntriesTail->Prev = EntriesHead;
|
|
}
|
|
else
|
|
{
|
|
for (FLayoutEntry* EntryIt = EntriesHead->Next; EntryIt != EntriesTail; EntryIt = EntryIt->Next)
|
|
{
|
|
if (!EntryIt->TargetFile)
|
|
{
|
|
EntryIt->Prev->Next = EntryIt->Next;
|
|
EntryIt->Next->Prev = EntryIt->Prev;
|
|
}
|
|
else
|
|
{
|
|
EntriesByOrderMap.Add(EntryIt->IdealOrder, EntryIt);
|
|
}
|
|
}
|
|
}
|
|
for (FLayoutEntry* UnassignedEntry : UnassignedEntries)
|
|
{
|
|
check(UnassignedEntry->TargetFile);
|
|
FLayoutEntry* PutAfterEntry = EntriesByOrderMap.FindRef(UnassignedEntry->IdealOrder - 1);
|
|
if (!PutAfterEntry)
|
|
{
|
|
PutAfterEntry = LastAddedEntry;
|
|
}
|
|
|
|
UnassignedEntry->Prev = PutAfterEntry;
|
|
UnassignedEntry->Next = PutAfterEntry->Next;
|
|
PutAfterEntry->Next->Prev = UnassignedEntry;
|
|
PutAfterEntry->Next = UnassignedEntry;
|
|
EntriesByOrderMap.Add(UnassignedEntry->IdealOrder, UnassignedEntry);
|
|
LastAddedEntry = UnassignedEntry;
|
|
}
|
|
|
|
TArray<FContainerTargetFile> IncludedContainerTargetFiles;
|
|
for (FLayoutEntry* EntryIt = EntriesHead->Next; EntryIt != EntriesTail; EntryIt = EntryIt->Next)
|
|
{
|
|
check(EntryIt->TargetFile);
|
|
IncludedContainerTargetFiles.Add(*EntryIt->TargetFile);
|
|
}
|
|
|
|
for (FLayoutEntry* Entry : Entries)
|
|
{
|
|
delete Entry;
|
|
}
|
|
|
|
Swap(ContainerTarget->TargetFiles, IncludedContainerTargetFiles);
|
|
}
|
|
}
|
|
|
|
static EIoChunkType BulkdataTypeToChunkIdType(FPackageStoreBulkDataManifest::EBulkdataType Type)
|
|
{
|
|
switch (Type)
|
|
{
|
|
case FPackageStoreBulkDataManifest::EBulkdataType::Normal:
|
|
return EIoChunkType::BulkData;
|
|
case FPackageStoreBulkDataManifest::EBulkdataType::Optional:
|
|
return EIoChunkType::OptionalBulkData;
|
|
case FPackageStoreBulkDataManifest::EBulkdataType::MemoryMapped:
|
|
return EIoChunkType::MemoryMappedBulkData;
|
|
default:
|
|
UE_LOG(LogIoStore, Error, TEXT("Invalid EBulkdataType (%d) found!"), Type);
|
|
return EIoChunkType::Invalid;
|
|
}
|
|
}
|
|
|
|
struct FScriptObjectData
|
|
{
|
|
FName ObjectName;
|
|
FString FullName;
|
|
FPackageObjectIndex GlobalIndex;
|
|
FPackageObjectIndex OuterIndex;
|
|
FPackageObjectIndex CDOClassIndex;
|
|
|
|
friend FArchive& operator<<(FArchive& Ar, FScriptObjectData& Data)
|
|
{
|
|
FString ObjectNameStr;
|
|
if (Ar.IsSaving())
|
|
{
|
|
ObjectNameStr = Data.ObjectName.ToString();
|
|
Ar << ObjectNameStr;
|
|
}
|
|
else
|
|
{
|
|
Ar << ObjectNameStr;
|
|
Data.ObjectName = FName(*ObjectNameStr);
|
|
}
|
|
Ar << Data.FullName;
|
|
Ar << Data.GlobalIndex;
|
|
Ar << Data.OuterIndex;
|
|
Ar << Data.CDOClassIndex;
|
|
return Ar;
|
|
}
|
|
};
|
|
|
|
struct FExportObjectData
|
|
{
|
|
FName ObjectName;
|
|
FString FullName;
|
|
int32 GlobalIndex = -1;
|
|
int32 SourceIndex = -1;
|
|
FPackageObjectIndex GlobalImportIndex;
|
|
FPackageObjectIndex OuterIndex;
|
|
FPackageObjectIndex ClassIndex;
|
|
FPackageObjectIndex SuperIndex;
|
|
FPackageObjectIndex TemplateIndex;
|
|
EObjectFlags ObjectFlags = RF_NoFlags;
|
|
|
|
FPackage* Package = nullptr;
|
|
FExportGraphNode* CreateNode = nullptr;
|
|
FExportGraphNode* SerializeNode = nullptr;
|
|
|
|
bool IsPublicExport() const
|
|
{
|
|
return !!(ObjectFlags & RF_Public);
|
|
}
|
|
|
|
friend FArchive& operator<<(FArchive& Ar, FExportObjectData& Data)
|
|
{
|
|
FString ObjectNameStr;
|
|
if (Ar.IsSaving())
|
|
{
|
|
ObjectNameStr = Data.ObjectName.ToString();
|
|
Ar << ObjectNameStr;
|
|
}
|
|
else
|
|
{
|
|
Ar << ObjectNameStr;
|
|
Data.ObjectName = FName(*ObjectNameStr);
|
|
}
|
|
Ar << Data.FullName;
|
|
Ar << Data.GlobalIndex;
|
|
Ar << Data.SourceIndex;
|
|
Ar << Data.SourceIndex;
|
|
Ar << Data.GlobalImportIndex;
|
|
Ar << Data.OuterIndex;
|
|
Ar << Data.ClassIndex;
|
|
Ar << Data.TemplateIndex;
|
|
Ar << (uint32&)Data.ObjectFlags;
|
|
return Ar;
|
|
}
|
|
};
|
|
|
|
using FImportObjectsByFullName = TMap<FString, FPackageObjectIndex>;
|
|
using FImportObjectsById = TMap<FPackageObjectIndex, FPackageObjectIndex>;
|
|
using FExportObjectsByFullName = TMap<FString, int32>;
|
|
|
|
using FGlobalScriptObjects = TMap<FPackageObjectIndex, FScriptObjectData>;
|
|
using FGlobalExportObjects = TArray<FExportObjectData>;
|
|
|
|
struct FGlobalPackageData
|
|
{
|
|
FGlobalScriptObjects ScriptObjects;
|
|
FGlobalExportObjects ExportObjects;
|
|
TMap<FPackageObjectIndex, int32> PublicExportIndices;
|
|
FImportObjectsByFullName ImportsByFullName;
|
|
FExportObjectsByFullName ExportsByFullName;
|
|
|
|
void Reserve(int32 TotalExportCount)
|
|
{
|
|
const int32 EstimatedPublicExportObjectCount = TotalExportCount / 10;
|
|
const int32 EstimatedScriptObjectCount = 64000;
|
|
ExportObjects.Reserve(TotalExportCount);
|
|
ExportsByFullName.Reserve(TotalExportCount);
|
|
PublicExportIndices.Reserve(EstimatedPublicExportObjectCount);
|
|
ScriptObjects.Reserve(EstimatedScriptObjectCount);
|
|
ImportsByFullName.Reserve(EstimatedScriptObjectCount + EstimatedPublicExportObjectCount);
|
|
}
|
|
|
|
const FExportObjectData* FindPublicExport(FPackageObjectIndex Index) const
|
|
{
|
|
check(Index.IsPackageImport());
|
|
if (const int32* GlobalExportIndex = PublicExportIndices.Find(Index))
|
|
{
|
|
return &ExportObjects[*GlobalExportIndex];
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
FName GetObjectName(FPackageObjectIndex Index, const TArray<int32>* PackageExportIndices) const
|
|
{
|
|
if (Index.IsScriptImport())
|
|
{
|
|
const FScriptObjectData* ScriptObjectData = ScriptObjects.Find(Index);
|
|
check(ScriptObjectData);
|
|
return ScriptObjectData->ObjectName;
|
|
}
|
|
if (Index.IsPackageImport())
|
|
{
|
|
const int32* GlobalExportIndex = PublicExportIndices.Find(Index);
|
|
check(GlobalExportIndex);
|
|
return ExportObjects[*GlobalExportIndex].ObjectName;
|
|
}
|
|
if (Index.IsExport() && PackageExportIndices)
|
|
{
|
|
int32 GlobalExportIndex = (*PackageExportIndices)[Index.ToExport()];
|
|
return ExportObjects[GlobalExportIndex].ObjectName;
|
|
}
|
|
return FName();
|
|
}
|
|
};
|
|
|
|
static void FindImportFullName(
|
|
TArray<FString>& ImportFullNames,
|
|
FObjectImport* ImportMap,
|
|
const int32 LocalImportIndex)
|
|
{
|
|
FString& FullName = ImportFullNames[LocalImportIndex];
|
|
|
|
if (FullName.Len() == 0)
|
|
{
|
|
FullName.Reserve(256);
|
|
|
|
FObjectImport* Import = &ImportMap[LocalImportIndex];
|
|
if (Import->OuterIndex.IsNull())
|
|
{
|
|
Import->ObjectName.AppendString(FullName);
|
|
FullName.ToLowerInline();
|
|
}
|
|
else
|
|
{
|
|
const int32 OuterIndex = Import->OuterIndex.ToImport();
|
|
FindImportFullName(ImportFullNames, ImportMap, OuterIndex);
|
|
const FString& OuterName = ImportFullNames[OuterIndex];
|
|
check(OuterName.Len() > 0);
|
|
|
|
FullName.Append(OuterName);
|
|
FullName.AppendChar(TEXT('/'));
|
|
Import->ObjectName.AppendString(FullName);
|
|
FullName.ToLowerInline();
|
|
}
|
|
}
|
|
}
|
|
|
|
static FPackageObjectIndex FindAndVerifyGlobalImport(
|
|
const FPackage* Package,
|
|
FGlobalPackageData& GlobalPackageData,
|
|
FObjectImport& Import,
|
|
const FString& FullName,
|
|
const FString& DLCPrefix)
|
|
{
|
|
FPackageObjectIndex GlobalImportIndex = GlobalPackageData.ImportsByFullName.FindRef(FullName);
|
|
if (GlobalImportIndex.IsNull())
|
|
{
|
|
bool bIsPackage = Import.OuterIndex.IsNull();
|
|
bool bIsScript = FullName.StartsWith(ScriptPrefix);
|
|
if (bIsPackage)
|
|
{
|
|
if (bIsScript)
|
|
{
|
|
UE_LOG(LogIoStore, Display, TEXT("For package '%s' (0x%llX): Missing import script package '%s'. Editor only?"),
|
|
*Package->Name.ToString(),
|
|
Package->GlobalPackageId.ValueForDebugging(),
|
|
*FullName);
|
|
}
|
|
else
|
|
{
|
|
// UPackages are never serialized, hence they are never imports
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (bIsScript)
|
|
{
|
|
UE_LOG(LogIoStore, Display, TEXT("For package '%s' (0x%llX): Missing import script object '%s'. Editor only?"),
|
|
*Package->Name.ToString(),
|
|
Package->GlobalPackageId.ValueForDebugging(),
|
|
*FullName);
|
|
}
|
|
else if (!DLCPrefix.Len() || FullName.StartsWith(DLCPrefix))
|
|
{
|
|
UE_LOG(LogIoStore, Display, TEXT("For package '%s' (0x%llX): Missing import object '%s' due to missing public export. Editor only?"),
|
|
*Package->Name.ToString(),
|
|
Package->GlobalPackageId.ValueForDebugging(),
|
|
*FullName);
|
|
}
|
|
}
|
|
}
|
|
return GlobalImportIndex;
|
|
}
|
|
|
|
static int32 FindExport(
|
|
FGlobalPackageData& GlobalPackageData,
|
|
TArray<FString>& TempFullNames,
|
|
const FObjectExport* ExportMap,
|
|
const int32 LocalExportIndex,
|
|
FPackage* Package)
|
|
{
|
|
FString& FullName = TempFullNames[LocalExportIndex];
|
|
|
|
if (FullName.Len() == 0)
|
|
{
|
|
FullName.Reserve(256);
|
|
|
|
const FObjectExport* Export = ExportMap + LocalExportIndex;
|
|
if (Export->OuterIndex.IsNull())
|
|
{
|
|
if (Package->RedirectedPackageId.IsValid())
|
|
{
|
|
Package->SourcePackageName.AppendString(FullName);
|
|
}
|
|
else
|
|
{
|
|
Package->Name.AppendString(FullName);
|
|
}
|
|
FullName.AppendChar(TEXT('/'));
|
|
Export->ObjectName.AppendString(FullName);
|
|
FullName.ToLowerInline();
|
|
}
|
|
else
|
|
{
|
|
check(Export->OuterIndex.IsExport());
|
|
|
|
FindExport(GlobalPackageData, TempFullNames, ExportMap, Export->OuterIndex.ToExport(), Package);
|
|
FString& OuterName = TempFullNames[Export->OuterIndex.ToExport()];
|
|
check(OuterName.Len() > 0);
|
|
FullName.Append(OuterName);
|
|
FullName.AppendChar(TEXT('/'));
|
|
Export->ObjectName.AppendString(FullName);
|
|
FullName.ToLowerInline();
|
|
}
|
|
|
|
int32 GlobalExportIndex = -1;
|
|
int32* FindGlobalExportIndex = GlobalPackageData.ExportsByFullName.Find(FullName);
|
|
if (!FindGlobalExportIndex)
|
|
{
|
|
GlobalExportIndex = GlobalPackageData.ExportObjects.Num();
|
|
GlobalPackageData.ExportsByFullName.Add(FullName, GlobalExportIndex);
|
|
GlobalPackageData.ExportObjects.AddDefaulted();
|
|
}
|
|
else
|
|
{
|
|
GlobalExportIndex = *FindGlobalExportIndex;
|
|
}
|
|
FExportObjectData& ExportData = GlobalPackageData.ExportObjects[GlobalExportIndex];
|
|
|
|
ExportData.GlobalIndex = GlobalExportIndex;
|
|
ExportData.Package = Package;
|
|
ExportData.ObjectName = Export->ObjectName;
|
|
ExportData.SourceIndex = LocalExportIndex;
|
|
ExportData.FullName = FullName;
|
|
ExportData.ObjectFlags = Export->ObjectFlags;
|
|
|
|
return GlobalExportIndex;
|
|
}
|
|
else
|
|
{
|
|
int32* GlobalExportIndex = GlobalPackageData.ExportsByFullName.Find(FullName);
|
|
check(GlobalExportIndex);
|
|
return *GlobalExportIndex;
|
|
}
|
|
}
|
|
|
|
FContainerTargetSpec* AddContainer(
|
|
FName Name,
|
|
TArray<FContainerTargetSpec*>& Containers)
|
|
{
|
|
FIoContainerId ContainerId = FIoContainerId::FromName(Name);
|
|
for (FContainerTargetSpec* ExistingContainer : Containers)
|
|
{
|
|
if (ExistingContainer->Name == Name)
|
|
{
|
|
UE_LOG(LogIoStore, Fatal, TEXT("Duplicate container name: '%s'"), *Name.ToString());
|
|
return nullptr;
|
|
}
|
|
if (ExistingContainer->Header.ContainerId == ContainerId)
|
|
{
|
|
UE_LOG(LogIoStore, Fatal, TEXT("Hash collision for container names: '%s' and '%s'"), *Name.ToString(), *ExistingContainer->Name.ToString());
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
FContainerTargetSpec* ContainerTargetSpec = new FContainerTargetSpec();
|
|
ContainerTargetSpec->Name = Name;
|
|
ContainerTargetSpec->Header.ContainerId = ContainerId;
|
|
Containers.Add(ContainerTargetSpec);
|
|
return ContainerTargetSpec;
|
|
}
|
|
|
|
FPackage* FindOrAddPackage(
|
|
const FIoStoreArguments& Arguments,
|
|
const TCHAR* RelativeFileName,
|
|
TArray<FPackage*>& Packages,
|
|
FPackageNameMap& PackageNameMap,
|
|
FPackageIdMap& PackageIdMap)
|
|
{
|
|
FString PackageName;
|
|
FString ErrorMessage;
|
|
if (!FPackageName::TryConvertFilenameToLongPackageName(RelativeFileName, PackageName, &ErrorMessage))
|
|
{
|
|
UE_LOG(LogIoStore, Warning, TEXT("Failed to convert file name from file name '%s'"), *ErrorMessage);
|
|
return nullptr;
|
|
}
|
|
|
|
FName PackageFName = *PackageName;
|
|
|
|
FPackage* Package = PackageNameMap.FindRef(PackageFName);
|
|
if (!Package)
|
|
{
|
|
FPackageId PackageId = FPackageId::FromName(PackageFName);
|
|
if (FPackage* FindById = PackageIdMap.FindRef(PackageId))
|
|
{
|
|
UE_LOG(LogIoStore, Fatal, TEXT("Package name hash collision \"%s\" and \"%s"), *FindById->Name.ToString(), *PackageFName.ToString());
|
|
}
|
|
|
|
if (const FName* ReleasedPackageName = Arguments.ReleasedPackages.PackageIdToName.Find(PackageId))
|
|
{
|
|
UE_LOG(LogIoStore, Fatal, TEXT("Package name hash collision \"%s\" and \"%s"), *ReleasedPackageName->ToString(), *PackageFName.ToString());
|
|
}
|
|
|
|
Package = new FPackage();
|
|
Package->Name = PackageFName;
|
|
Package->GlobalPackageId = PackageId;
|
|
|
|
if (Arguments.IsDLC() && Arguments.bRemapPluginContentToGame)
|
|
{
|
|
const int32 DLCNameLen = Arguments.DLCName.Len() + 1;
|
|
FString RedirectedPackageNameStr = TEXT("/Game");
|
|
RedirectedPackageNameStr.AppendChars(*PackageName + DLCNameLen, PackageName.Len() - DLCNameLen);
|
|
FName RedirectedPackageName = FName(*RedirectedPackageNameStr);
|
|
|
|
if (Arguments.ReleasedPackages.PackageNames.Contains(RedirectedPackageName))
|
|
{
|
|
Package->SourcePackageName = RedirectedPackageName;
|
|
Package->RedirectedPackageId = FPackageId::FromName(RedirectedPackageName);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Package->SourcePackageName = *RemapLocalizationPathIfNeeded(PackageName, &Package->Region);
|
|
}
|
|
|
|
Packages.Add(Package);
|
|
PackageNameMap.Add(PackageFName, Package);
|
|
PackageIdMap.Add(PackageId, Package);
|
|
}
|
|
|
|
return Package;
|
|
}
|
|
|
|
static bool ConformLocalizedPackage(
|
|
const FPackageNameMap& PackageMap,
|
|
FGlobalPackageData& GlobalPackageData,
|
|
const FPackage& SourcePackage,
|
|
FPackage& LocalizedPackage,
|
|
FLocalizedToSourceImportIndexMap& LocalizedToSourceImportIndexMap)
|
|
{
|
|
const int32 ExportCount =
|
|
SourcePackage.ExportCount < LocalizedPackage.ExportCount ?
|
|
SourcePackage.ExportCount :
|
|
LocalizedPackage.ExportCount;
|
|
|
|
UE_CLOG(SourcePackage.ExportCount != LocalizedPackage.ExportCount, LogIoStore, Verbose,
|
|
TEXT("For culture '%s': Localized package '%s' (0x%llX) for source package '%s' (0x%llX) - Has ExportCount %d vs. %d"),
|
|
*LocalizedPackage.Region,
|
|
*LocalizedPackage.Name.ToString(),
|
|
LocalizedPackage.GlobalPackageId.ValueForDebugging(),
|
|
*LocalizedPackage.SourcePackageName.ToString(),
|
|
SourcePackage.GlobalPackageId.ValueForDebugging(),
|
|
LocalizedPackage.ExportCount,
|
|
SourcePackage.ExportCount);
|
|
|
|
auto GetExportNameSafe = [](
|
|
const FString& ExportFullName,
|
|
const FName& PackageName,
|
|
int32 PackageNameLen) -> const TCHAR*
|
|
{
|
|
const bool bValidNameLen = ExportFullName.Len() > PackageNameLen + 1;
|
|
if (bValidNameLen)
|
|
{
|
|
const TCHAR* ExportNameStr = *ExportFullName + PackageNameLen;
|
|
const bool bValidNameFormat = *ExportNameStr == '/';
|
|
if (bValidNameFormat)
|
|
{
|
|
return ExportNameStr + 1; // skip verified '/'
|
|
}
|
|
else
|
|
{
|
|
UE_CLOG(!bValidNameFormat, LogIoStore, Warning,
|
|
TEXT("Export name '%s' should start with '/' at position %d, i.e. right after package prefix '%s'"),
|
|
*ExportFullName,
|
|
PackageNameLen,
|
|
*PackageName.ToString());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_CLOG(!bValidNameLen, LogIoStore, Warning,
|
|
TEXT("Export name '%s' with length %d should be longer than package name '%s' with length %d"),
|
|
*ExportFullName,
|
|
PackageNameLen,
|
|
*PackageName.ToString());
|
|
}
|
|
|
|
return nullptr;
|
|
};
|
|
|
|
auto AppendMismatchMessage = [&GlobalPackageData, &LocalizedPackage, &SourcePackage](
|
|
const TCHAR* Text, FName ExportName, FPackageObjectIndex LocIndex, FPackageObjectIndex SrcIndex, FString& FailReason)
|
|
{
|
|
FString LocString = GlobalPackageData.GetObjectName(LocIndex, &LocalizedPackage.Exports).ToString();
|
|
FString SrcString = GlobalPackageData.GetObjectName(SrcIndex, &SourcePackage.Exports).ToString();
|
|
|
|
FailReason.Appendf(TEXT("Public export '%s' has %s %s vs. %s"),
|
|
*ExportName.ToString(),
|
|
Text,
|
|
*LocString,
|
|
*SrcString);
|
|
};
|
|
|
|
const int32 LocalizedPackageNameLen = LocalizedPackage.Name.GetStringLength();
|
|
const int32 SourcePackageNameLen = SourcePackage.Name.GetStringLength();
|
|
|
|
TArray <TPair<int32, int32>, TInlineAllocator<64>> NewPublicExports;
|
|
NewPublicExports.Reserve(ExportCount);
|
|
|
|
bool bSuccess = true;
|
|
int32 LocalizedIndex = 0;
|
|
int32 SourceIndex = 0;
|
|
while (LocalizedIndex < ExportCount && SourceIndex < ExportCount)
|
|
{
|
|
FString FailReason;
|
|
const FExportObjectData& LocalizedExportData = GlobalPackageData.ExportObjects[LocalizedPackage.Exports[LocalizedIndex]];
|
|
const FExportObjectData& SourceExportData = GlobalPackageData.ExportObjects[SourcePackage.Exports[SourceIndex]];
|
|
|
|
const TCHAR* LocalizedExportStr = GetExportNameSafe(
|
|
LocalizedExportData.FullName, LocalizedPackage.Name, LocalizedPackageNameLen);
|
|
const TCHAR* SourceExportStr = GetExportNameSafe(
|
|
SourceExportData.FullName, SourcePackage.Name, SourcePackageNameLen);
|
|
|
|
if (!LocalizedExportStr || !SourceExportStr)
|
|
{
|
|
UE_LOG(LogIoStore, Error,
|
|
TEXT("Culture '%s': Localized package '%s' (0x%llX) for source package '%s' (0x%llX) - Has some bad data from an earlier phase."),
|
|
*LocalizedPackage.Region,
|
|
*LocalizedPackage.Name.ToString(),
|
|
LocalizedPackage.GlobalPackageId.ValueForDebugging(),
|
|
*LocalizedPackage.SourcePackageName.ToString(),
|
|
SourcePackage.GlobalPackageId.ValueForDebugging())
|
|
return false;
|
|
}
|
|
|
|
int32 CompareResult = FCString::Stricmp(LocalizedExportStr, SourceExportStr);
|
|
if (CompareResult < 0)
|
|
{
|
|
++LocalizedIndex;
|
|
|
|
if (LocalizedExportData.IsPublicExport())
|
|
{
|
|
// public localized export is missing in the source package, so just keep it as it is
|
|
NewPublicExports.Emplace(LocalizedIndex - 1, 1);
|
|
}
|
|
}
|
|
else if (CompareResult > 0)
|
|
{
|
|
++SourceIndex;
|
|
|
|
if (SourceExportData.IsPublicExport())
|
|
{
|
|
FailReason.Appendf(TEXT("Public source export '%s' is missing in the localized package"),
|
|
*SourceExportData.ObjectName.ToString());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
++LocalizedIndex;
|
|
++SourceIndex;
|
|
|
|
if (SourceExportData.IsPublicExport())
|
|
{
|
|
if (!LocalizedExportData.IsPublicExport())
|
|
{
|
|
FailReason.Appendf(TEXT("Public source export '%s' exists in the localized package")
|
|
TEXT(", but is not a public localized export."),
|
|
*SourceExportData.ObjectName.ToString());
|
|
}
|
|
else if (LocalizedExportData.ClassIndex != SourceExportData.ClassIndex)
|
|
{
|
|
AppendMismatchMessage(TEXT("class"), LocalizedExportData.ObjectName,
|
|
LocalizedExportData.ClassIndex, SourceExportData.ClassIndex, FailReason);
|
|
}
|
|
else if (LocalizedExportData.TemplateIndex != SourceExportData.TemplateIndex)
|
|
{
|
|
AppendMismatchMessage(TEXT("template"), LocalizedExportData.ObjectName,
|
|
LocalizedExportData.TemplateIndex, SourceExportData.TemplateIndex, FailReason);
|
|
}
|
|
else if (LocalizedExportData.SuperIndex != SourceExportData.SuperIndex)
|
|
{
|
|
AppendMismatchMessage(TEXT("super"), LocalizedExportData.ObjectName,
|
|
LocalizedExportData.SuperIndex, SourceExportData.SuperIndex, FailReason);
|
|
}
|
|
else
|
|
{
|
|
NewPublicExports.Emplace(LocalizedIndex - 1, SourceIndex - 1);
|
|
}
|
|
}
|
|
else if (LocalizedExportData.IsPublicExport())
|
|
{
|
|
FailReason.Appendf(TEXT("Public localized export '%s' exists in the source package")
|
|
TEXT(", but is not a public source export."),
|
|
*LocalizedExportData.ObjectName.ToString());
|
|
}
|
|
}
|
|
|
|
if (FailReason.Len() > 0)
|
|
{
|
|
UE_LOG(LogIoStore, Warning,
|
|
TEXT("Culture '%s': Localized package '%s' (0x%llX) for '%s' (0x%llX) - %s"),
|
|
*LocalizedPackage.Region,
|
|
*LocalizedPackage.Name.ToString(),
|
|
LocalizedPackage.GlobalPackageId.ValueForDebugging(),
|
|
*LocalizedPackage.SourcePackageName.ToString(),
|
|
SourcePackage.GlobalPackageId.ValueForDebugging(),
|
|
*FailReason);
|
|
bSuccess = false;
|
|
}
|
|
}
|
|
|
|
if (bSuccess)
|
|
{
|
|
for (TPair<int32, int32>& Pair : NewPublicExports)
|
|
{
|
|
FExportObjectData& LocalizedExportData = GlobalPackageData.ExportObjects[LocalizedPackage.Exports[Pair.Key]];
|
|
if (Pair.Value != -1)
|
|
{
|
|
const FExportObjectData& SourceExportData = GlobalPackageData.ExportObjects[SourcePackage.Exports[Pair.Value]];
|
|
|
|
LocalizedToSourceImportIndexMap.Add(LocalizedExportData.GlobalImportIndex, SourceExportData.GlobalImportIndex);
|
|
|
|
LocalizedExportData.GlobalImportIndex = SourceExportData.GlobalImportIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bSuccess;
|
|
}
|
|
|
|
static void AddPreloadDependencies(
|
|
const FPackageAssetData& PackageAssetData,
|
|
const FGlobalPackageData& GlobalPackageData,
|
|
const FSourceToLocalizedPackageMultimap& SourceToLocalizedPackageMap,
|
|
FExportGraph& ExportGraph,
|
|
TArray<FPackage*>& Packages)
|
|
{
|
|
IOSTORE_CPU_SCOPE(PreLoadDependencies);
|
|
UE_LOG(LogIoStore, Display, TEXT("Adding preload dependencies..."));
|
|
|
|
TSet<FPackageId> ExternalPackageDependencies;
|
|
TArray<FPackage*> LocalizedPackages;
|
|
for (FPackage* Package : Packages)
|
|
{
|
|
ExternalPackageDependencies.Reset();
|
|
|
|
// Convert PreloadDependencies to arcs
|
|
for (int32 I = 0; I < Package->ExportCount; ++I)
|
|
{
|
|
const FObjectExport& ObjectExport = PackageAssetData.ObjectExports[Package->ExportIndexOffset + I];
|
|
int32 PreloadDependenciesBaseIndex = Package->PreloadIndexOffset;
|
|
|
|
FPackageIndex ExportPackageIndex = FPackageIndex::FromExport(I);
|
|
|
|
auto AddPreloadArc = [&](FPackageIndex Dep, EPreloadDependencyType PhaseFrom, EPreloadDependencyType PhaseTo)
|
|
{
|
|
if (Dep.IsExport())
|
|
{
|
|
AddInternalExportArc(ExportGraph, *Package, Dep.ToExport(), PhaseFrom, I, PhaseTo);
|
|
}
|
|
else
|
|
{
|
|
FPackageObjectIndex ImportIndex = Package->Imports[Dep.ToImport()];
|
|
if (ImportIndex.IsPackageImport())
|
|
{
|
|
// When building DLC's exports can be missing
|
|
if (const FExportObjectData* Export = GlobalPackageData.FindPublicExport(ImportIndex))
|
|
{
|
|
check(Export->GlobalImportIndex == ImportIndex);
|
|
|
|
AddExternalExportArc(ExportGraph, *Export->Package, Export->SourceIndex, PhaseFrom, *Package, I, PhaseTo);
|
|
|
|
LocalizedPackages.Reset();
|
|
SourceToLocalizedPackageMap.MultiFind(Export->Package, LocalizedPackages);
|
|
for (FPackage* LocalizedPackage : LocalizedPackages)
|
|
{
|
|
UE_LOG(LogIoStore, Verbose, TEXT("For package '%s' (0x%llX): Adding localized preload dependency '%s' in '%s'"),
|
|
*Package->Name.ToString(),
|
|
Package->GlobalPackageId.ValueForDebugging(),
|
|
*Export->ObjectName.ToString(),
|
|
*LocalizedPackage->Name.ToString());
|
|
|
|
AddExternalExportArc(ExportGraph, *LocalizedPackage, Export->SourceIndex, PhaseFrom, *Package, I, PhaseTo);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const FObjectImport* ImportMap = PackageAssetData.ObjectImports.GetData() + Package->ImportIndexOffset;
|
|
const FObjectImport* Import = ImportMap + Dep.ToImport();
|
|
while (!Import->OuterIndex.IsNull())
|
|
{
|
|
Import = ImportMap + Import->OuterIndex.ToImport();
|
|
}
|
|
|
|
FPackageId PackageId = FPackageId::FromName(Import->ObjectName);
|
|
bool bIsAlreadyInSet = false;
|
|
ExternalPackageDependencies.Add(PackageId, &bIsAlreadyInSet);
|
|
if (!bIsAlreadyInSet)
|
|
{
|
|
AddBaseGamePackageArc(ExportGraph, PackageId, *Package, I, PhaseTo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
if (PreloadDependenciesBaseIndex >= 0 && ObjectExport.FirstExportDependency >= 0)
|
|
{
|
|
int32 RunningIndex = PreloadDependenciesBaseIndex + ObjectExport.FirstExportDependency;
|
|
for (int32 Index = ObjectExport.SerializationBeforeSerializationDependencies; Index > 0; Index--)
|
|
{
|
|
FPackageIndex Dep = PackageAssetData.PreloadDependencies[RunningIndex++];
|
|
check(!Dep.IsNull());
|
|
AddPreloadArc(Dep, PreloadDependencyType_Serialize, PreloadDependencyType_Serialize);
|
|
}
|
|
|
|
for (int32 Index = ObjectExport.CreateBeforeSerializationDependencies; Index > 0; Index--)
|
|
{
|
|
FPackageIndex Dep = PackageAssetData.PreloadDependencies[RunningIndex++];
|
|
check(!Dep.IsNull());
|
|
AddPreloadArc(Dep, PreloadDependencyType_Create, PreloadDependencyType_Serialize);
|
|
}
|
|
|
|
for (int32 Index = ObjectExport.SerializationBeforeCreateDependencies; Index > 0; Index--)
|
|
{
|
|
FPackageIndex Dep = PackageAssetData.PreloadDependencies[RunningIndex++];
|
|
check(!Dep.IsNull());
|
|
AddPreloadArc(Dep, PreloadDependencyType_Serialize, PreloadDependencyType_Create);
|
|
}
|
|
|
|
for (int32 Index = ObjectExport.CreateBeforeCreateDependencies; Index > 0; Index--)
|
|
{
|
|
FPackageIndex Dep = PackageAssetData.PreloadDependencies[RunningIndex++];
|
|
check(!Dep.IsNull());
|
|
// can't create this export until these things are created
|
|
AddPreloadArc(Dep, PreloadDependencyType_Create, PreloadDependencyType_Create);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
void FinalizeNameMaps(FContainerTargetSpec& ContainerTarget)
|
|
{
|
|
for (FContainerTargetFile& TargetFile : ContainerTarget.TargetFiles)
|
|
{
|
|
if (TargetFile.bIsBulkData)
|
|
{
|
|
continue;
|
|
}
|
|
FPackage* Package = TargetFile.Package;
|
|
TargetFile.NameMapBuilder->MarkNameAsReferenced(Package->Name);
|
|
TargetFile.NameMapBuilder->MarkNameAsReferenced(Package->SourcePackageName);
|
|
TargetFile.NameMapBuilder->MarkNamesAsReferenced(Package->SummaryNames, TargetFile.NameIndices);
|
|
}
|
|
}
|
|
|
|
void FinalizePackageHeaders(
|
|
FContainerTargetSpec& ContainerTarget,
|
|
const TArray<FObjectExport>& ObjectExports,
|
|
const TArray<FExportObjectData>& GlobalExports,
|
|
const FImportObjectsByFullName& GlobalImportsByFullName)
|
|
{
|
|
for (FContainerTargetFile& TargetFile : ContainerTarget.TargetFiles)
|
|
{
|
|
if (TargetFile.bIsBulkData)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FPackage* Package = TargetFile.Package;
|
|
|
|
// Temporary Archive for serializing ImportMap
|
|
FBufferWriter ImportMapArchive(nullptr, 0, EBufferWriterFlags::AllowResize | EBufferWriterFlags::TakeOwnership);
|
|
for (FPackageObjectIndex& GlobalImportIndex : Package->Imports)
|
|
{
|
|
ImportMapArchive << GlobalImportIndex;
|
|
}
|
|
TargetFile.ImportMapSize = ImportMapArchive.Tell();
|
|
|
|
// Temporary Archive for serializing EDL graph data
|
|
FBufferWriter GraphArchive(nullptr, 0, EBufferWriterFlags::AllowResize | EBufferWriterFlags::TakeOwnership);
|
|
|
|
int32 ReferencedPackagesCount = Package->ExternalArcs.Num();
|
|
GraphArchive << ReferencedPackagesCount;
|
|
TArray<TTuple<FPackageId, TArray<FArc>>> SortedExternalArcs;
|
|
SortedExternalArcs.Reserve(Package->ExternalArcs.Num());
|
|
for (auto& KV : Package->ExternalArcs)
|
|
{
|
|
FPackageId ImportedPackageId = KV.Key;
|
|
TArray<FArc> SortedArcs = KV.Value;
|
|
Algo::Sort(SortedArcs, [](const FArc& A, const FArc& B)
|
|
{
|
|
if (A.FromNodeIndex == B.FromNodeIndex)
|
|
{
|
|
return A.ToNodeIndex < B.ToNodeIndex;
|
|
}
|
|
return A.FromNodeIndex < B.ToNodeIndex;
|
|
});
|
|
SortedExternalArcs.Emplace(ImportedPackageId, MoveTemp(SortedArcs));
|
|
}
|
|
Algo::Sort(SortedExternalArcs, [](const TTuple<FPackageId, TArray<FArc>>& A, const TTuple<FPackageId, TArray<FArc>>& B)
|
|
{
|
|
return A.Key < B.Key;
|
|
});
|
|
for (auto& KV : SortedExternalArcs)
|
|
{
|
|
FPackageId ImportedPackageId = KV.Key;
|
|
TArray<FArc>& Arcs = KV.Value;
|
|
int32 ExternalArcCount = Arcs.Num();
|
|
|
|
GraphArchive << ImportedPackageId;
|
|
GraphArchive << ExternalArcCount;
|
|
GraphArchive.Serialize(Arcs.GetData(), ExternalArcCount * sizeof(FArc));
|
|
}
|
|
TargetFile.UGraphSize = GraphArchive.Tell();
|
|
|
|
// Temporary Archive for serializing export map data
|
|
FBufferWriter ExportMapArchive(nullptr, 0, EBufferWriterFlags::AllowResize | EBufferWriterFlags::TakeOwnership);
|
|
for (int32 I = 0; I < Package->ExportCount; ++I)
|
|
{
|
|
const FObjectExport& ObjectExport = ObjectExports[Package->ExportIndexOffset + I];
|
|
const FExportObjectData& ExportData = GlobalExports[Package->Exports[I]];
|
|
|
|
FExportMapEntry ExportMapEntry;
|
|
ExportMapEntry.CookedSerialOffset = ObjectExport.SerialOffset;
|
|
ExportMapEntry.CookedSerialSize = ObjectExport.SerialSize;
|
|
ExportMapEntry.ObjectName = TargetFile.NameMapBuilder->MapName(ObjectExport.ObjectName);
|
|
ExportMapEntry.OuterIndex = ExportData.OuterIndex;
|
|
ExportMapEntry.ClassIndex = ExportData.ClassIndex;
|
|
ExportMapEntry.SuperIndex = ExportData.SuperIndex;
|
|
ExportMapEntry.TemplateIndex = ExportData.TemplateIndex;
|
|
ExportMapEntry.GlobalImportIndex = ExportData.GlobalImportIndex;
|
|
ExportMapEntry.ObjectFlags = ObjectExport.ObjectFlags;
|
|
ExportMapEntry.FilterFlags = EExportFilterFlags::None;
|
|
if (ObjectExport.bNotForClient)
|
|
{
|
|
ExportMapEntry.FilterFlags = EExportFilterFlags::NotForClient;
|
|
}
|
|
else if (ObjectExport.bNotForServer)
|
|
{
|
|
ExportMapEntry.FilterFlags = EExportFilterFlags::NotForServer;
|
|
}
|
|
|
|
ExportMapArchive << ExportMapEntry;
|
|
}
|
|
TargetFile.ExportMapSize = ExportMapArchive.Tell();
|
|
|
|
// Temporary archive for serializing export bundle data
|
|
FBufferWriter ExportBundlesArchive(nullptr, 0, EBufferWriterFlags::AllowResize | EBufferWriterFlags::TakeOwnership);
|
|
uint32 ExportBundleEntryIndex = 0;
|
|
for (FExportBundle& ExportBundle : Package->ExportBundles)
|
|
{
|
|
const uint32 EntryCount = ExportBundle.Nodes.Num();
|
|
FExportBundleHeader ExportBundleHeader { ExportBundleEntryIndex, EntryCount };
|
|
ExportBundlesArchive << ExportBundleHeader ;
|
|
|
|
ExportBundleEntryIndex += EntryCount;
|
|
}
|
|
for (FExportBundle& ExportBundle : Package->ExportBundles)
|
|
{
|
|
for (FExportGraphNode* ExportNode : ExportBundle.Nodes)
|
|
{
|
|
ExportBundlesArchive << ExportNode->BundleEntry;
|
|
}
|
|
}
|
|
TargetFile.ExportBundlesHeaderSize = ExportBundlesArchive.Tell();
|
|
|
|
FMappedName MappedPackageName = TargetFile.NameMapBuilder->MapName(Package->Name);
|
|
FMappedName MappedPackageSourceName = TargetFile.NameMapBuilder->MapName(Package->SourcePackageName);
|
|
|
|
TArray<uint8> NamesBuffer;
|
|
TArray<uint8> NameHashesBuffer;
|
|
SaveNameBatch(Package->LocalNameMapBuilder.GetNameMap(), NamesBuffer, NameHashesBuffer);
|
|
TargetFile.NameMapSize = Align(NamesBuffer.Num(), 8) + NameHashesBuffer.Num();
|
|
|
|
TargetFile.HeaderSerialSize =
|
|
sizeof(FPackageSummary)
|
|
+ TargetFile.NameMapSize
|
|
+ TargetFile.ImportMapSize
|
|
+ TargetFile.ExportMapSize
|
|
+ TargetFile.ExportBundlesHeaderSize
|
|
+ TargetFile.UGraphSize;
|
|
|
|
TargetFile.PackageHeaderData.AddZeroed(TargetFile.HeaderSerialSize);
|
|
uint8* PackageHeaderBuffer = TargetFile.PackageHeaderData.GetData();
|
|
FPackageSummary* PackageSummary = reinterpret_cast<FPackageSummary*>(PackageHeaderBuffer);
|
|
|
|
PackageSummary->Name = MappedPackageName;
|
|
PackageSummary->SourceName = MappedPackageSourceName;
|
|
PackageSummary->PackageFlags = Package->PackageFlags;
|
|
PackageSummary->CookedHeaderSize = Package->CookedHeaderSize;
|
|
FBufferWriter SummaryArchive(PackageHeaderBuffer, TargetFile.HeaderSerialSize);
|
|
SummaryArchive.Seek(sizeof(FPackageSummary));
|
|
|
|
// NameMap data
|
|
{
|
|
PackageSummary->NameMapNamesOffset = SummaryArchive.Tell();
|
|
check(PackageSummary->NameMapNamesOffset % 8 == 0);
|
|
PackageSummary->NameMapNamesSize = NamesBuffer.Num();
|
|
SummaryArchive.Serialize(NamesBuffer.GetData(), NamesBuffer.Num());
|
|
PackageSummary->NameMapHashesOffset = Align(SummaryArchive.Tell(), 8);
|
|
int32 PaddingByteCount = PackageSummary->NameMapHashesOffset - SummaryArchive.Tell();
|
|
if (PaddingByteCount)
|
|
{
|
|
check(PaddingByteCount < 8);
|
|
uint8 PaddingBytes[8]{ 0 };
|
|
SummaryArchive.Serialize(PaddingBytes, PaddingByteCount);
|
|
}
|
|
PackageSummary->NameMapHashesSize = NameHashesBuffer.Num();
|
|
SummaryArchive.Serialize(NameHashesBuffer.GetData(), NameHashesBuffer.Num());
|
|
}
|
|
|
|
// ImportMap data
|
|
{
|
|
check(ImportMapArchive.Tell() == TargetFile.ImportMapSize);
|
|
PackageSummary->ImportMapOffset = SummaryArchive.Tell();
|
|
SummaryArchive.Serialize(ImportMapArchive.GetWriterData(), ImportMapArchive.Tell());
|
|
}
|
|
|
|
// ExportMap data
|
|
{
|
|
check(ExportMapArchive.Tell() == TargetFile.ExportMapSize);
|
|
PackageSummary->ExportMapOffset = SummaryArchive.Tell();
|
|
SummaryArchive.Serialize(ExportMapArchive.GetWriterData(), ExportMapArchive.Tell());
|
|
}
|
|
|
|
// ExportBundle data
|
|
{
|
|
check(ExportBundlesArchive.Tell() == TargetFile.ExportBundlesHeaderSize);
|
|
PackageSummary->ExportBundlesOffset = SummaryArchive.Tell();
|
|
SummaryArchive.Serialize(ExportBundlesArchive.GetWriterData(), ExportBundlesArchive.Tell());
|
|
}
|
|
|
|
// Graph data
|
|
{
|
|
check(GraphArchive.Tell() == TargetFile.UGraphSize);
|
|
PackageSummary->GraphDataOffset = SummaryArchive.Tell();
|
|
PackageSummary->GraphDataSize = TargetFile.UGraphSize;
|
|
SummaryArchive.Serialize(GraphArchive.GetWriterData(), GraphArchive.Tell());
|
|
}
|
|
}
|
|
}
|
|
|
|
void FinalizePackageStoreContainerHeader(FContainerTargetSpec& ContainerTarget)
|
|
{
|
|
check(ContainerTarget.NameMapBuilder);
|
|
FNameMapBuilder& NameMapBuilder = *ContainerTarget.NameMapBuilder;
|
|
FCulturePackageMap& CulturePackageMap = ContainerTarget.Header.CulturePackageMap;
|
|
TArray<FPackageId>& PackageIds = ContainerTarget.Header.PackageIds;
|
|
TArray<TTuple<FPackageId, FPackageId>>& PackageRedirects = ContainerTarget.Header.PackageRedirects;
|
|
|
|
int32 StoreTocSize = ContainerTarget.PackageCount * sizeof(FPackageStoreEntry);
|
|
FLargeMemoryWriter StoreTocArchive(0, true);
|
|
FLargeMemoryWriter StoreDataArchive(0, true);
|
|
|
|
auto SerializePackageEntryCArrayHeader = [&StoreTocSize,&StoreTocArchive,&StoreDataArchive](int32 Count)
|
|
{
|
|
const int32 RemainingTocSize = StoreTocSize - StoreTocArchive.Tell();
|
|
const int32 OffsetFromThis = RemainingTocSize + StoreDataArchive.Tell();
|
|
uint32 ArrayNum = Count > 0 ? Count : 0;
|
|
uint32 OffsetToDataFromThis = ArrayNum > 0 ? OffsetFromThis : 0;
|
|
|
|
StoreTocArchive << ArrayNum;
|
|
StoreTocArchive << OffsetToDataFromThis;
|
|
};
|
|
|
|
PackageIds.Reserve(ContainerTarget.PackageCount);
|
|
for (FContainerTargetFile& TargetFile : ContainerTarget.TargetFiles)
|
|
{
|
|
if (TargetFile.bIsBulkData)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FPackage* Package = TargetFile.Package;
|
|
|
|
// PackageIds
|
|
{
|
|
check(!PackageIds.Contains(Package->GlobalPackageId));
|
|
PackageIds.Add(Package->GlobalPackageId);
|
|
}
|
|
|
|
// CulturePackageMap
|
|
if (Package->bIsLocalizedAndConformed)
|
|
{
|
|
CulturePackageMap.FindOrAdd(Package->Region).Emplace(Package->SourceGlobalPackageId, Package->GlobalPackageId);
|
|
}
|
|
|
|
// Redirects
|
|
if (Package->RedirectedPackageId.IsValid())
|
|
{
|
|
PackageRedirects.Add(MakeTuple(Package->RedirectedPackageId, Package->GlobalPackageId));
|
|
}
|
|
|
|
// StoreEntries
|
|
{
|
|
uint64 ExportBundlesSize = TargetFile.HeaderSerialSize + Package->ExportsSerialSize;
|
|
int32 ExportBundleCount = Package->ExportBundles.Num();
|
|
uint32 LoadOrder = Package->ExportBundles.Num() > 0 ? Package->ExportBundles[0].LoadOrder : 0;
|
|
uint32 Pad = 0;
|
|
|
|
StoreTocArchive << ExportBundlesSize;
|
|
StoreTocArchive << Package->ExportCount;
|
|
StoreTocArchive << ExportBundleCount;
|
|
StoreTocArchive << LoadOrder;
|
|
StoreTocArchive << Pad;
|
|
|
|
// ImportedPackages
|
|
{
|
|
SerializePackageEntryCArrayHeader(Package->ImportedPackageIds.Num());
|
|
for (FPackageId PackageId : Package->ImportedPackageIds)
|
|
{
|
|
check(PackageId.IsValid());
|
|
StoreDataArchive << PackageId;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const int32 StoreByteCount = StoreTocArchive.TotalSize() + StoreDataArchive.TotalSize();
|
|
ContainerTarget.Header.PackageCount = ContainerTarget.PackageCount;
|
|
ContainerTarget.Header.StoreEntries.AddUninitialized(StoreByteCount);
|
|
FBufferWriter PackageStoreArchive(ContainerTarget.Header.StoreEntries.GetData(), StoreByteCount);
|
|
PackageStoreArchive.Serialize(StoreTocArchive.GetData(), StoreTocArchive.TotalSize());
|
|
PackageStoreArchive.Serialize(StoreDataArchive.GetData(), StoreDataArchive.TotalSize());
|
|
}
|
|
|
|
static void FinalizeInitialLoadMeta(
|
|
FNameMapBuilder& GlobalNameMapBuilder,
|
|
const FGlobalScriptObjects& GlobalScriptImports,
|
|
FArchive& InitialLoadArchive)
|
|
{
|
|
IOSTORE_CPU_SCOPE(FinalizeInitialLoad);
|
|
UE_LOG(LogIoStore, Display, TEXT("Finalizing initial load..."));
|
|
|
|
int32 NumScriptObjects = GlobalScriptImports.Num();
|
|
InitialLoadArchive << NumScriptObjects;
|
|
|
|
TArray<FScriptObjectData> ScriptObjects;
|
|
GlobalScriptImports.GenerateValueArray(ScriptObjects );
|
|
Algo::Sort(ScriptObjects, [](const FScriptObjectData& A, const FScriptObjectData& B)
|
|
{
|
|
return A.FullName < B.FullName;
|
|
});
|
|
|
|
for (const FScriptObjectData& ImportData : ScriptObjects)
|
|
{
|
|
GlobalNameMapBuilder.MarkNameAsReferenced(ImportData.ObjectName);
|
|
FScriptObjectEntry Entry;
|
|
Entry.ObjectName = GlobalNameMapBuilder.MapName(ImportData.ObjectName).ToUnresolvedMinimalName();
|
|
Entry.GlobalIndex = ImportData.GlobalIndex;
|
|
Entry.OuterIndex = ImportData.OuterIndex;
|
|
Entry.CDOClassIndex = ImportData.CDOClassIndex;
|
|
|
|
InitialLoadArchive << Entry;
|
|
}
|
|
};
|
|
|
|
static FIoBuffer CreateExportBundleBuffer(const FContainerTargetFile& TargetFile, const TArray<FObjectExport>& ObjectExports, const FIoBuffer UExpBuffer)
|
|
{
|
|
const FPackage* Package = TargetFile.Package;
|
|
check(TargetFile.PackageHeaderData.Num() > 0);
|
|
const uint64 BundleBufferSize = TargetFile.PackageHeaderData.Num() + TargetFile.Package->ExportsSerialSize;
|
|
FIoBuffer BundleBuffer(BundleBufferSize);
|
|
FMemory::Memcpy(BundleBuffer.Data(), TargetFile.PackageHeaderData.GetData(), TargetFile.PackageHeaderData.Num());
|
|
uint64 BundleBufferOffset = TargetFile.PackageHeaderData.Num();
|
|
for (const FExportBundle& ExportBundle : TargetFile.Package->ExportBundles)
|
|
{
|
|
for (const FExportGraphNode* Node : ExportBundle.Nodes)
|
|
{
|
|
if (Node->BundleEntry.CommandType == FExportBundleEntry::ExportCommandType_Serialize)
|
|
{
|
|
const FObjectExport& ObjectExport = ObjectExports[Package->ExportIndexOffset + Node->BundleEntry.LocalExportIndex];
|
|
const int64 Offset = ObjectExport.SerialOffset - Package->UAssetSize;
|
|
check(uint64(Offset + ObjectExport.SerialSize) <= UExpBuffer.DataSize());
|
|
FMemory::Memcpy(BundleBuffer.Data() + BundleBufferOffset, UExpBuffer.Data() + Offset, ObjectExport.SerialSize);
|
|
BundleBufferOffset += ObjectExport.SerialSize;
|
|
}
|
|
}
|
|
}
|
|
check(BundleBufferOffset == BundleBuffer.DataSize());
|
|
return BundleBuffer;
|
|
}
|
|
|
|
static void ParsePackageAssets(
|
|
TArray<FPackage*>& Packages,
|
|
FPackageAssetData& PackageAssetData)
|
|
{
|
|
IOSTORE_CPU_SCOPE(ParsePackageAssets);
|
|
UE_LOG(LogIoStore, Display, TEXT("Parsing packages..."));
|
|
|
|
TAtomic<int32> ReadCount {0};
|
|
TAtomic<int32> ParseCount {0};
|
|
const int32 TotalPackageCount = Packages.Num();
|
|
|
|
TArray<FPackageFileSummary> PackageFileSummaries;
|
|
PackageFileSummaries.SetNum(TotalPackageCount);
|
|
|
|
uint8* UAssetMemory = nullptr;
|
|
TArray<uint8*> PackageAssetBuffers;
|
|
PackageAssetBuffers.SetNum(TotalPackageCount);
|
|
|
|
UE_LOG(LogIoStore, Display, TEXT("Reading package assets..."));
|
|
{
|
|
IOSTORE_CPU_SCOPE(ReadUAssetFiles);
|
|
|
|
uint64 TotalUAssetSize = 0;
|
|
for (const FPackage* Package : Packages)
|
|
{
|
|
TotalUAssetSize += Package->UAssetSize;
|
|
}
|
|
UAssetMemory = reinterpret_cast<uint8*>(FMemory::Malloc(TotalUAssetSize));
|
|
uint8* UAssetMemoryPtr = UAssetMemory;
|
|
for (int32 Index = 0; Index < TotalPackageCount; ++Index)
|
|
{
|
|
PackageAssetBuffers[Index] = UAssetMemoryPtr;
|
|
UAssetMemoryPtr += Packages[Index]->UAssetSize;
|
|
}
|
|
|
|
TAtomic<uint64> CurrentFileIndex{ 0 };
|
|
ParallelFor(TotalPackageCount, [&ReadCount, &PackageAssetBuffers, &Packages, &CurrentFileIndex](int32 Index)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(ReadUAssetFile);
|
|
FPackage* Package = Packages[Index];
|
|
uint8* Buffer = PackageAssetBuffers[Index];
|
|
IFileHandle* FileHandle = FPlatformFileManager::Get().GetPlatformFile().OpenRead(*Package->FileName);
|
|
if (FileHandle)
|
|
{
|
|
bool bSuccess = FileHandle->Read(Buffer, Package->UAssetSize);
|
|
UE_CLOG(!bSuccess, LogIoStore, Warning, TEXT("Failed reading file '%s'"), *Package->FileName);
|
|
delete FileHandle;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogIoStore, Warning, TEXT("Couldn't open file '%s'"), *Package->FileName);
|
|
}
|
|
uint64 LocalFileIndex = CurrentFileIndex.IncrementExchange() + 1;
|
|
UE_CLOG(LocalFileIndex % 1000 == 0, LogIoStore, Display, TEXT("Reading %d/%d: '%s'"), LocalFileIndex, Packages.Num(), *Package->FileName);
|
|
}, EParallelForFlags::Unbalanced);
|
|
}
|
|
|
|
{
|
|
IOSTORE_CPU_SCOPE(SerializeSummaries);
|
|
|
|
ParallelFor(TotalPackageCount, [
|
|
&ReadCount,
|
|
&PackageAssetBuffers,
|
|
&PackageFileSummaries,
|
|
&Packages](int32 Index)
|
|
{
|
|
uint8* PackageBuffer = PackageAssetBuffers[Index];
|
|
FPackageFileSummary& Summary = PackageFileSummaries[Index];
|
|
FPackage& Package = *Packages[Index];
|
|
|
|
TArrayView<const uint8> MemView(PackageBuffer, Package.UAssetSize);
|
|
if (!Package.UAssetSize)
|
|
{
|
|
return;
|
|
}
|
|
|
|
FMemoryReaderView Ar(MemView);
|
|
Ar << Summary;
|
|
|
|
Package.SummarySize = Ar.Tell();
|
|
Package.NameCount = Summary.NameCount;
|
|
Package.ImportCount = Summary.ImportCount;
|
|
Package.PreloadDependencyCount = Summary.PreloadDependencyCount;
|
|
Package.ExportCount = Summary.ExportCount;
|
|
Package.PackageFlags = Summary.PackageFlags;
|
|
Package.CookedHeaderSize = Summary.TotalHeaderSize;
|
|
|
|
}, EParallelForFlags::Unbalanced);
|
|
}
|
|
|
|
int32 TotalImportCount = 0;
|
|
int32 TotalPreloadDependencyCount = 0;
|
|
int32 TotalExportCount = 0;
|
|
for (FPackage* Package : Packages)
|
|
{
|
|
if (Package->ImportCount > 0)
|
|
{
|
|
Package->ImportIndexOffset = TotalImportCount;
|
|
TotalImportCount += Package->ImportCount;
|
|
}
|
|
|
|
if (Package->PreloadDependencyCount > 0)
|
|
{
|
|
Package->PreloadIndexOffset = TotalPreloadDependencyCount;
|
|
TotalPreloadDependencyCount += Package->PreloadDependencyCount;
|
|
}
|
|
|
|
if (Package->ExportCount > 0)
|
|
{
|
|
Package->ExportIndexOffset = TotalExportCount;
|
|
TotalExportCount += Package->ExportCount;
|
|
}
|
|
}
|
|
PackageAssetData.ObjectImports.AddUninitialized(TotalImportCount);
|
|
PackageAssetData.PreloadDependencies.AddUninitialized(TotalPreloadDependencyCount);
|
|
PackageAssetData.ObjectExports.AddUninitialized(TotalExportCount);
|
|
|
|
UE_LOG(LogIoStore, Display, TEXT("Parsing package assets..."));
|
|
{
|
|
IOSTORE_CPU_SCOPE(SerializeAssets);
|
|
|
|
for (int32 PackageIndex = 0; PackageIndex < TotalPackageCount; ++PackageIndex)
|
|
{
|
|
uint8* PackageBuffer = PackageAssetBuffers[PackageIndex];
|
|
const FPackageFileSummary& Summary = PackageFileSummaries[PackageIndex];
|
|
FPackage& Package = *Packages[PackageIndex];
|
|
TArrayView<const uint8> MemView(PackageBuffer, Package.UAssetSize);
|
|
FMemoryReaderView Ar(MemView);
|
|
|
|
if (Summary.NameCount > 0)
|
|
{
|
|
Ar.Seek(Summary.NameOffset);
|
|
|
|
FNameEntrySerialized NameEntry(ENAME_LinkerConstructor);
|
|
|
|
Package.SummaryNames.Reserve(Summary.NameCount);
|
|
for (int32 I = 0; I < Summary.NameCount; ++I)
|
|
{
|
|
Ar << NameEntry;
|
|
FName Name(NameEntry);
|
|
Package.SummaryNames.Add(Name);
|
|
Package.LocalNameMapBuilder.AddName(Name);
|
|
}
|
|
}
|
|
}
|
|
|
|
ParallelFor(TotalPackageCount,[
|
|
&ParseCount,
|
|
&PackageAssetBuffers,
|
|
&PackageFileSummaries,
|
|
&Packages,
|
|
&PackageAssetData](int32 Index)
|
|
{
|
|
uint8* PackageBuffer = PackageAssetBuffers[Index];
|
|
const FPackageFileSummary& Summary = PackageFileSummaries[Index];
|
|
FPackage& Package = *Packages[Index];
|
|
TArrayView<const uint8> MemView(PackageBuffer, Package.UAssetSize);
|
|
FMemoryReaderView Ar(MemView);
|
|
Ar.SetFilterEditorOnly((Package.PackageFlags & EPackageFlags::PKG_FilterEditorOnly) != 0);
|
|
|
|
IOSTORE_CPU_SCOPE_DATA(ParsePackage, TCHAR_TO_ANSI(*Package.FileName));
|
|
|
|
const int32 Count = ParseCount.IncrementExchange();
|
|
UE_CLOG(Count % 1000 == 0, LogIoStore, Display, TEXT("Parsing %d/%d: '%s'"), Count, Packages.Num(), *Package.FileName);
|
|
|
|
if (Summary.ImportCount > 0)
|
|
{
|
|
FNameReaderProxyArchive ProxyAr(Ar, Package.LocalNameMapBuilder.GetNameMap());
|
|
ProxyAr.Seek(Summary.ImportOffset);
|
|
|
|
for (int32 I = 0; I < Summary.ImportCount; ++I)
|
|
{
|
|
FObjectImport& ObjectImport = PackageAssetData.ObjectImports[Package.ImportIndexOffset + I];
|
|
ProxyAr << ObjectImport;
|
|
}
|
|
}
|
|
|
|
if (Summary.PreloadDependencyCount > 0)
|
|
{
|
|
Ar.Seek(Summary.PreloadDependencyOffset);
|
|
Ar.Serialize(PackageAssetData.PreloadDependencies.GetData() + Package.PreloadIndexOffset, Summary.PreloadDependencyCount * sizeof(FPackageIndex));
|
|
}
|
|
|
|
if (Summary.ExportCount > 0)
|
|
{
|
|
FNameReaderProxyArchive ProxyAr(Ar, Package.LocalNameMapBuilder.GetNameMap());
|
|
ProxyAr.Seek(Summary.ExportOffset);
|
|
|
|
for (int32 I = 0; I < Summary.ExportCount; ++I)
|
|
{
|
|
FObjectExport& ObjectExport = PackageAssetData.ObjectExports[Package.ExportIndexOffset + I];
|
|
ProxyAr << ObjectExport;
|
|
Package.ExportsSerialSize += ObjectExport.SerialSize;
|
|
}
|
|
}
|
|
}, EParallelForFlags::Unbalanced);
|
|
}
|
|
|
|
FMemory::Free(UAssetMemory);
|
|
}
|
|
|
|
// modified copy from SavePackage
|
|
EObjectMark GetExcludedObjectMarksForTargetPlatform(const ITargetPlatform* TargetPlatform)
|
|
{
|
|
EObjectMark Marks = OBJECTMARK_NOMARKS;
|
|
if (!TargetPlatform->HasEditorOnlyData())
|
|
{
|
|
Marks = (EObjectMark)(Marks | OBJECTMARK_EditorOnly);
|
|
}
|
|
if (TargetPlatform->IsServerOnly())
|
|
{
|
|
Marks = (EObjectMark)(Marks | OBJECTMARK_NotForServer);
|
|
}
|
|
if (TargetPlatform->IsClientOnly())
|
|
{
|
|
Marks = (EObjectMark)(Marks | OBJECTMARK_NotForClient);
|
|
}
|
|
return Marks;
|
|
}
|
|
|
|
// modified copy from SavePackage
|
|
EObjectMark GetExcludedObjectMarksForObject(const UObject* Object, const ITargetPlatform* TargetPlatform)
|
|
{
|
|
EObjectMark Marks = OBJECTMARK_NOMARKS;
|
|
if (!Object->NeedsLoadForClient())
|
|
{
|
|
Marks = (EObjectMark)(Marks | OBJECTMARK_NotForClient);
|
|
}
|
|
if (!Object->NeedsLoadForServer())
|
|
{
|
|
Marks = (EObjectMark)(Marks | OBJECTMARK_NotForServer);
|
|
}
|
|
if (!Object->NeedsLoadForTargetPlatform(TargetPlatform))
|
|
{
|
|
Marks = (EObjectMark)(Marks | OBJECTMARK_NotForClient | OBJECTMARK_NotForServer);
|
|
}
|
|
if (Object->IsEditorOnly())
|
|
{
|
|
Marks = (EObjectMark)(Marks | OBJECTMARK_EditorOnly);
|
|
}
|
|
if ((Marks & OBJECTMARK_NotForClient) && (Marks & OBJECTMARK_NotForServer))
|
|
{
|
|
Marks = (EObjectMark)(Marks | OBJECTMARK_EditorOnly);
|
|
}
|
|
return Marks;
|
|
}
|
|
|
|
static void FindScriptObjectsRecursive(
|
|
FGlobalPackageData& GlobalPackageData,
|
|
FPackageObjectIndex OuterIndex,
|
|
UObject* Object,
|
|
const ITargetPlatform* TargetPlatform,
|
|
const EObjectMark ExcludedObjectMarks)
|
|
{
|
|
if (!Object->HasAllFlags(RF_Public))
|
|
{
|
|
UE_LOG(LogIoStore, Log, TEXT("Skipping script object: %s (!RF_Public)"), *Object->GetFullName());
|
|
return;
|
|
}
|
|
|
|
const UObject* ObjectForExclusion = Object->HasAnyFlags(RF_ClassDefaultObject) ? (const UObject*)Object->GetClass() : Object;
|
|
const EObjectMark ObjectMarks = GetExcludedObjectMarksForObject(ObjectForExclusion, TargetPlatform);
|
|
|
|
if (ObjectMarks & ExcludedObjectMarks)
|
|
{
|
|
UE_LOG(LogIoStore, Log, TEXT("Skipping script object: %s (Excluded for target platform)"), *Object->GetFullName());
|
|
return;
|
|
}
|
|
|
|
FGlobalScriptObjects& ScriptObjects = GlobalPackageData.ScriptObjects;
|
|
const FScriptObjectData* Outer = ScriptObjects.Find(OuterIndex);
|
|
check(Outer);
|
|
|
|
FName ObjectName = Object->GetFName();
|
|
|
|
FString TempFullName = ScriptObjects.FindRef(OuterIndex).FullName;
|
|
TempFullName.AppendChar(TEXT('/'));
|
|
ObjectName.AppendString(TempFullName);
|
|
|
|
TempFullName.ToLowerInline();
|
|
FPackageObjectIndex GlobalImportIndex = FPackageObjectIndex::FromScriptPath(TempFullName);
|
|
|
|
check(!GlobalPackageData.ImportsByFullName.Contains(TempFullName));
|
|
FScriptObjectData* ScriptImport = ScriptObjects.Find(GlobalImportIndex);
|
|
if (ScriptImport)
|
|
{
|
|
UE_LOG(LogIoStore, Fatal, TEXT("Import name hash collision \"%s\" and \"%s"), *TempFullName, *ScriptImport->FullName);
|
|
}
|
|
|
|
FPackageObjectIndex CDOClassIndex = Outer->CDOClassIndex;
|
|
if (CDOClassIndex.IsNull())
|
|
{
|
|
TCHAR NameBuffer[FName::StringBufferSize];
|
|
uint32 Len = ObjectName.ToString(NameBuffer);
|
|
if (FCString::Strncmp(NameBuffer, TEXT("Default__"), 9) == 0)
|
|
{
|
|
FString CDOClassFullName = Outer->FullName;
|
|
CDOClassFullName.AppendChar(TEXT('/'));
|
|
CDOClassFullName.AppendChars(NameBuffer + 9, Len - 9);
|
|
CDOClassFullName.ToLowerInline();
|
|
|
|
CDOClassIndex = GlobalPackageData.ImportsByFullName.FindRef(CDOClassFullName);
|
|
check(CDOClassIndex.IsScriptImport());
|
|
}
|
|
}
|
|
|
|
GlobalPackageData.ImportsByFullName.Add(TempFullName, GlobalImportIndex);
|
|
ScriptImport = &ScriptObjects.Add(GlobalImportIndex);
|
|
ScriptImport->GlobalIndex = GlobalImportIndex;
|
|
ScriptImport->FullName = MoveTemp(TempFullName);
|
|
ScriptImport->OuterIndex = Outer->GlobalIndex;
|
|
ScriptImport->ObjectName = ObjectName;
|
|
ScriptImport->CDOClassIndex = CDOClassIndex;
|
|
|
|
TArray<UObject*> InnerObjects;
|
|
GetObjectsWithOuter(Object, InnerObjects, /*bIncludeNestedObjects*/false);
|
|
for (UObject* InnerObject : InnerObjects)
|
|
{
|
|
FindScriptObjectsRecursive(GlobalPackageData, GlobalImportIndex, InnerObject, TargetPlatform, ExcludedObjectMarks);
|
|
}
|
|
};
|
|
|
|
static void CreateGlobalScriptObjects(
|
|
FGlobalPackageData& GlobalPackageData,
|
|
const ITargetPlatform* TargetPlatform)
|
|
{
|
|
IOSTORE_CPU_SCOPE(CreateGlobalScriptObjects);
|
|
UE_LOG(LogIoStore, Display, TEXT("Creating global script objects..."));
|
|
|
|
const EObjectMark ExcludedObjectMarks = GetExcludedObjectMarksForTargetPlatform(TargetPlatform);
|
|
|
|
TArray<UPackage*> ScriptPackages;
|
|
FindAllRuntimeScriptPackages(ScriptPackages);
|
|
|
|
TArray<UObject*> InnerObjects;
|
|
for (UPackage* Package : ScriptPackages)
|
|
{
|
|
FGlobalScriptObjects& ScriptObjects = GlobalPackageData.ScriptObjects;
|
|
|
|
FName ObjectName = Package->GetFName();
|
|
FString FullName = Package->GetName();
|
|
|
|
FullName.ToLowerInline();
|
|
FPackageObjectIndex GlobalImportIndex = FPackageObjectIndex::FromScriptPath(FullName);
|
|
|
|
check(!GlobalPackageData.ImportsByFullName.Contains(FullName));
|
|
FScriptObjectData* ScriptImport = ScriptObjects.Find(GlobalImportIndex);
|
|
if (ScriptImport)
|
|
{
|
|
UE_LOG(LogIoStore, Fatal, TEXT("Import name hash collision \"%s\" and \"%s"), *FullName, *ScriptImport->FullName);
|
|
}
|
|
|
|
GlobalPackageData.ImportsByFullName.Add(FullName, GlobalImportIndex);
|
|
ScriptImport = &ScriptObjects.Add(GlobalImportIndex);
|
|
ScriptImport->GlobalIndex = GlobalImportIndex;
|
|
ScriptImport->FullName = FullName;
|
|
ScriptImport->OuterIndex = FPackageObjectIndex();
|
|
ScriptImport->ObjectName = ObjectName;
|
|
|
|
InnerObjects.Reset();
|
|
GetObjectsWithOuter(Package, InnerObjects, /*bIncludeNestedObjects*/false);
|
|
for (UObject* InnerObject : InnerObjects)
|
|
{
|
|
FindScriptObjectsRecursive(GlobalPackageData, GlobalImportIndex, InnerObject, TargetPlatform, ExcludedObjectMarks);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void CreateGlobalImportsAndExports(
|
|
const FIoStoreArguments& Arguments,
|
|
TArray<FPackage*>& Packages,
|
|
const FPackageIdMap& PackageIdMap,
|
|
FPackageAssetData& PackageAssetData,
|
|
FGlobalPackageData& GlobalPackageData,
|
|
FExportGraph& ExportGraph)
|
|
{
|
|
IOSTORE_CPU_SCOPE(CreateGlobalImportsAndExports);
|
|
UE_LOG(LogIoStore, Display, TEXT("Creating global imports and exports..."));
|
|
|
|
TArray<FString> TempFullNames;
|
|
const FString DLCPrefix = Arguments.IsDLC() ? FString::Printf(TEXT("/%s/"), *FPaths::GetBaseFilename(Arguments.DLCPluginPath)) : FString();
|
|
|
|
TSet<FPackage*> TempImportedPackages;
|
|
for (FPackage* Package : Packages)
|
|
{
|
|
if (Package->ExportCount == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
TempFullNames.Reset();
|
|
TempFullNames.SetNum(Package->ExportCount, false);
|
|
FObjectExport* ExportMap = PackageAssetData.ObjectExports.GetData() + Package->ExportIndexOffset;
|
|
for (int32 ExportIndex = 0; ExportIndex < Package->ExportCount; ExportIndex++)
|
|
{
|
|
int32 GlobalExportIndex = FindExport(
|
|
GlobalPackageData,
|
|
TempFullNames,
|
|
PackageAssetData.ObjectExports.GetData() + Package->ExportIndexOffset,
|
|
ExportIndex,
|
|
Package);
|
|
|
|
FExportObjectData& ExportData = GlobalPackageData.ExportObjects[GlobalExportIndex];
|
|
ExportData.CreateNode = ExportGraph.AddNode(Package, { uint32(ExportIndex), FExportBundleEntry::ExportCommandType_Create });
|
|
ExportData.SerializeNode = ExportGraph.AddNode(Package, { uint32(ExportIndex), FExportBundleEntry::ExportCommandType_Serialize });
|
|
|
|
Package->Exports.Add(GlobalExportIndex);
|
|
Package->CreateExportNodes.Add(ExportData.CreateNode);
|
|
Package->SerializeExportNodes.Add(ExportData.SerializeNode);
|
|
ExportGraph.AddInternalDependency(ExportData.CreateNode, ExportData.SerializeNode);
|
|
}
|
|
}
|
|
|
|
for (FExportObjectData& Export : GlobalPackageData.ExportObjects)
|
|
{
|
|
if (Export.IsPublicExport())
|
|
{
|
|
TMap<FPackageObjectIndex, int32>& PublicExports = GlobalPackageData.PublicExportIndices;
|
|
FPackageObjectIndex GlobalImportIndex = FPackageObjectIndex::FromPackagePath(Export.FullName);
|
|
|
|
check(!GlobalPackageData.ImportsByFullName.Contains(Export.FullName));
|
|
int32* ExportIndex = PublicExports.Find(GlobalImportIndex);
|
|
if (ExportIndex)
|
|
{
|
|
UE_LOG(LogIoStore, Fatal, TEXT("Import name hash collision \"%s\" and \"%s"),
|
|
*Export.FullName,
|
|
*GlobalPackageData.ExportObjects[*ExportIndex].FullName);
|
|
}
|
|
GlobalPackageData.ImportsByFullName.Add(Export.FullName, GlobalImportIndex);
|
|
PublicExports.Add(GlobalImportIndex, Export.GlobalIndex);
|
|
Export.GlobalImportIndex = GlobalImportIndex;
|
|
}
|
|
}
|
|
|
|
for (FPackage* Package : Packages)
|
|
{
|
|
if (Package->ImportCount == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FObjectImport* ImportMap = PackageAssetData.ObjectImports.GetData() + Package->ImportIndexOffset;
|
|
TempFullNames.Reset();
|
|
TempFullNames.SetNum(Package->ImportCount, false);
|
|
Package->Imports.Reserve(Package->ImportCount);
|
|
Package->ImportedPackages.Reserve(Package->ImportCount / 2);
|
|
TempImportedPackages.Reset();
|
|
|
|
for (int32 ImportIndex = 0; ImportIndex < Package->ImportCount; ++ImportIndex)
|
|
{
|
|
FindImportFullName(TempFullNames, ImportMap, ImportIndex);
|
|
FString& FullName = TempFullNames[ImportIndex];
|
|
FObjectImport& Import = ImportMap[ImportIndex];
|
|
const bool bIsPackage = Import.OuterIndex.IsNull();
|
|
|
|
FPackageObjectIndex GlobalImportIndex = FindAndVerifyGlobalImport(
|
|
Package,
|
|
GlobalPackageData,
|
|
Import,
|
|
FullName,
|
|
DLCPrefix);
|
|
|
|
// When building DLC:s and we don't have all packages available,
|
|
// then a global package import object can be missing and still be valid
|
|
if (GlobalImportIndex.IsNull() && !bIsPackage && !FullName.StartsWith(ScriptPrefix))
|
|
{
|
|
GlobalImportIndex = FPackageObjectIndex::FromPackagePath(FullName);
|
|
}
|
|
|
|
Package->Imports.Add(GlobalImportIndex);
|
|
|
|
if (bIsPackage && GlobalImportIndex.IsNull())
|
|
{
|
|
FPackageId PackageId = FPackageId::FromName(Import.ObjectName);
|
|
Package->ImportedPackageIds.Add(PackageId);
|
|
if (FPackage* ImportedPackage = PackageIdMap.FindRef(PackageId))
|
|
{
|
|
Package->ImportedPackages.Add(ImportedPackage);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void MapExportEntryIndices(
|
|
TArray<FObjectExport>& ObjectExports,
|
|
TArray<FExportObjectData>& GlobalExports,
|
|
TArray<FPackage*>& Packages)
|
|
{
|
|
IOSTORE_CPU_SCOPE(ExportData);
|
|
UE_LOG(LogIoStore, Display, TEXT("Converting export map import indices..."));
|
|
|
|
auto PackageObjectIdFromPackageIndex =
|
|
[](const TArray<FPackageObjectIndex>& Imports, const FPackageIndex& PackageIndex) -> FPackageObjectIndex
|
|
{
|
|
if (PackageIndex.IsImport())
|
|
{
|
|
return Imports[PackageIndex.ToImport()];
|
|
}
|
|
if (PackageIndex.IsExport())
|
|
{
|
|
return FPackageObjectIndex::FromExportIndex(PackageIndex.ToExport());
|
|
}
|
|
return FPackageObjectIndex();
|
|
};
|
|
|
|
for (FPackage* Package : Packages)
|
|
{
|
|
for (int32 I = 0; I < Package->ExportCount; ++I)
|
|
{
|
|
const FObjectExport& ObjectExport = ObjectExports[Package->ExportIndexOffset + I];
|
|
FExportObjectData& ExportData = GlobalExports[Package->Exports[I]];
|
|
ExportData.OuterIndex = PackageObjectIdFromPackageIndex(Package->Imports, ObjectExport.OuterIndex);
|
|
ExportData.ClassIndex = PackageObjectIdFromPackageIndex(Package->Imports, ObjectExport.ClassIndex);
|
|
ExportData.SuperIndex = PackageObjectIdFromPackageIndex(Package->Imports, ObjectExport.SuperIndex);
|
|
ExportData.TemplateIndex = PackageObjectIdFromPackageIndex(Package->Imports, ObjectExport.TemplateIndex);
|
|
}
|
|
}
|
|
};
|
|
|
|
static void ProcessLocalizedPackages(
|
|
const TArray<FPackage*>& Packages,
|
|
const FPackageNameMap& PackageMap,
|
|
FGlobalPackageData& GlobalPackageData,
|
|
FSourceToLocalizedPackageMultimap& OutSourceToLocalizedPackageMap)
|
|
{
|
|
IOSTORE_CPU_SCOPE(ProcessLocalizedPackages);
|
|
|
|
FLocalizedToSourceImportIndexMap LocalizedToSourceImportIndexMap;
|
|
|
|
UE_LOG(LogIoStore, Display, TEXT("Conforming localized packages..."));
|
|
for (FPackage* Package : Packages)
|
|
{
|
|
if (Package->Region.Len() == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
check(!Package->RedirectedPackageId.IsValid());
|
|
if (Package->Name == Package->SourcePackageName)
|
|
{
|
|
UE_LOG(LogIoStore, Error,
|
|
TEXT("For culture '%s': Localized package '%s' (0x%llX) should have a package name different from source name."),
|
|
*Package->Region,
|
|
*Package->Name.ToString(),
|
|
Package->GlobalPackageId.ValueForDebugging())
|
|
continue;
|
|
}
|
|
|
|
FPackage* SourcePackage = PackageMap.FindRef(Package->SourcePackageName);
|
|
if (!SourcePackage)
|
|
{
|
|
// no update or verification required
|
|
UE_LOG(LogIoStore, Verbose,
|
|
TEXT("For culture '%s': Localized package '%s' (0x%llX) is unique and does not override a source package."),
|
|
*Package->Region,
|
|
*Package->Name.ToString(),
|
|
Package->GlobalPackageId.ValueForDebugging());
|
|
continue;
|
|
}
|
|
|
|
Package->SourceGlobalPackageId = SourcePackage->GlobalPackageId;
|
|
|
|
Package->bIsLocalizedAndConformed = ConformLocalizedPackage(
|
|
PackageMap, GlobalPackageData, *SourcePackage,
|
|
*Package, LocalizedToSourceImportIndexMap);
|
|
|
|
if (Package->bIsLocalizedAndConformed)
|
|
{
|
|
UE_LOG(LogIoStore, Verbose, TEXT("For culture '%s': Adding conformed localized package '%s' (0x%llX) for '%s' (0x%llX). ")
|
|
TEXT("When loading the source package, it will be remapped to this localized package."),
|
|
*Package->Region,
|
|
*Package->Name.ToString(),
|
|
Package->GlobalPackageId.ValueForDebugging(),
|
|
*Package->SourcePackageName.ToString(),
|
|
SourcePackage->GlobalPackageId.ValueForDebugging());
|
|
|
|
OutSourceToLocalizedPackageMap.Add(SourcePackage, Package);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogIoStore, Display,
|
|
TEXT("For culture '%s': Localized package '%s' (0x%llX) does not conform to source package '%s' (0x%llX) due to mismatching public exports. ")
|
|
TEXT("When loading the source package, it will never be remapped to this localized package."),
|
|
*Package->Region,
|
|
*Package->Name.ToString(),
|
|
Package->GlobalPackageId.ValueForDebugging(),
|
|
*Package->SourcePackageName.ToString(),
|
|
SourcePackage->GlobalPackageId.ValueForDebugging());
|
|
}
|
|
}
|
|
|
|
UE_LOG(LogIoStore, Display, TEXT("Adding localized import packages..."));
|
|
TArray<FPackage*> LocalizedPackages;
|
|
|
|
for (FPackage* Package : Packages)
|
|
{
|
|
LocalizedPackages.Reset();
|
|
for (FPackage* ImportedPackage : Package->ImportedPackages)
|
|
{
|
|
LocalizedPackages.Reset();
|
|
OutSourceToLocalizedPackageMap.MultiFind(ImportedPackage, LocalizedPackages);
|
|
for (FPackage* LocalizedPackage : LocalizedPackages)
|
|
{
|
|
UE_LOG(LogIoStore, Verbose, TEXT("For package '%s' (0x%llX): Adding localized imported package '%s' (0x%llX)"),
|
|
*Package->Name.ToString(),
|
|
Package->GlobalPackageId.ValueForDebugging(),
|
|
*LocalizedPackage->Name.ToString(),
|
|
LocalizedPackage->GlobalPackageId.ValueForDebugging());
|
|
}
|
|
}
|
|
Package->ImportedPackages.Append(LocalizedPackages);
|
|
}
|
|
|
|
UE_LOG(LogIoStore, Display, TEXT("Conforming localized imports..."));
|
|
for (FPackage* Package : Packages)
|
|
{
|
|
for (FPackageObjectIndex& GlobalImportIndex : Package->Imports)
|
|
{
|
|
if (GlobalImportIndex.IsPackageImport())
|
|
{
|
|
// When building DLC's the export can be missing
|
|
if (const FExportObjectData* Export = GlobalPackageData.FindPublicExport(GlobalImportIndex))
|
|
{
|
|
if (Export->Package->SourcePackageName != Export->Package->Name)
|
|
{
|
|
const FPackageObjectIndex* SourceGlobalImportIndex = LocalizedToSourceImportIndexMap.Find(GlobalImportIndex);
|
|
if (SourceGlobalImportIndex)
|
|
{
|
|
GlobalImportIndex = *SourceGlobalImportIndex;
|
|
|
|
const FExportObjectData& SourceExportData = *GlobalPackageData.FindPublicExport(*SourceGlobalImportIndex);
|
|
UE_LOG(LogIoStore, Verbose,
|
|
TEXT("For package '%s' (0x%llX): Remap localized import %s to source import %s (in a conformed localized package)"),
|
|
*Package->Name.ToString(),
|
|
Package->GlobalPackageId.ValueForDebugging(),
|
|
*Export->FullName,
|
|
*SourceExportData.FullName);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogIoStore, Verbose,
|
|
TEXT("For package '%s' (0x%llX): Skip remap for localized import %s")
|
|
TEXT(", either there is no source package or the localized package did not conform to it."),
|
|
*Package->Name.ToString(),
|
|
Package->GlobalPackageId.ValueForDebugging(),
|
|
*Export->FullName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TUniquePtr<FIoStoreReader> CreateIoStoreReader(const TCHAR* Path, const FKeyChain& KeyChain)
|
|
{
|
|
FIoStoreEnvironment IoEnvironment;
|
|
IoEnvironment.InitializeFileEnvironment(FPaths::ChangeExtension(Path, TEXT("")));
|
|
TUniquePtr<FIoStoreReader> IoStoreReader(new FIoStoreReader());
|
|
|
|
TMap<FGuid, FAES::FAESKey> DecryptionKeys;
|
|
for (const auto& KV : KeyChain.EncryptionKeys)
|
|
{
|
|
DecryptionKeys.Add(KV.Key, KV.Value.Key);
|
|
}
|
|
FIoStatus Status = IoStoreReader->Initialize(IoEnvironment, DecryptionKeys);
|
|
if (Status.IsOk())
|
|
{
|
|
return IoStoreReader;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogIoStore, Warning, TEXT("Failed creating IoStore reader '%s' [%s]"), *Path, *Status.ToString())
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
TArray<TUniquePtr<FIoStoreReader>> CreatePatchSourceReaders(const TArray<FString>& Files, const FIoStoreArguments& Arguments)
|
|
{
|
|
TArray<TUniquePtr<FIoStoreReader>> Readers;
|
|
for (const FString& PatchSourceContainerFile : Files)
|
|
{
|
|
TUniquePtr<FIoStoreReader> Reader = CreateIoStoreReader(*PatchSourceContainerFile, Arguments.PatchKeyChain);
|
|
if (Reader.IsValid())
|
|
{
|
|
UE_LOG(LogIoStore, Display, TEXT("Loaded patch source container '%s'"), *PatchSourceContainerFile);
|
|
Readers.Add(MoveTemp(Reader));
|
|
}
|
|
}
|
|
return Readers;
|
|
}
|
|
|
|
void InitializeContainerTargetsAndPackages(
|
|
const FIoStoreArguments& Arguments,
|
|
TArray<FPackage*>& Packages,
|
|
FPackageNameMap& PackageNameMap,
|
|
FPackageIdMap& PackageIdMap,
|
|
TArray<FContainerTargetSpec*>& ContainerTargets,
|
|
FNameMapBuilder& GlobalNameMapBuilder)
|
|
{
|
|
FString ProjectName = FApp::GetProjectName();
|
|
FString RelativeEnginePath = FPaths::GetRelativePathToRoot();
|
|
FString RelativeProjectPath = FPaths::ProjectDir();
|
|
int32 CookedEngineDirLen = Arguments.CookedDir.Len() + 1;;
|
|
int32 CookedProjectDirLen = CookedEngineDirLen + ProjectName.Len() + 1;
|
|
|
|
auto ConvertCookedPathToRelativePath = [
|
|
&ProjectName,
|
|
&CookedEngineDirLen,
|
|
&CookedProjectDirLen,
|
|
&RelativeEnginePath,
|
|
&RelativeProjectPath]
|
|
(const FString& CookedFile) -> FString
|
|
{
|
|
FString RelativeFileName;
|
|
const TCHAR* FileName = *CookedFile + CookedEngineDirLen;
|
|
if (FCString::Strncmp(FileName, *ProjectName, ProjectName.Len()))
|
|
{
|
|
int32 FileNameLen = CookedFile.Len() - CookedEngineDirLen;
|
|
RelativeFileName.Reserve(RelativeEnginePath.Len() + FileNameLen);
|
|
RelativeFileName = RelativeEnginePath;
|
|
RelativeFileName.AppendChars(*CookedFile + CookedEngineDirLen, FileNameLen);
|
|
}
|
|
else
|
|
{
|
|
FileName = *CookedFile + CookedProjectDirLen;
|
|
int32 FileNameLen = CookedFile.Len() - CookedProjectDirLen;
|
|
RelativeFileName.Reserve(RelativeProjectPath.Len() + FileNameLen);
|
|
RelativeFileName = RelativeProjectPath;
|
|
RelativeFileName.AppendChars(*CookedFile + CookedProjectDirLen, FileNameLen);
|
|
}
|
|
return RelativeFileName;
|
|
};
|
|
|
|
for (const FContainerSourceSpec& ContainerSource : Arguments.Containers)
|
|
{
|
|
FContainerTargetSpec* ContainerTarget = AddContainer(ContainerSource.Name, ContainerTargets);
|
|
ContainerTarget->OutputPath = ContainerSource.OutputPath;
|
|
ContainerTarget->bGenerateDiffPatch = ContainerSource.bGenerateDiffPatch;
|
|
if (Arguments.bSign)
|
|
{
|
|
ContainerTarget->ContainerFlags |= EIoContainerFlags::Signed;
|
|
}
|
|
|
|
if (!ContainerTarget->EncryptionKeyGuid.IsValid())
|
|
{
|
|
ContainerTarget->EncryptionKeyGuid = ContainerSource.EncryptionKeyOverrideGuid;
|
|
}
|
|
|
|
ContainerTarget->PatchSourceReaders = CreatePatchSourceReaders(ContainerSource.PatchSourceContainerFiles, Arguments);
|
|
|
|
ContainerTarget->LocalNameMapBuilder.SetNameMapType(FMappedName::EType::Container);
|
|
{
|
|
IOSTORE_CPU_SCOPE(ProcessSourceFiles);
|
|
for (const FContainerSourceFile& SourceFile : ContainerSource.SourceFiles)
|
|
{
|
|
const FCookedFileStatData* OriginalCookedFileStatData = Arguments.CookedFileStatMap.Find(SourceFile.NormalizedPath);
|
|
if (!OriginalCookedFileStatData)
|
|
{
|
|
UE_LOG(LogIoStore, Warning, TEXT("File not found: '%s'"), *SourceFile.NormalizedPath);
|
|
continue;
|
|
}
|
|
const FCookedFileStatData* CookedFileStatData = OriginalCookedFileStatData;
|
|
FString NormalizedSourcePath = SourceFile.NormalizedPath;
|
|
if (CookedFileStatData->FileType == FCookedFileStatData::PackageHeader)
|
|
{
|
|
NormalizedSourcePath = FPaths::ChangeExtension(SourceFile.NormalizedPath, TEXT(".uexp"));
|
|
CookedFileStatData = Arguments.CookedFileStatMap.Find(NormalizedSourcePath);
|
|
if (!CookedFileStatData)
|
|
{
|
|
UE_LOG(LogIoStore, Warning, TEXT("File not found: '%s'"), *NormalizedSourcePath);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
FString RelativeFileName = ConvertCookedPathToRelativePath(SourceFile.NormalizedPath);
|
|
FPackage* Package = nullptr;
|
|
const bool bIsMemoryMappedBulkData = CookedFileStatData->FileExt == FCookedFileStatData::EFileExt::UMappedBulk;
|
|
|
|
if (bIsMemoryMappedBulkData)
|
|
{
|
|
FString TmpFileName = FString(RelativeFileName.Len() - 8, GetData(RelativeFileName)) + TEXT(".ubulk");
|
|
Package = FindOrAddPackage(Arguments, *TmpFileName, Packages, PackageNameMap, PackageIdMap);
|
|
}
|
|
else
|
|
{
|
|
Package = FindOrAddPackage(Arguments, *RelativeFileName, Packages, PackageNameMap, PackageIdMap);
|
|
}
|
|
|
|
if (Package)
|
|
{
|
|
FContainerTargetFile& TargetFile = ContainerTarget->TargetFiles.AddDefaulted_GetRef();
|
|
TargetFile.ContainerTarget = ContainerTarget;
|
|
TargetFile.SourceSize = uint64(CookedFileStatData->FileSize);
|
|
TargetFile.NormalizedSourcePath = NormalizedSourcePath;
|
|
TargetFile.TargetPath = MoveTemp(RelativeFileName);
|
|
TargetFile.Package = Package;
|
|
if (SourceFile.bNeedsCompression)
|
|
{
|
|
ContainerTarget->ContainerFlags |= EIoContainerFlags::Compressed;
|
|
}
|
|
else
|
|
{
|
|
TargetFile.bForceUncompressed = true;
|
|
}
|
|
if (SourceFile.bNeedsEncryption)
|
|
{
|
|
ContainerTarget->ContainerFlags |= EIoContainerFlags::Encrypted;
|
|
ContainerTarget->bUseLocalNameMap = true;
|
|
}
|
|
|
|
if (CookedFileStatData->FileType == FCookedFileStatData::BulkData)
|
|
{
|
|
TargetFile.bIsBulkData = true;
|
|
if (CookedFileStatData->FileExt == FCookedFileStatData::UPtnl)
|
|
{
|
|
TargetFile.bIsOptionalBulkData = true;
|
|
TargetFile.ChunkId = CreateChunkId(Package->GlobalPackageId, 0, BulkdataTypeToChunkIdType(FPackageStoreBulkDataManifest::EBulkdataType::Optional), *TargetFile.TargetPath);
|
|
}
|
|
else if (CookedFileStatData->FileExt == FCookedFileStatData::UMappedBulk)
|
|
{
|
|
TargetFile.bIsMemoryMappedBulkData = true;
|
|
TargetFile.bForceUncompressed = true;
|
|
TargetFile.ChunkId = CreateChunkId(Package->GlobalPackageId, 0, BulkdataTypeToChunkIdType(FPackageStoreBulkDataManifest::EBulkdataType::MemoryMapped), *TargetFile.TargetPath);
|
|
}
|
|
else
|
|
{
|
|
TargetFile.ChunkId = CreateChunkId(Package->GlobalPackageId, 0, BulkdataTypeToChunkIdType(FPackageStoreBulkDataManifest::EBulkdataType::Normal), *TargetFile.TargetPath);
|
|
}
|
|
if (Package->FileName.IsEmpty())
|
|
{
|
|
Package->FileName = FPaths::ChangeExtension(SourceFile.NormalizedPath, TEXT(".uasset"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
check(CookedFileStatData->FileType == FCookedFileStatData::PackageData);
|
|
|
|
++ContainerTarget->PackageCount;
|
|
|
|
Package->FileName = SourceFile.NormalizedPath; // .uasset path
|
|
Package->UAssetSize = OriginalCookedFileStatData->FileSize;
|
|
Package->UExpSize = CookedFileStatData->FileSize;
|
|
TargetFile.ChunkId = CreateChunkId(Package->GlobalPackageId, 0, EIoChunkType::ExportBundleData, *TargetFile.TargetPath);
|
|
TargetFile.NameMapBuilder = &Package->LocalNameMapBuilder;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!ContainerTarget->bUseLocalNameMap)
|
|
{
|
|
ContainerTarget->NameMapBuilder = &GlobalNameMapBuilder;
|
|
}
|
|
}
|
|
}
|
|
|
|
Algo::Sort(Packages, [](const FPackage* A, const FPackage* B)
|
|
{
|
|
return A->GlobalPackageId < B->GlobalPackageId;
|
|
});
|
|
};
|
|
|
|
void LogWriterResults(const TArray<FIoStoreWriterResult>& Results)
|
|
{
|
|
UE_LOG(LogIoStore, Display, TEXT("--------------------------------------------------- IoDispatcher --------------------------------------------------------"));
|
|
UE_LOG(LogIoStore, Display, TEXT(""));
|
|
UE_LOG(LogIoStore, Display, TEXT("%-30s %10s %15s %15s %15s %25s"),
|
|
TEXT("Container"), TEXT("Flags"), TEXT("TOC Size (KB)"), TEXT("TOC Entries"), TEXT("Size (MB)"), TEXT("Compressed (MB)"));
|
|
UE_LOG(LogIoStore, Display, TEXT("-------------------------------------------------------------------------------------------------------------------------"));
|
|
uint64 TotalTocSize = 0;
|
|
uint64 TotalTocEntryCount = 0;
|
|
uint64 TotalUncompressedContainerSize = 0;
|
|
uint64 TotalPaddingSize = 0;
|
|
for (const FIoStoreWriterResult& Result : Results)
|
|
{
|
|
FString CompressionInfo = TEXT("-");
|
|
|
|
if (Result.CompressionMethod != NAME_None)
|
|
{
|
|
double Procentage = (double(Result.UncompressedContainerSize - Result.CompressedContainerSize) / double(Result.UncompressedContainerSize)) * 100.0;
|
|
CompressionInfo = FString::Printf(TEXT("%.2lf (%.2lf%% %s)"),
|
|
(double)Result.CompressedContainerSize / 1024.0 / 1024.0,
|
|
Procentage,
|
|
*Result.CompressionMethod.ToString());
|
|
}
|
|
|
|
FString ContainerSettings = FString::Printf(TEXT("%s/%s/%s"),
|
|
EnumHasAnyFlags(Result.ContainerFlags, EIoContainerFlags::Compressed) ? TEXT("C") : TEXT("-"),
|
|
EnumHasAnyFlags(Result.ContainerFlags, EIoContainerFlags::Encrypted) ? TEXT("E") : TEXT("-"),
|
|
EnumHasAnyFlags(Result.ContainerFlags, EIoContainerFlags::Signed) ? TEXT("S") : TEXT("-"));
|
|
|
|
UE_LOG(LogIoStore, Display, TEXT("%-30s %10s %15.2lf %15llu %15.2lf %25s"),
|
|
*Result.ContainerName,
|
|
*ContainerSettings,
|
|
(double)Result.TocSize / 1024.0,
|
|
Result.TocEntryCount,
|
|
(double)Result.UncompressedContainerSize / 1024.0 / 1024.0,
|
|
*CompressionInfo);
|
|
|
|
|
|
TotalTocSize += Result.TocSize;
|
|
TotalTocEntryCount += Result.TocEntryCount;
|
|
TotalUncompressedContainerSize += Result.UncompressedContainerSize;
|
|
TotalPaddingSize += Result.PaddingSize;
|
|
}
|
|
|
|
UE_LOG(LogIoStore, Display, TEXT("%-30s %10s %15.2lf %15llu %15.2lf %25s"),
|
|
TEXT("TOTAL"),
|
|
TEXT(""),
|
|
(double)TotalTocSize / 1024.0,
|
|
TotalTocEntryCount,
|
|
(double)TotalUncompressedContainerSize / 1024.0 / 1024.0,
|
|
TEXT("-"));
|
|
|
|
UE_LOG(LogIoStore, Display, TEXT(""));
|
|
UE_LOG(LogIoStore, Display, TEXT("** Flags: (C)ompressed / (E)ncrypted / (S)igned) **"));
|
|
UE_LOG(LogIoStore, Display, TEXT(""));
|
|
UE_LOG(LogIoStore, Display, TEXT("Compression block padding: %8.2lf MB"), (double)TotalPaddingSize / 1024.0 / 1024.0);
|
|
UE_LOG(LogIoStore, Display, TEXT(""));
|
|
}
|
|
|
|
void LogContainerPackageInfo(const TArray<FContainerTargetSpec*>& ContainerTargets)
|
|
{
|
|
uint64 TotalStoreSize = 0;
|
|
uint64 TotalPackageCount = 0;
|
|
uint64 TotalLocalizedPackageCount = 0;
|
|
|
|
UE_LOG(LogIoStore, Display, TEXT(""));
|
|
UE_LOG(LogIoStore, Display, TEXT(""));
|
|
UE_LOG(LogIoStore, Display, TEXT("--------------------------------------------------- PackageStore (KB) ---------------------------------------------------"));
|
|
UE_LOG(LogIoStore, Display, TEXT(""));
|
|
UE_LOG(LogIoStore, Display, TEXT("%-30s %20s %20s %20s"),
|
|
TEXT("Container"),
|
|
TEXT("Store Size"),
|
|
TEXT("Packages"),
|
|
TEXT("Localized"));
|
|
UE_LOG(LogIoStore, Display, TEXT("-------------------------------------------------------------------------------------------------------------------------"));
|
|
|
|
for (const FContainerTargetSpec* ContainerTarget : ContainerTargets)
|
|
{
|
|
uint64 StoreSize = ContainerTarget->Header.StoreEntries.Num();
|
|
uint64 PackageCount = ContainerTarget->PackageCount;
|
|
uint64 LocalizedPackageCount = 0;
|
|
|
|
for (const auto& KV : ContainerTarget->Header.CulturePackageMap)
|
|
{
|
|
LocalizedPackageCount += KV.Value.Num();
|
|
}
|
|
|
|
UE_LOG(LogIoStore, Display, TEXT("%-30s %20.0lf %20llu %20llu"),
|
|
*ContainerTarget->Name.ToString(),
|
|
(double)StoreSize / 1024.0,
|
|
PackageCount,
|
|
LocalizedPackageCount);
|
|
|
|
TotalStoreSize += StoreSize;
|
|
TotalPackageCount += PackageCount;
|
|
TotalLocalizedPackageCount += LocalizedPackageCount;
|
|
}
|
|
UE_LOG(LogIoStore, Display, TEXT("%-30s %20.0lf %20llu %20llu"),
|
|
TEXT("TOTAL"),
|
|
(double)TotalStoreSize / 1024.0,
|
|
TotalPackageCount,
|
|
TotalLocalizedPackageCount);
|
|
|
|
|
|
uint64 TotalHeaderSize = 0;
|
|
uint64 TotalSummarySize = 0;
|
|
uint64 TotalUGraphSize = 0;
|
|
uint64 TotalImportMapSize = 0;
|
|
uint64 TotalExportMapSize = 0;
|
|
uint64 TotalNameMapSize = 0;
|
|
|
|
UE_LOG(LogIoStore, Display, TEXT(""));
|
|
UE_LOG(LogIoStore, Display, TEXT(""));
|
|
UE_LOG(LogIoStore, Display, TEXT("--------------------------------------------------- PackageHeader (KB) --------------------------------------------------"));
|
|
UE_LOG(LogIoStore, Display, TEXT(""));
|
|
UE_LOG(LogIoStore, Display, TEXT("%-30s %13s %13s %13s %13s %13s %13s"),
|
|
TEXT("Container"),
|
|
TEXT("Header"),
|
|
TEXT("Summary"),
|
|
TEXT("Graph"),
|
|
TEXT("ImportMap"),
|
|
TEXT("ExportMap"),
|
|
TEXT("NameMap"));
|
|
UE_LOG(LogIoStore, Display, TEXT("-------------------------------------------------------------------------------------------------------------------------"));
|
|
for (const FContainerTargetSpec* ContainerTarget : ContainerTargets)
|
|
{
|
|
uint64 HeaderSize = 0;
|
|
uint64 SummarySize = ContainerTarget->PackageCount * sizeof(FPackageSummary);
|
|
uint64 UGraphSize = 0;
|
|
uint64 ImportMapSize = 0;
|
|
uint64 ExportMapSize = 0;
|
|
uint64 NameMapSize = 0;
|
|
|
|
for (const FContainerTargetFile& TargetFile : ContainerTarget->TargetFiles)
|
|
{
|
|
if (TargetFile.bIsBulkData)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
UGraphSize += TargetFile.UGraphSize;
|
|
ImportMapSize += TargetFile.ImportMapSize;
|
|
ExportMapSize += TargetFile.ExportMapSize;
|
|
NameMapSize += TargetFile.NameMapSize;
|
|
}
|
|
|
|
HeaderSize = SummarySize + UGraphSize + ImportMapSize + ExportMapSize + NameMapSize;
|
|
|
|
UE_LOG(LogIoStore, Display, TEXT("%-30s %13.0lf %13.0lf %13.0lf %13.0lf %13.0lf %13.0lf"),
|
|
*ContainerTarget->Name.ToString(),
|
|
(double)HeaderSize / 1024.0,
|
|
(double)SummarySize / 1024.0,
|
|
(double)UGraphSize / 1024.0,
|
|
(double)ImportMapSize / 1024.0,
|
|
(double)ExportMapSize / 1024.0,
|
|
(double)NameMapSize / 1024.0);
|
|
|
|
TotalHeaderSize += HeaderSize;
|
|
TotalSummarySize += SummarySize;
|
|
TotalUGraphSize += UGraphSize;
|
|
TotalImportMapSize += ImportMapSize;
|
|
TotalExportMapSize += ExportMapSize;
|
|
TotalNameMapSize += NameMapSize;
|
|
}
|
|
|
|
UE_LOG(LogIoStore, Display, TEXT("%-30s %13.0lf %13.0lf %13.0lf %13.0lf %13.0lf %13.0lf"),
|
|
TEXT("TOTAL"),
|
|
(double)TotalHeaderSize / 1024.0,
|
|
(double)TotalSummarySize / 1024.0,
|
|
(double)TotalUGraphSize / 1024.0,
|
|
(double)TotalImportMapSize / 1024.0,
|
|
(double)TotalExportMapSize / 1024.0,
|
|
(double)TotalNameMapSize / 1024.0);
|
|
|
|
UE_LOG(LogIoStore, Display, TEXT(""));
|
|
UE_LOG(LogIoStore, Display, TEXT(""));
|
|
}
|
|
|
|
int32 CreateTarget(const FIoStoreArguments& Arguments, const FIoStoreWriterSettings& GeneralIoWriterSettings)
|
|
{
|
|
TGuardValue<int32> GuardAllowUnversionedContentInEditor(GAllowUnversionedContentInEditor, 1);
|
|
|
|
FPackageStoreBulkDataManifest BulkDataManifest(FString(Arguments.CookedDir) / FApp::GetProjectName());
|
|
if (!BulkDataManifest.Load())
|
|
{
|
|
UE_LOG(LogIoStore, Warning, TEXT("Failed to load Bulk Data manifest %s"), *BulkDataManifest.GetFilename());
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogIoStore, Display, TEXT("Loaded Bulk Data manifest '%s'"), *BulkDataManifest.GetFilename());
|
|
}
|
|
|
|
#if OUTPUT_CHUNKID_DIRECTORY
|
|
ChunkIdCsv.CreateOutputFile(CookedDir);
|
|
#endif
|
|
|
|
FNameMapBuilder GlobalNameMapBuilder;
|
|
GlobalNameMapBuilder.SetNameMapType(FMappedName::EType::Global);
|
|
FPackageAssetData PackageAssetData;
|
|
FGlobalPackageData GlobalPackageData;
|
|
|
|
TArray<FPackage*> Packages;
|
|
FPackageNameMap PackageNameMap;
|
|
FPackageIdMap PackageIdMap;
|
|
|
|
#if OUTPUT_DEBUG_PACKAGE_HASHES
|
|
TMap<FName, FPackageHashes> PreviousBuildPackageHashes;
|
|
#endif
|
|
|
|
TArray<FContainerTargetSpec*> ContainerTargets;
|
|
UE_LOG(LogIoStore, Display, TEXT("Creating container targets..."));
|
|
{
|
|
IOSTORE_CPU_SCOPE(CreateContainerTargets);
|
|
|
|
if (!Arguments.MetaInputDir.IsEmpty())
|
|
{
|
|
#if OUTPUT_DEBUG_PACKAGE_HASHES
|
|
FString PackageHashesOutputPath = FPaths::Combine(*Arguments.MetaInputDir, TEXT("iodispatcher.upackagehashes"));
|
|
TUniquePtr<FArchive> PackageHashesArchive(IFileManager::Get().CreateFileReader(*PackageHashesOutputPath));
|
|
if (PackageHashesArchive)
|
|
{
|
|
int32 HashesCount;
|
|
(*PackageHashesArchive) << HashesCount;
|
|
for (int32 Index = 0; Index < HashesCount; ++Index)
|
|
{
|
|
FNameEntrySerialized NameEntrySerialized(ENAME_LinkerConstructor);
|
|
(*PackageHashesArchive) << NameEntrySerialized;
|
|
int32 NameNumber;
|
|
(*PackageHashesArchive) << NameNumber;
|
|
FName Name(NameEntrySerialized, NameNumber);
|
|
FPackageHashes& Hashes = PreviousBuildPackageHashes.Add(Name);
|
|
(*PackageHashesArchive) << Hashes.UAssetHash;
|
|
(*PackageHashesArchive) << Hashes.UExpHash;
|
|
(*PackageHashesArchive) << Hashes.ExportBundleHash;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
InitializeContainerTargetsAndPackages(Arguments, Packages, PackageNameMap, PackageIdMap, ContainerTargets, GlobalNameMapBuilder);
|
|
}
|
|
|
|
ParsePackageAssets(Packages, PackageAssetData);
|
|
|
|
FExportGraph ExportGraph(PackageAssetData.ObjectExports.Num(), PackageAssetData.PreloadDependencies.Num());
|
|
GlobalPackageData.Reserve(PackageAssetData.ObjectExports.Num());
|
|
|
|
CreateGlobalScriptObjects(GlobalPackageData, Arguments.TargetPlatform);
|
|
CreateGlobalImportsAndExports(Arguments, Packages, PackageIdMap, PackageAssetData, GlobalPackageData, ExportGraph);
|
|
|
|
// Mapped import and exports are required before processing localization, and preload/postload arcs
|
|
MapExportEntryIndices(PackageAssetData.ObjectExports, GlobalPackageData.ExportObjects, Packages);
|
|
// Intermediate map for adding extra ImportedPackages and ExternalArcs to the build graph
|
|
// for every dependency on a source package with localizations, add dependencies to all language localizations
|
|
FSourceToLocalizedPackageMultimap SourceToLocalizedPackageMap;
|
|
|
|
ProcessLocalizedPackages(Packages, PackageNameMap, GlobalPackageData, SourceToLocalizedPackageMap);
|
|
|
|
AddPreloadDependencies(
|
|
PackageAssetData,
|
|
GlobalPackageData,
|
|
SourceToLocalizedPackageMap,
|
|
ExportGraph,
|
|
Packages);
|
|
|
|
BuildBundles(ExportGraph, Packages);
|
|
|
|
#if OUTPUT_DEBUG_PACKAGE_EXPORT_BUNDLES
|
|
if (!Arguments.MetaOutputDir.IsEmpty())
|
|
{
|
|
TUniquePtr<IFileHandle> ExportBundleMetaFile(FPlatformFileManager::Get().GetPlatformFile().OpenWrite(*(Arguments.MetaOutputDir / TEXT("iodispatcher.uexportbundles"))));
|
|
for (const FPackage* Package : Packages)
|
|
{
|
|
int32 ExportBundleIndex = 0;
|
|
for (const FExportBundle& ExportBundle : Package->ExportBundles)
|
|
{
|
|
FString Text = FString::Printf(TEXT("%s[%d]\n"), *Package->Name.ToString(), ExportBundleIndex);
|
|
ExportBundleMetaFile->Write((uint8*)StringCast<ANSICHAR>(*Text).Get(), Text.Len());
|
|
for (const FExportGraphNode* Node : ExportBundle.Nodes)
|
|
{
|
|
Text = FString::Printf(TEXT("- %s %d\n"), Node->BundleEntry.CommandType == FExportBundleEntry::ExportCommandType_Serialize ? "S" : "C", Node->BundleEntry.LocalExportIndex);
|
|
ExportBundleMetaFile->Write((uint8*)StringCast<ANSICHAR>(*Text).Get(), Text.Len());
|
|
}
|
|
++ExportBundleIndex;
|
|
}
|
|
}
|
|
ExportBundleMetaFile->Flush();
|
|
}
|
|
#endif
|
|
|
|
{
|
|
IOSTORE_CPU_SCOPE(FinalizeNameMaps);
|
|
UE_LOG(LogIoStore, Display, TEXT("Finalizing name maps..."));
|
|
|
|
for (FContainerTargetSpec* ContainerTarget : ContainerTargets)
|
|
{
|
|
FinalizeNameMaps(*ContainerTarget);
|
|
}
|
|
}
|
|
|
|
{
|
|
IOSTORE_CPU_SCOPE(FinalizePackageHeaders);
|
|
UE_LOG(LogIoStore, Display, TEXT("Finalizing package headers..."));
|
|
|
|
for (FContainerTargetSpec* ContainerTarget : ContainerTargets)
|
|
{
|
|
FinalizePackageHeaders(
|
|
*ContainerTarget,
|
|
PackageAssetData.ObjectExports,
|
|
GlobalPackageData.ExportObjects,
|
|
GlobalPackageData.ImportsByFullName);
|
|
|
|
FinalizePackageStoreContainerHeader(*ContainerTarget);
|
|
|
|
const FNameMapBuilder& NameMapBuilder = *ContainerTarget->NameMapBuilder;
|
|
SaveNameBatch(ContainerTarget->LocalNameMapBuilder.GetNameMap(), ContainerTarget->Header.Names, ContainerTarget->Header.NameHashes);
|
|
}
|
|
}
|
|
|
|
UE_LOG(LogIoStore, Display, TEXT("Calculating hashes"));
|
|
{
|
|
IOSTORE_CPU_SCOPE(CalculateHashes);
|
|
|
|
int32 TotalFileCount = 0;
|
|
for (const FContainerTargetSpec* ContainerTarget : ContainerTargets)
|
|
{
|
|
TotalFileCount += ContainerTarget->TargetFiles.Num();
|
|
}
|
|
TArray<FContainerTargetFile*> AllTargetFiles;
|
|
AllTargetFiles.Reserve(TotalFileCount);
|
|
for (FContainerTargetSpec* ContainerTarget : ContainerTargets)
|
|
{
|
|
for (FContainerTargetFile& TargetFile : ContainerTarget->TargetFiles)
|
|
{
|
|
AllTargetFiles.Add(&TargetFile);
|
|
}
|
|
}
|
|
TArray<FObjectExport>& ObjectExports = PackageAssetData.ObjectExports;
|
|
TAtomic<uint64> CurrentFileIndex{ 0 };
|
|
ParallelFor(TotalFileCount, [&AllTargetFiles, &ObjectExports, &CurrentFileIndex](int32 Index)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(HashFile);
|
|
FContainerTargetFile* TargetFile = AllTargetFiles[Index];
|
|
IFileHandle* FileHandle = FPlatformFileManager::Get().GetPlatformFile().OpenRead(*TargetFile->NormalizedSourcePath);
|
|
check(FileHandle);
|
|
FIoBuffer IoBuffer(TargetFile->SourceSize);
|
|
bool bSuccess = FileHandle->Read(IoBuffer.Data(), TargetFile->SourceSize);
|
|
check(bSuccess);
|
|
delete FileHandle;
|
|
if (!TargetFile->bIsBulkData)
|
|
{
|
|
IoBuffer = CreateExportBundleBuffer(*TargetFile, ObjectExports, IoBuffer);
|
|
TargetFile->TargetSize = IoBuffer.DataSize();
|
|
}
|
|
else
|
|
{
|
|
TargetFile->TargetSize = TargetFile->SourceSize;
|
|
}
|
|
TargetFile->ChunkHash = FIoChunkHash::HashBuffer(IoBuffer.Data(), IoBuffer.DataSize());
|
|
#if OUTPUT_DEBUG_PACKAGE_HASHES
|
|
if (!TargetFile->bIsBulkData)
|
|
{
|
|
TargetFile->Package->Hashes.ExportBundleHash = TargetFile->ChunkHash;
|
|
}
|
|
#endif
|
|
uint64 LocalFileIndex = CurrentFileIndex.IncrementExchange() + 1;
|
|
UE_CLOG(LocalFileIndex % 1000 == 0, LogIoStore, Display, TEXT("Hashing %d/%d: '%s'"), LocalFileIndex, AllTargetFiles.Num(), *TargetFile->NormalizedSourcePath);
|
|
}, EParallelForFlags::Unbalanced);
|
|
|
|
#if OUTPUT_DEBUG_PACKAGE_HASHES
|
|
ParallelFor(Packages.Num(), [&Packages](int32 Index)
|
|
{
|
|
FPackage* Package = Packages[Index];
|
|
|
|
FString FileName = Package->FileName;
|
|
IFileHandle* FileHandle = FPlatformFileManager::Get().GetPlatformFile().OpenRead(*FileName);
|
|
check(FileHandle);
|
|
FIoBuffer IoBuffer(FileHandle->Size());
|
|
bool bSuccess = FileHandle->Read(IoBuffer.Data(), IoBuffer.DataSize());
|
|
check(bSuccess);
|
|
delete FileHandle;
|
|
FSHA1::HashBuffer(IoBuffer.Data(), IoBuffer.DataSize(), Package->Hashes.UAssetHash.Hash);
|
|
|
|
FileName = FPaths::ChangeExtension(FileName, TEXT(".uexp"));
|
|
FileHandle = FPlatformFileManager::Get().GetPlatformFile().OpenRead(*FileName);
|
|
check(FileHandle);
|
|
IoBuffer = FIoBuffer(FileHandle->Size());
|
|
bSuccess = FileHandle->Read(IoBuffer.Data(), IoBuffer.DataSize());
|
|
check(bSuccess);
|
|
delete FileHandle;
|
|
FSHA1::HashBuffer(IoBuffer.Data(), IoBuffer.DataSize(), Package->Hashes.UExpHash.Hash);
|
|
|
|
}, EParallelForFlags::Unbalanced);
|
|
#endif
|
|
}
|
|
|
|
UE_LOG(LogIoStore, Display, TEXT("Creating disk layout..."));
|
|
CreateDiskLayout(ContainerTargets, Packages, Arguments.GameOrderMap, Arguments.CookerOrderMap);
|
|
|
|
UE_LOG(LogIoStore, Display, TEXT("Serializing container(s)..."));
|
|
TUniquePtr<FIoStoreWriterContext> IoStoreWriterContext(new FIoStoreWriterContext());
|
|
TArray<FIoStoreWriter*> IoStoreWriters;
|
|
FIoStoreEnvironment GlobalIoStoreEnv;
|
|
FIoStoreWriter* GlobalIoStoreWriter = nullptr;
|
|
{
|
|
IOSTORE_CPU_SCOPE(InitializeIoStoreWriters);
|
|
if (!Arguments.IsDLC())
|
|
{
|
|
GlobalIoStoreEnv.InitializeFileEnvironment(*Arguments.GlobalContainerPath);
|
|
GlobalIoStoreWriter = new FIoStoreWriter(GlobalIoStoreEnv);
|
|
IoStoreWriters.Add(GlobalIoStoreWriter);
|
|
}
|
|
for (FContainerTargetSpec* ContainerTarget : ContainerTargets)
|
|
{
|
|
check(ContainerTarget->Header.ContainerId.IsValid());
|
|
if (!ContainerTarget->OutputPath.IsEmpty())
|
|
{
|
|
ContainerTarget->IoStoreEnv.Reset(new FIoStoreEnvironment());
|
|
ContainerTarget->IoStoreEnv->InitializeFileEnvironment(ContainerTarget->OutputPath);
|
|
ContainerTarget->IoStoreWriter = new FIoStoreWriter(*ContainerTarget->IoStoreEnv);
|
|
IoStoreWriters.Add(ContainerTarget->IoStoreWriter);
|
|
}
|
|
}
|
|
FIoStatus IoStatus = IoStoreWriterContext->Initialize(GeneralIoWriterSettings);
|
|
check(IoStatus.IsOk());
|
|
|
|
FIoContainerSettings GlobalContainerSettings;
|
|
if (Arguments.bSign)
|
|
{
|
|
GlobalContainerSettings.SigningKey = Arguments.KeyChain.SigningKey;
|
|
GlobalContainerSettings.ContainerFlags |= EIoContainerFlags::Signed;
|
|
}
|
|
if (GlobalIoStoreWriter)
|
|
{
|
|
IoStatus = GlobalIoStoreWriter->Initialize(*IoStoreWriterContext, GlobalContainerSettings);
|
|
}
|
|
check(IoStatus.IsOk());
|
|
for (FContainerTargetSpec* ContainerTarget : ContainerTargets)
|
|
{
|
|
if (ContainerTarget->IoStoreWriter && ContainerTarget->IoStoreWriter != GlobalIoStoreWriter)
|
|
{
|
|
FIoContainerSettings ContainerSettings;
|
|
ContainerSettings.ContainerId = ContainerTarget->Header.ContainerId;
|
|
ContainerSettings.ContainerFlags = ContainerTarget->ContainerFlags;
|
|
if (EnumHasAnyFlags(ContainerTarget->ContainerFlags, EIoContainerFlags::Encrypted))
|
|
{
|
|
const FNamedAESKey* Key = Arguments.KeyChain.EncryptionKeys.Find(ContainerTarget->EncryptionKeyGuid);
|
|
check(Key);
|
|
ContainerSettings.EncryptionKeyGuid = ContainerTarget->EncryptionKeyGuid;
|
|
ContainerSettings.EncryptionKey = Key->Key;
|
|
}
|
|
if (EnumHasAnyFlags(ContainerTarget->ContainerFlags, EIoContainerFlags::Signed))
|
|
{
|
|
ContainerSettings.SigningKey = Arguments.KeyChain.SigningKey;
|
|
ContainerSettings.ContainerFlags |= EIoContainerFlags::Signed;
|
|
}
|
|
IoStatus = ContainerTarget->IoStoreWriter->Initialize(*IoStoreWriterContext, ContainerSettings);
|
|
check(IoStatus.IsOk());
|
|
}
|
|
}
|
|
}
|
|
{
|
|
IOSTORE_CPU_SCOPE(SerializeContainers);
|
|
|
|
struct FReadFileTask
|
|
{
|
|
const FContainerTargetFile* ContainerTargetFile = nullptr;
|
|
FIoStoreWriter* IoStoreWriter = nullptr;
|
|
IAsyncReadFileHandle* FileHandle = nullptr;
|
|
IAsyncReadRequest* ReadRequest = nullptr;
|
|
FIoBuffer IoBuffer;
|
|
TAtomic<bool> bStarted{ false };
|
|
};
|
|
|
|
int32 TotalFileCount = 0;
|
|
for (FContainerTargetSpec* ContainerTarget : ContainerTargets)
|
|
{
|
|
check(ContainerTarget->IoStoreWriter || ContainerTarget->TargetFiles.Num() == 0);
|
|
TotalFileCount += ContainerTarget->TargetFiles.Num();
|
|
}
|
|
|
|
TArray<uint8> ContainerHeaderPadding;
|
|
if (GeneralIoWriterSettings.CompressionBlockSize > 0)
|
|
{
|
|
ContainerHeaderPadding.AddZeroed(GeneralIoWriterSettings.CompressionBlockSize);
|
|
}
|
|
|
|
TArray<FReadFileTask> ReadFileTasks;
|
|
ReadFileTasks.SetNum(TotalFileCount);
|
|
int32 ReadFileTaskIndex = 0;
|
|
for (FContainerTargetSpec* ContainerTarget : ContainerTargets)
|
|
{
|
|
if (!ContainerTarget->IoStoreWriter)
|
|
{
|
|
continue;
|
|
}
|
|
for (FContainerTargetFile& TargetFile : ContainerTarget->TargetFiles)
|
|
{
|
|
FReadFileTask& ReadFileTask = ReadFileTasks[ReadFileTaskIndex++];
|
|
ReadFileTask.ContainerTargetFile = &TargetFile;
|
|
ReadFileTask.IoStoreWriter = ContainerTarget->IoStoreWriter;
|
|
}
|
|
|
|
FLargeMemoryWriter Ar(0, true);
|
|
Ar << ContainerTarget->Header;
|
|
|
|
if (GeneralIoWriterSettings.CompressionBlockSize > 0)
|
|
{
|
|
const uint64 RemainingInBlock = Align(Ar.TotalSize(), GeneralIoWriterSettings.CompressionBlockSize) - Ar.TotalSize();
|
|
if (RemainingInBlock > 0)
|
|
{
|
|
Ar.Serialize(ContainerHeaderPadding.GetData(), RemainingInBlock);
|
|
}
|
|
}
|
|
|
|
FIoWriteOptions WriteOptions;
|
|
WriteOptions.DebugName = TEXT("ContainerHeader");
|
|
FIoStatus Status = ContainerTarget->IoStoreWriter->Append(
|
|
CreateIoChunkId(ContainerTarget->Header.ContainerId.Value(), 0, EIoChunkType::ContainerHeader),
|
|
FIoBuffer(FIoBuffer::Wrap, Ar.GetData(), Ar.TotalSize()), WriteOptions);
|
|
|
|
UE_CLOG(!Status.IsOk(), LogIoStore, Error, TEXT("Failed to serialize container header"));
|
|
}
|
|
|
|
FEvent* TaskStartedEvent = FPlatformProcess::GetSynchEventFromPool(false);
|
|
FEvent* TaskFinishedEvent = FPlatformProcess::GetSynchEventFromPool(false);
|
|
|
|
TAtomic<uint64> BufferMemoryAllocated{ 0 };
|
|
TAtomic<uint64> OpenFileHandlesCount{ 0 };
|
|
|
|
TArray<FObjectExport>& ObjectExports = PackageAssetData.ObjectExports;
|
|
TFuture<void> ReaderTask = Async(EAsyncExecution::ThreadPool, [&ReadFileTasks, &BufferMemoryAllocated, &OpenFileHandlesCount, TaskStartedEvent, TaskFinishedEvent, &BulkDataManifest, &ObjectExports, &GeneralIoWriterSettings]()
|
|
{
|
|
IOSTORE_CPU_SCOPE(ReadContainerSourceFilesTask);
|
|
for (FReadFileTask& ReadFileTask : ReadFileTasks)
|
|
{
|
|
while (!ReadFileTask.bStarted)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(WaitUntilStarted);
|
|
TaskStartedEvent->Wait();
|
|
}
|
|
ReadFileTask.ReadRequest->WaitCompletion();
|
|
delete ReadFileTask.ReadRequest;
|
|
delete ReadFileTask.FileHandle;
|
|
|
|
--OpenFileHandlesCount;
|
|
|
|
const FContainerTargetFile& TargetFile = *ReadFileTask.ContainerTargetFile;
|
|
|
|
uint64 BufferSize = ReadFileTask.IoBuffer.DataSize();
|
|
if (!TargetFile.bIsBulkData)
|
|
{
|
|
ReadFileTask.IoBuffer = CreateExportBundleBuffer(TargetFile, ObjectExports, ReadFileTask.IoBuffer);
|
|
}
|
|
|
|
FIoWriteOptions WriteOptions;
|
|
WriteOptions.DebugName = *TargetFile.TargetPath;
|
|
WriteOptions.bForceUncompressed = TargetFile.bForceUncompressed;
|
|
WriteOptions.bIsMemoryMapped = TargetFile.bIsMemoryMappedBulkData;
|
|
FIoStatus Status = ReadFileTask.IoStoreWriter->Append(TargetFile.ChunkId, TargetFile.ChunkHash, ReadFileTask.IoBuffer, WriteOptions);
|
|
UE_CLOG(!Status.IsOk(), LogIoStore, Fatal, TEXT("Failed to append chunk to container file due to '%s'"), *Status.ToString());
|
|
|
|
ReadFileTask.IoBuffer = FIoBuffer();
|
|
BufferMemoryAllocated -= BufferSize;
|
|
TaskFinishedEvent->Trigger();
|
|
}
|
|
});
|
|
|
|
uint64 CurrentFileIndex = 0;
|
|
const uint64 MaxReadFileBufferSize = 2ull << 30;
|
|
const uint64 MaxReadFileHandlesCount = 65536;
|
|
for (FReadFileTask& ReadFileTask : ReadFileTasks)
|
|
{
|
|
while (BufferMemoryAllocated > MaxReadFileBufferSize)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(WaitForBufferMemory);
|
|
TaskFinishedEvent->Wait();
|
|
}
|
|
while (OpenFileHandlesCount > MaxReadFileHandlesCount)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(WaitForFileHandle);
|
|
TaskFinishedEvent->Wait();
|
|
}
|
|
BufferMemoryAllocated += ReadFileTask.ContainerTargetFile->SourceSize;
|
|
ReadFileTask.IoBuffer = FIoBuffer(ReadFileTask.ContainerTargetFile->SourceSize);
|
|
++OpenFileHandlesCount;
|
|
ReadFileTask.FileHandle = FPlatformFileManager::Get().GetPlatformFile().OpenAsyncRead(*ReadFileTask.ContainerTargetFile->NormalizedSourcePath);
|
|
ReadFileTask.ReadRequest = ReadFileTask.FileHandle->ReadRequest(0, ReadFileTask.ContainerTargetFile->SourceSize, AIOP_Normal, nullptr, ReadFileTask.IoBuffer.Data());
|
|
ReadFileTask.bStarted = true;
|
|
TaskStartedEvent->Trigger();
|
|
|
|
++CurrentFileIndex;
|
|
UE_CLOG(CurrentFileIndex % 1000 == 0, LogIoStore, Display, TEXT("Serializing %d/%d: '%s'"), CurrentFileIndex, ReadFileTasks.Num(), *ReadFileTask.ContainerTargetFile->NormalizedSourcePath);
|
|
}
|
|
{
|
|
IOSTORE_CPU_SCOPE(CompleteReads);
|
|
ReaderTask.Wait();
|
|
}
|
|
FPlatformProcess::ReturnSynchEventToPool(TaskFinishedEvent);
|
|
FPlatformProcess::ReturnSynchEventToPool(TaskStartedEvent);
|
|
}
|
|
|
|
uint64 InitialLoadSize = 0;
|
|
if (GlobalIoStoreWriter)
|
|
{
|
|
FLargeMemoryWriter InitialLoadArchive(0, true);
|
|
FinalizeInitialLoadMeta(
|
|
GlobalNameMapBuilder,
|
|
GlobalPackageData.ScriptObjects,
|
|
InitialLoadArchive);
|
|
|
|
InitialLoadSize = InitialLoadArchive.Tell();
|
|
|
|
UE_LOG(LogIoStore, Display, TEXT("Serializing global meta data"));
|
|
IOSTORE_CPU_SCOPE(SerializeInitialLoadMeta);
|
|
FIoWriteOptions WriteOptions;
|
|
WriteOptions.DebugName = TEXT("LoaderInitialLoadMeta");
|
|
const FIoStatus Status = GlobalIoStoreWriter->Append(CreateIoChunkId(0, 0, EIoChunkType::LoaderInitialLoadMeta), FIoBuffer(FIoBuffer::Wrap, InitialLoadArchive.GetData(), InitialLoadArchive.TotalSize()), WriteOptions);
|
|
if (!Status.IsOk())
|
|
{
|
|
UE_LOG(LogIoStore, Error, TEXT("Failed to save initial load meta data to container file"));
|
|
}
|
|
}
|
|
|
|
uint64 GlobalNamesMB = 0;
|
|
uint64 GlobalNameHashesMB = 0;
|
|
if (GlobalIoStoreWriter)
|
|
{
|
|
IOSTORE_CPU_SCOPE(SerializeGlobalNameMap);
|
|
|
|
UE_LOG(LogIoStore, Display, TEXT("Saving global name map to container file"));
|
|
|
|
TArray<uint8> Names;
|
|
TArray<uint8> Hashes;
|
|
SaveNameBatch(GlobalNameMapBuilder.GetNameMap(), /* out */ Names, /* out */ Hashes);
|
|
|
|
InitialLoadSize += Names.Num() + Hashes.Num();
|
|
GlobalNamesMB = Names.Num() >> 20;
|
|
GlobalNameHashesMB = Hashes.Num() >> 20;
|
|
|
|
FIoWriteOptions WriteOptions;
|
|
WriteOptions.DebugName = TEXT("LoaderGlobalNames");
|
|
FIoStatus NameStatus = GlobalIoStoreWriter->Append(CreateIoChunkId(0, 0, EIoChunkType::LoaderGlobalNames),
|
|
FIoBuffer(FIoBuffer::Wrap, Names.GetData(), Names.Num()), WriteOptions);
|
|
WriteOptions.DebugName = TEXT("LoaderGlobalNameHashes");
|
|
FIoStatus HashStatus = GlobalIoStoreWriter->Append(CreateIoChunkId(0, 0, EIoChunkType::LoaderGlobalNameHashes),
|
|
FIoBuffer(FIoBuffer::Wrap, Hashes.GetData(), Hashes.Num()), WriteOptions);
|
|
|
|
if (!NameStatus.IsOk() || !HashStatus.IsOk())
|
|
{
|
|
UE_LOG(LogIoStore, Error, TEXT("Failed to save global name map to container file"));
|
|
}
|
|
|
|
#if OUTPUT_NAMEMAP_CSV
|
|
NameMapBuilder.SaveCsv(OutputDir / TEXT("Container.namemap.csv"));
|
|
#endif
|
|
}
|
|
|
|
TArray<FIoStoreWriterResult> IoStoreWriterResults;
|
|
IoStoreWriterResults.Reserve(IoStoreWriters.Num());
|
|
for (FIoStoreWriter* IoStoreWriter : IoStoreWriters)
|
|
{
|
|
IoStoreWriterResults.Emplace(IoStoreWriter->Flush().ConsumeValueOrDie());
|
|
delete IoStoreWriter;
|
|
}
|
|
IoStoreWriters.Empty();
|
|
|
|
if (!Arguments.MetaOutputDir.IsEmpty())
|
|
{
|
|
#if OUTPUT_DEBUG_PACKAGE_HASHES
|
|
FString PackageHashesOutputPath = FPaths::Combine(*Arguments.MetaOutputDir, TEXT("iodispatcher.upackagehashes"));
|
|
TUniquePtr<FArchive> PackageHashesArchive(IFileManager::Get().CreateFileWriter(*PackageHashesOutputPath));
|
|
|
|
int32 PackageCount = Packages.Num();
|
|
(*PackageHashesArchive) << PackageCount;
|
|
for (FPackage* Package : Packages)
|
|
{
|
|
const FNameEntry* NameEntry = FName::GetEntry(Package->Name.GetComparisonIndex());
|
|
NameEntry->Write(*PackageHashesArchive);
|
|
int32 NameNumber = Package->Name.GetNumber();
|
|
(*PackageHashesArchive) << NameNumber;
|
|
(*PackageHashesArchive) << Package->Hashes.UAssetHash;
|
|
(*PackageHashesArchive) << Package->Hashes.UExpHash;
|
|
(*PackageHashesArchive) << Package->Hashes.ExportBundleHash;
|
|
}
|
|
#endif
|
|
}
|
|
#if OUTPUT_DEBUG_PACKAGE_HASHES
|
|
{
|
|
uint64 AddedCount = 0;
|
|
uint64 AddedUAssetSize = 0;
|
|
uint64 AddedUExpSize = 0;
|
|
uint64 AddedExportBundlesSize = 0;
|
|
uint64 DeletedCount = 0;
|
|
uint64 ModifiedUAssetOrUExpCount = 0;
|
|
uint64 ModifiedUAssetCount = 0;
|
|
uint64 ModifiedUAssetSize = 0;
|
|
uint64 ModifiedUExpCount = 0;
|
|
uint64 ModifiedUExpSize = 0;
|
|
uint64 ModifiedExportBundlesCount = 0;
|
|
uint64 ModifiedExportBundlesSize = 0;
|
|
TMap<const FPackage*, uint64> PackageToExportBundleSizeMap;
|
|
for (const FContainerTargetSpec* ContainerTarget : ContainerTargets)
|
|
{
|
|
for (const FContainerTargetFile& TargetFile : ContainerTarget->TargetFiles)
|
|
{
|
|
if (TargetFile.bIsBulkData)
|
|
{
|
|
continue;
|
|
}
|
|
PackageToExportBundleSizeMap.Add(TargetFile.Package, TargetFile.TargetSize);
|
|
}
|
|
}
|
|
for (const FPackage* Package : Packages)
|
|
{
|
|
uint64 ExportBundleSize = PackageToExportBundleSizeMap.FindRef(Package);
|
|
check(ExportBundleSize);
|
|
const FPackageHashes* PreviousHashes = PreviousBuildPackageHashes.Find(Package->Name);
|
|
if (PreviousHashes)
|
|
{
|
|
bool bModified = false;
|
|
if (PreviousHashes->UAssetHash != Package->Hashes.UAssetHash)
|
|
{
|
|
++ModifiedUAssetCount;
|
|
bModified = true;
|
|
|
|
ModifiedUAssetSize += Package->UAssetSize;
|
|
}
|
|
if (PreviousHashes->UExpHash != Package->Hashes.UExpHash)
|
|
{
|
|
++ModifiedUExpCount;
|
|
bModified = true;
|
|
|
|
ModifiedUExpSize += Package->UExpSize;
|
|
}
|
|
if (PreviousHashes->ExportBundleHash != Package->Hashes.ExportBundleHash)
|
|
{
|
|
++ModifiedExportBundlesCount;
|
|
ModifiedExportBundlesSize += ExportBundleSize;
|
|
UE_CLOG(!bModified, LogIoStore, Warning, TEXT("Modified export bundle without modified package data: %s"), *Package->Name.ToString());
|
|
}
|
|
if (bModified)
|
|
{
|
|
++ModifiedUAssetOrUExpCount;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
++AddedCount;
|
|
AddedUAssetSize += Package->UAssetSize;
|
|
AddedUExpSize += Package->UExpSize;
|
|
AddedExportBundlesSize += ExportBundleSize;
|
|
}
|
|
}
|
|
for (const auto& KV : PreviousBuildPackageHashes)
|
|
{
|
|
if (!PackageNameMap.Contains(KV.Key))
|
|
{
|
|
++DeletedCount;
|
|
}
|
|
}
|
|
UE_LOG(LogIoStore, Display, TEXT("Modified packages (export bundle): %d, %fMB"), ModifiedExportBundlesCount, ModifiedExportBundlesSize / 1024.0 / 1024.0);
|
|
UE_LOG(LogIoStore, Display, TEXT("Modified packages (uasset|uexp): %d, %fMB"), ModifiedUAssetOrUExpCount, (ModifiedUAssetSize + ModifiedUExpSize) / 1024.0 / 1024.0);
|
|
UE_LOG(LogIoStore, Display, TEXT("Modified packages (uasset): %d, %fMB"), ModifiedUAssetCount, ModifiedUAssetSize / 1024.0 / 1024.0);
|
|
UE_LOG(LogIoStore, Display, TEXT("Modified packages (uexp): %d, %fMB"), ModifiedUExpCount, ModifiedUExpSize / 1024.0 / 1024.0);
|
|
UE_LOG(LogIoStore, Display, TEXT("Added packages: %d"), AddedCount);
|
|
UE_LOG(LogIoStore, Display, TEXT("Added packages (export bundle): %fMB"), AddedExportBundlesSize / 1024.0 / 1024.0);
|
|
UE_LOG(LogIoStore, Display, TEXT("Added packages (uasset|uexp): %fMB"), (AddedUAssetSize + AddedUExpSize) / 1024.0 / 1024.0);
|
|
UE_LOG(LogIoStore, Display, TEXT("Added packages (uasset): %fMB"), AddedUAssetSize / 1024.0 / 1024.0);
|
|
UE_LOG(LogIoStore, Display, TEXT("Added packages (uexp): %fMB"), AddedUExpSize / 1024.0 / 1024.0);
|
|
UE_LOG(LogIoStore, Display, TEXT("Deleted packages: %d"), DeletedCount);
|
|
}
|
|
#endif
|
|
|
|
IOSTORE_CPU_SCOPE(CalculateStats);
|
|
|
|
UE_LOG(LogIoStore, Display, TEXT("Calculating stats..."));
|
|
uint64 UExpSize = 0;
|
|
uint64 UAssetSize = 0;
|
|
uint64 SummarySize = 0;
|
|
uint64 PackageSummarySize = Packages.Num() * sizeof(FPackageSummary);
|
|
uint64 ImportedPackagesCount = 0;
|
|
uint64 NoImportedPackagesCount = 0;
|
|
uint64 PublicExportsCount = 0;
|
|
uint64 TotalExternalArcCount = 0;
|
|
uint64 NameMapCount = 0;
|
|
|
|
uint64 BundleCount = 0;
|
|
uint64 BundleEntryCount = 0;
|
|
|
|
for (const FPackage* Package : Packages)
|
|
{
|
|
UExpSize += Package->UExpSize;
|
|
UAssetSize += Package->UAssetSize;
|
|
SummarySize += Package->SummarySize;
|
|
NameMapCount += Package->SummaryNames.Num();
|
|
ImportedPackagesCount += Package->ImportedPackages.Num();
|
|
NoImportedPackagesCount += Package->ImportedPackages.Num() == 0;
|
|
|
|
for (auto& KV : Package->ExternalArcs)
|
|
{
|
|
const TArray<FArc>& Arcs = KV.Value;
|
|
TotalExternalArcCount += Arcs.Num();
|
|
}
|
|
|
|
for (const FExportBundle& Bundle : Package->ExportBundles)
|
|
{
|
|
++BundleCount;
|
|
BundleEntryCount += Bundle.Nodes.Num();
|
|
}
|
|
}
|
|
|
|
for (FExportObjectData& Export : GlobalPackageData.ExportObjects)
|
|
{
|
|
if (Export.IsPublicExport())
|
|
{
|
|
++PublicExportsCount;
|
|
}
|
|
}
|
|
|
|
LogWriterResults(IoStoreWriterResults);
|
|
LogContainerPackageInfo(ContainerTargets);
|
|
|
|
UE_LOG(LogIoStore, Display, TEXT("Input: %8.2lf MB UExp"), (double)UExpSize / 1024.0 / 1024.0);
|
|
UE_LOG(LogIoStore, Display, TEXT("Input: %8.2lf MB UAsset"), (double)UAssetSize / 1024.0 / 1024.0);
|
|
UE_LOG(LogIoStore, Display, TEXT("Input: %8.2lf MB FPackageFileSummary"), (double)SummarySize / 1024.0 / 1024.0);
|
|
UE_LOG(LogIoStore, Display, TEXT("Input: %8d Packages"), Packages.Num());
|
|
UE_LOG(LogIoStore, Display, TEXT("Input: %8llu Imported package entries"), ImportedPackagesCount);
|
|
UE_LOG(LogIoStore, Display, TEXT("Input: %8llu Packages without imports"), NoImportedPackagesCount);
|
|
UE_LOG(LogIoStore, Display, TEXT("Input: %8llu Name map entries"), NameMapCount);
|
|
UE_LOG(LogIoStore, Display, TEXT("Input: %8d PreloadDependencies entries"), PackageAssetData.PreloadDependencies.Num());
|
|
UE_LOG(LogIoStore, Display, TEXT("Input: %8d ImportMap entries"), PackageAssetData.ObjectImports.Num());
|
|
UE_LOG(LogIoStore, Display, TEXT("Input: %8d ExportMap entries"), PackageAssetData.ObjectExports.Num());
|
|
UE_LOG(LogIoStore, Display, TEXT("Input: %8llu Public exports"), PublicExportsCount);
|
|
UE_LOG(LogIoStore, Display, TEXT(""));
|
|
UE_LOG(LogIoStore, Display, TEXT("Output: %8llu Export bundles"), BundleCount);
|
|
UE_LOG(LogIoStore, Display, TEXT("Output: %8llu Export bundle entries"), BundleEntryCount);
|
|
UE_LOG(LogIoStore, Display, TEXT("Output: %8llu Export bundle arcs"), TotalExternalArcCount);
|
|
UE_LOG(LogIoStore, Display, TEXT("Output: %8d Public runtime script objects"), GlobalPackageData.ScriptObjects.Num());
|
|
UE_LOG(LogIoStore, Display, TEXT("Output: %8.2lf MB InitialLoadData"), (double)InitialLoadSize / 1024.0 / 1024.0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int32 CreateContentPatch(const FIoStoreArguments& Arguments, const FIoStoreWriterSettings& GeneralIoWriterSettings)
|
|
{
|
|
UE_LOG(LogIoStore, Display, TEXT("Serializing container(s)..."));
|
|
TUniquePtr<FIoStoreWriterContext> IoStoreWriterContext(new FIoStoreWriterContext());
|
|
FIoStatus IoStatus = IoStoreWriterContext->Initialize(GeneralIoWriterSettings);
|
|
check(IoStatus.IsOk());
|
|
TArray<FIoStoreWriterResult> Results;
|
|
for (const FContainerSourceSpec& Container : Arguments.Containers)
|
|
{
|
|
TArray<TUniquePtr<FIoStoreReader>> SourceReaders = CreatePatchSourceReaders(Container.PatchSourceContainerFiles, Arguments);
|
|
TUniquePtr<FIoStoreReader> TargetReader = CreateIoStoreReader(*Container.PatchTargetFile, Arguments.KeyChain);
|
|
if (!TargetReader.IsValid())
|
|
{
|
|
UE_LOG(LogIoStore, Error, TEXT("Failed loading target container"));
|
|
return -1;
|
|
}
|
|
|
|
FIoStoreEnvironment IoStoreEnv;
|
|
FIoStoreWriter IoStoreWriter(IoStoreEnv);
|
|
IoStoreEnv.InitializeFileEnvironment(*Container.OutputPath);
|
|
|
|
EIoContainerFlags TargetContainerFlags = TargetReader->GetContainerFlags();
|
|
|
|
FIoContainerSettings ContainerSettings;
|
|
ContainerSettings.ContainerId = TargetReader->GetContainerId();
|
|
if (Arguments.bSign || EnumHasAnyFlags(TargetContainerFlags, EIoContainerFlags::Signed))
|
|
{
|
|
ContainerSettings.SigningKey = Arguments.KeyChain.SigningKey;
|
|
ContainerSettings.ContainerFlags |= EIoContainerFlags::Signed;
|
|
}
|
|
|
|
if (EnumHasAnyFlags(TargetContainerFlags, EIoContainerFlags::Encrypted))
|
|
{
|
|
ContainerSettings.ContainerFlags |= EIoContainerFlags::Encrypted;
|
|
const FNamedAESKey* Key = Arguments.KeyChain.EncryptionKeys.Find(TargetReader->GetEncryptionKeyGuid());
|
|
if (!Key)
|
|
{
|
|
UE_LOG(LogIoStore, Error, TEXT("Missing encryption key for target container"));
|
|
return -1;
|
|
}
|
|
ContainerSettings.EncryptionKeyGuid = Key->Guid;
|
|
ContainerSettings.EncryptionKey = Key->Key;
|
|
}
|
|
|
|
IoStatus = IoStoreWriter.Initialize(*IoStoreWriterContext, ContainerSettings);
|
|
check(IoStatus.IsOk());
|
|
|
|
TMap<FIoChunkId, FIoChunkHash> SourceHashByChunkId;
|
|
for (const TUniquePtr<FIoStoreReader>& SourceReader : SourceReaders)
|
|
{
|
|
SourceReader->EnumerateChunks([&SourceHashByChunkId](const FIoStoreTocChunkInfo& ChunkInfo)
|
|
{
|
|
SourceHashByChunkId.Add(ChunkInfo.Id, ChunkInfo.Hash);
|
|
return true;
|
|
});
|
|
}
|
|
TargetReader->EnumerateChunks([&TargetReader, &SourceHashByChunkId, &IoStoreWriter](const FIoStoreTocChunkInfo& ChunkInfo)
|
|
{
|
|
FIoChunkHash* FindSourceHash = SourceHashByChunkId.Find(ChunkInfo.Id);
|
|
if (!FindSourceHash || *FindSourceHash != ChunkInfo.Hash)
|
|
{
|
|
FIoReadOptions ReadOptions;
|
|
TIoStatusOr<FIoBuffer> ChunkBuffer = TargetReader->Read(ChunkInfo.Id, ReadOptions);
|
|
FIoWriteOptions WriteOptions;
|
|
WriteOptions.bIsMemoryMapped = ChunkInfo.bIsMemoryMapped;
|
|
WriteOptions.bForceUncompressed = ChunkInfo.bForceUncompressed;
|
|
FIoStatus Status = IoStoreWriter.Append(ChunkInfo.Id, ChunkInfo.Hash, ChunkBuffer.ConsumeValueOrDie(), WriteOptions);
|
|
UE_CLOG(!Status.IsOk(), LogIoStore, Fatal, TEXT("Failed to append chunk to container file due to '%s'"), *Status.ToString());
|
|
}
|
|
return true;
|
|
});
|
|
|
|
|
|
Results.Emplace(IoStoreWriter.Flush().ConsumeValueOrDie());
|
|
}
|
|
|
|
LogWriterResults(Results);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool ParsePakResponseFile(const TCHAR* FilePath, TArray<FContainerSourceFile>& OutFiles)
|
|
{
|
|
TArray<FString> ResponseFileContents;
|
|
if (!FFileHelper::LoadFileToStringArray(ResponseFileContents, FilePath))
|
|
{
|
|
UE_LOG(LogIoStore, Error, TEXT("Failed to read response file '%s'."), *FilePath);
|
|
return false;
|
|
}
|
|
|
|
for (const FString& ResponseLine : ResponseFileContents)
|
|
{
|
|
TArray<FString> SourceAndDest;
|
|
TArray<FString> Switches;
|
|
|
|
FString NextToken;
|
|
const TCHAR* ResponseLinePtr = *ResponseLine;
|
|
while (FParse::Token(ResponseLinePtr, NextToken, false))
|
|
{
|
|
if ((**NextToken == TCHAR('-')))
|
|
{
|
|
new(Switches) FString(NextToken.Mid(1));
|
|
}
|
|
else
|
|
{
|
|
new(SourceAndDest) FString(NextToken);
|
|
}
|
|
}
|
|
|
|
if (SourceAndDest.Num() == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (SourceAndDest.Num() != 2)
|
|
{
|
|
UE_LOG(LogIoStore, Error, TEXT("Invalid line in response file '%s'."), *ResponseLine);
|
|
return false;
|
|
}
|
|
|
|
FPaths::NormalizeFilename(SourceAndDest[0]);
|
|
|
|
FContainerSourceFile& FileEntry = OutFiles.AddDefaulted_GetRef();
|
|
FileEntry.NormalizedPath = MoveTemp(SourceAndDest[0]);
|
|
|
|
for (int32 Index = 0; Index < Switches.Num(); ++Index)
|
|
{
|
|
if (Switches[Index] == TEXT("compress"))
|
|
{
|
|
FileEntry.bNeedsCompression = true;
|
|
}
|
|
if (Switches[Index] == TEXT("encrypt"))
|
|
{
|
|
FileEntry.bNeedsEncryption = true;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool ParsePakOrderFile(const TCHAR* FilePath, TMap<FName, uint64>& OutMap)
|
|
{
|
|
TArray<FString> OrderFileContents;
|
|
if (!FFileHelper::LoadFileToStringArray(OrderFileContents, FilePath))
|
|
{
|
|
UE_LOG(LogIoStore, Error, TEXT("Failed to read order file '%s'."), *FilePath);
|
|
return false;
|
|
}
|
|
|
|
uint64 LineNumber = 1;
|
|
for (const FString& OrderLine : OrderFileContents)
|
|
{
|
|
const TCHAR* OrderLinePtr = *OrderLine;
|
|
FString Path;
|
|
if (!FParse::Token(OrderLinePtr, Path, false))
|
|
{
|
|
UE_LOG(LogIoStore, Error, TEXT("Invalid line in order file '%s'."), *OrderLine);
|
|
return false;
|
|
}
|
|
FString PackageName;
|
|
if (!FPackageName::TryConvertFilenameToLongPackageName(Path, PackageName, nullptr))
|
|
{
|
|
continue;;
|
|
}
|
|
|
|
uint64 Order = LineNumber;
|
|
|
|
FName PackageFName(MoveTemp(PackageName));
|
|
if (!OutMap.Contains(PackageFName))
|
|
{
|
|
OutMap.Emplace(PackageFName, Order);
|
|
}
|
|
|
|
++LineNumber;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
class FCookedFileVisitor : public IPlatformFile::FDirectoryStatVisitor
|
|
{
|
|
FCookedFileStatMap& CookedFileStatMap;
|
|
FContainerSourceSpec* ContainerSpec = nullptr;
|
|
|
|
public:
|
|
FCookedFileVisitor(FCookedFileStatMap& InCookedFileSizes, FContainerSourceSpec* InContainerSpec)
|
|
: CookedFileStatMap(InCookedFileSizes)
|
|
, ContainerSpec(InContainerSpec)
|
|
{}
|
|
|
|
FCookedFileVisitor(FCookedFileStatMap& InFileSizes)
|
|
: CookedFileStatMap(InFileSizes)
|
|
{}
|
|
|
|
virtual bool Visit(const TCHAR* FilenameOrDirectory, const FFileStatData& StatData)
|
|
{
|
|
// Should match FCookedFileStatData::EFileExt
|
|
static const TCHAR* Extensions[] = { TEXT("umap"), TEXT("uasset"), TEXT("uexp"), TEXT("ubulk"), TEXT("uptnl"), TEXT("m.ubulk") };
|
|
static const int32 NumPackageExtensions = 2;
|
|
static const int32 UExpExtensionIndex = 2;
|
|
|
|
if (StatData.bIsDirectory)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
const TCHAR* Extension = FCString::Strrchr(FilenameOrDirectory, '.');
|
|
if (!Extension || *(++Extension) == TEXT('\0'))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
int32 ExtIndex = 0;
|
|
if (0 == FCString::Stricmp(Extension, Extensions[3]))
|
|
{
|
|
ExtIndex = 3;
|
|
if (0 == FCString::Stricmp(Extension - 3, TEXT(".m.ubulk")))
|
|
{
|
|
ExtIndex = 5;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (ExtIndex = 0; ExtIndex < UE_ARRAY_COUNT(Extensions); ++ExtIndex)
|
|
{
|
|
if (0 == FCString::Stricmp(Extension, Extensions[ExtIndex]))
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ExtIndex >= UE_ARRAY_COUNT(Extensions))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
FString Path = FilenameOrDirectory;
|
|
FPaths::NormalizeFilename(Path);
|
|
|
|
if (ContainerSpec && ExtIndex != UExpExtensionIndex)
|
|
{
|
|
FContainerSourceFile& FileEntry = ContainerSpec->SourceFiles.AddDefaulted_GetRef();
|
|
FileEntry.NormalizedPath = Path;
|
|
}
|
|
|
|
FCookedFileStatData& CookedFileStatData = CookedFileStatMap.Add(MoveTemp(Path));
|
|
CookedFileStatData.FileSize = StatData.FileSize;
|
|
CookedFileStatData.FileExt = FCookedFileStatData::EFileExt(ExtIndex);
|
|
if (ExtIndex < NumPackageExtensions)
|
|
{
|
|
CookedFileStatData.FileType = FCookedFileStatData::PackageHeader;
|
|
}
|
|
else if (ExtIndex == UExpExtensionIndex)
|
|
{
|
|
CookedFileStatData.FileType = FCookedFileStatData::PackageData;
|
|
}
|
|
else
|
|
{
|
|
CookedFileStatData.FileType = FCookedFileStatData::BulkData;
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
static bool ParseSizeArgument(const TCHAR* CmdLine, const TCHAR* Argument, uint64& OutSize, uint64 DefaultSize = 0)
|
|
{
|
|
FString SizeString;
|
|
if (FParse::Value(CmdLine, Argument, SizeString) && FParse::Value(CmdLine, Argument, OutSize))
|
|
{
|
|
if (SizeString.EndsWith(TEXT("MB")))
|
|
{
|
|
OutSize *= 1024*1024;
|
|
}
|
|
else if (SizeString.EndsWith(TEXT("KB")))
|
|
{
|
|
OutSize *= 1024;
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
OutSize = DefaultSize;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int32 CreateIoStoreContainerFiles(const TCHAR* CmdLine)
|
|
{
|
|
IOSTORE_CPU_SCOPE(CreateIoStoreContainerFiles);
|
|
|
|
UE_LOG(LogIoStore, Display, TEXT("==================== IoStore Utils ===================="));
|
|
|
|
FIoStoreArguments Arguments;
|
|
|
|
LoadKeyChain(FCommandLine::Get(), Arguments.KeyChain);
|
|
|
|
if (FParse::Param(FCommandLine::Get(), TEXT("sign")))
|
|
{
|
|
Arguments.bSign = true;
|
|
}
|
|
|
|
UE_LOG(LogIoStore, Display, TEXT("Container signing - %s"), Arguments.bSign ? TEXT("ENABLED") : TEXT("DISABLED"));
|
|
|
|
FString PatchReferenceCryptoKeysFilename;
|
|
FKeyChain PatchKeyChain;
|
|
if (FParse::Value(FCommandLine::Get(), TEXT("PatchCryptoKeys="), PatchReferenceCryptoKeysFilename))
|
|
{
|
|
LoadKeyChainFromFile(PatchReferenceCryptoKeysFilename, Arguments.PatchKeyChain);
|
|
}
|
|
|
|
FString GameOrderFilePath;
|
|
if (FParse::Value(FCommandLine::Get(), TEXT("GameOrder="), GameOrderFilePath))
|
|
{
|
|
if (!ParsePakOrderFile(*GameOrderFilePath, Arguments.GameOrderMap))
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
FString CookerOrderFilePath;
|
|
if (FParse::Value(FCommandLine::Get(), TEXT("CookerOrder="), CookerOrderFilePath))
|
|
{
|
|
if (!ParsePakOrderFile(*CookerOrderFilePath, Arguments.CookerOrderMap))
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
FIoStoreWriterSettings GeneralIoWriterSettings { DefaultCompressionMethod, DefaultCompressionBlockSize, false };
|
|
GeneralIoWriterSettings.bEnableCsvOutput = FParse::Param(CmdLine, TEXT("-csvoutput"));
|
|
|
|
TArray<FName> CompressionFormats;
|
|
FString DesiredCompressionFormats;
|
|
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))
|
|
{
|
|
GeneralIoWriterSettings.CompressionMethod = FormatName;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (GeneralIoWriterSettings.CompressionMethod == NAME_None)
|
|
{
|
|
UE_LOG(LogIoStore, Warning, TEXT("Failed to find desired compression format(s) '%s'. Using falling back to '%s'"),
|
|
*DesiredCompressionFormats, *DefaultCompressionMethod.ToString());
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogIoStore, Display, TEXT("Using compression format '%s'"), *GeneralIoWriterSettings.CompressionMethod.ToString());
|
|
}
|
|
}
|
|
|
|
ParseSizeArgument(CmdLine, TEXT("-alignformemorymapping="), GeneralIoWriterSettings.MemoryMappingAlignment, DefaultMemoryMappingAlignment);
|
|
ParseSizeArgument(CmdLine, TEXT("-compressionblocksize="), GeneralIoWriterSettings.CompressionBlockSize, DefaultCompressionBlockSize);
|
|
ParseSizeArgument(CmdLine, TEXT("-blocksize="), GeneralIoWriterSettings.CompressionBlockAlignment);
|
|
|
|
uint64 PatchPaddingAlignment = 0;
|
|
if (ParseSizeArgument(CmdLine, TEXT("-patchpaddingalign"), PatchPaddingAlignment))
|
|
{
|
|
if (!GeneralIoWriterSettings.CompressionBlockAlignment || PatchPaddingAlignment < GeneralIoWriterSettings.CompressionBlockAlignment)
|
|
{
|
|
GeneralIoWriterSettings.CompressionBlockAlignment = PatchPaddingAlignment;
|
|
}
|
|
}
|
|
if (!GeneralIoWriterSettings.CompressionBlockAlignment)
|
|
{
|
|
GeneralIoWriterSettings.CompressionBlockAlignment = DefaultCompressionBlockAlignment;
|
|
}
|
|
|
|
UE_LOG(LogIoStore, Display, TEXT("Using memory mapping alignment '%ld'"), GeneralIoWriterSettings.MemoryMappingAlignment);
|
|
UE_LOG(LogIoStore, Display, TEXT("Using compression block size '%ld'"), GeneralIoWriterSettings.CompressionBlockSize);
|
|
UE_LOG(LogIoStore, Display, TEXT("Using compression block alignment '%ld'"), GeneralIoWriterSettings.CompressionBlockAlignment);
|
|
|
|
FParse::Value(CmdLine, TEXT("-MetaOutputDirectory="), Arguments.MetaOutputDir);
|
|
FParse::Value(CmdLine, TEXT("-MetaInputDirectory="), Arguments.MetaInputDir);
|
|
|
|
FString CommandListFile;
|
|
if (FParse::Value(FCommandLine::Get(), TEXT("Commands="), CommandListFile))
|
|
{
|
|
UE_LOG(LogIoStore, Display, TEXT("Using command list file: '%s'"), *CommandListFile);
|
|
TArray<FString> Commands;
|
|
if (!FFileHelper::LoadFileToStringArray(Commands, *CommandListFile))
|
|
{
|
|
UE_LOG(LogIoStore, Error, TEXT("Failed to read command list file '%s'."), *CommandListFile);
|
|
return -1;
|
|
}
|
|
|
|
Arguments.Containers.Reserve(Commands.Num());
|
|
for (const FString& Command : Commands)
|
|
{
|
|
FContainerSourceSpec& ContainerSpec = Arguments.Containers.AddDefaulted_GetRef();
|
|
|
|
if (!FParse::Value(*Command, TEXT("Output="), ContainerSpec.OutputPath))
|
|
{
|
|
UE_LOG(LogIoStore, Error, TEXT("Output argument missing from command '%s'"), *Command);
|
|
return -1;
|
|
}
|
|
ContainerSpec.OutputPath = FPaths::ChangeExtension(ContainerSpec.OutputPath, TEXT(""));
|
|
|
|
FString ContainerName;
|
|
if (FParse::Value(*Command, TEXT("ContainerName="), ContainerName))
|
|
{
|
|
ContainerSpec.Name = FName(ContainerName);
|
|
}
|
|
|
|
FString PatchSourceWildcard;
|
|
if (FParse::Value(*Command, TEXT("PatchSource="), PatchSourceWildcard))
|
|
{
|
|
IFileManager::Get().FindFiles(ContainerSpec.PatchSourceContainerFiles, *PatchSourceWildcard, true, false);
|
|
FString PatchSourceContainersDirectory = FPaths::GetPath(*PatchSourceWildcard);
|
|
for (FString& PatchSourceContainerFile : ContainerSpec.PatchSourceContainerFiles)
|
|
{
|
|
PatchSourceContainerFile = PatchSourceContainersDirectory / PatchSourceContainerFile;
|
|
FPaths::NormalizeFilename(PatchSourceContainerFile);
|
|
}
|
|
}
|
|
|
|
ContainerSpec.bGenerateDiffPatch = FParse::Param(*Command, TEXT("GenerateDiffPatch"));
|
|
|
|
FParse::Value(*Command, TEXT("PatchTarget="), ContainerSpec.PatchTargetFile);
|
|
|
|
FString ResponseFilePath;
|
|
if (FParse::Value(*Command, TEXT("ResponseFile="), ResponseFilePath))
|
|
{
|
|
if (!ParsePakResponseFile(*ResponseFilePath, ContainerSpec.SourceFiles))
|
|
{
|
|
UE_LOG(LogIoStore, Error, TEXT("Failed to parse Pak response file '%s'"), *ResponseFilePath);
|
|
return -1;
|
|
}
|
|
|
|
FString EncryptionKeyOverrideGuidString;
|
|
if (FParse::Value(*Command, TEXT("EncryptionKeyOverrideGuid="), EncryptionKeyOverrideGuidString))
|
|
{
|
|
FGuid::Parse(EncryptionKeyOverrideGuidString, ContainerSpec.EncryptionKeyOverrideGuid);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (FParse::Value(FCommandLine::Get(), TEXT("BasedOnReleaseVersionPath="), Arguments.BasedOnReleaseVersionPath))
|
|
{
|
|
UE_LOG(LogIoStore, Display, TEXT("Based on release version path: '%s'"), *Arguments.BasedOnReleaseVersionPath);
|
|
}
|
|
|
|
if (FParse::Value(FCommandLine::Get(), TEXT("DLCFile="), Arguments.DLCPluginPath))
|
|
{
|
|
Arguments.DLCName = FPaths::GetBaseFilename(*Arguments.DLCPluginPath);
|
|
Arguments.bRemapPluginContentToGame = FParse::Param(FCommandLine::Get(), TEXT("RemapPluginContentToGame"));
|
|
|
|
UE_LOG(LogIoStore, Display, TEXT("DLC: '%s'"), *Arguments.DLCPluginPath);
|
|
UE_LOG(LogIoStore, Display, TEXT("Remapping plugin content to game: '%s'"), Arguments.bRemapPluginContentToGame ? TEXT("True") : TEXT("False"));
|
|
|
|
if (Arguments.BasedOnReleaseVersionPath.IsEmpty())
|
|
{
|
|
UE_LOG(LogIoStore, Error, TEXT("Based on release version path is needed for DLC"));
|
|
return -1;
|
|
}
|
|
|
|
FString DevelopmentAssetRegistryPath = FPaths::Combine(Arguments.BasedOnReleaseVersionPath, TEXT("Metadata/DevelopmentAssetRegistry.bin"));
|
|
|
|
bool bAssetRegistryLoaded = false;
|
|
FArrayReader SerializedAssetData;
|
|
if (FFileHelper::LoadFileToArray(SerializedAssetData, *DevelopmentAssetRegistryPath))
|
|
{
|
|
FAssetRegistrySerializationOptions Options;
|
|
if (Arguments.ReleaseAssetRegistry.Serialize(SerializedAssetData, Options))
|
|
{
|
|
UE_LOG(LogIoStore, Display, TEXT("Loaded asset registry '%s'"), *DevelopmentAssetRegistryPath);
|
|
bAssetRegistryLoaded = true;
|
|
|
|
TArray<FName> PackageNames;
|
|
Arguments.ReleaseAssetRegistry.GetPackageNames(PackageNames);
|
|
Arguments.ReleasedPackages.PackageNames.Reserve(PackageNames.Num());
|
|
Arguments.ReleasedPackages.PackageIdToName.Reserve(PackageNames.Num());
|
|
|
|
for (FName PackageName : PackageNames)
|
|
{
|
|
Arguments.ReleasedPackages.PackageNames.Add(PackageName);
|
|
Arguments.ReleasedPackages.PackageIdToName.Add(FPackageId::FromName(PackageName), PackageName);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bAssetRegistryLoaded)
|
|
{
|
|
UE_LOG(LogIoStore, Warning, TEXT("Failed to load Asset registry '%s'. Needed to verify DLC package names"), *DevelopmentAssetRegistryPath);
|
|
}
|
|
}
|
|
|
|
if (FParse::Value(FCommandLine::Get(), TEXT("CreateGlobalContainer="), Arguments.GlobalContainerPath))
|
|
{
|
|
Arguments.GlobalContainerPath = FPaths::ChangeExtension(Arguments.GlobalContainerPath, TEXT(""));
|
|
}
|
|
|
|
if (Arguments.ShouldCreateContainers())
|
|
{
|
|
FString TargetPlatform;
|
|
if (FParse::Value(FCommandLine::Get(), TEXT("TargetPlatform="), TargetPlatform))
|
|
{
|
|
UE_LOG(LogIoStore, Display, TEXT("Using target platform '%s'"), *TargetPlatform);
|
|
ITargetPlatformManagerModule& TPM = GetTargetPlatformManagerRef();
|
|
Arguments.TargetPlatform = TPM.FindTargetPlatform(TargetPlatform);
|
|
if (!Arguments.TargetPlatform)
|
|
{
|
|
UE_LOG(LogIoStore, Error, TEXT("Invalid TargetPlatform: '%s'"), *TargetPlatform);
|
|
return 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogIoStore, Error, TEXT("TargetPlatform must be specified"));
|
|
return 1;
|
|
}
|
|
|
|
// Now that we have the target platform we need to error out if LegacyBulkDataOffsets is enabled for it
|
|
{
|
|
FConfigFile PlatformEngineIni;
|
|
FConfigCacheIni::LoadLocalIniFile(PlatformEngineIni, TEXT("Engine"), true, *Arguments.TargetPlatform->IniPlatformName());
|
|
|
|
bool bLegacyBulkDataOffsets = false;
|
|
PlatformEngineIni.GetBool(TEXT("Core.System"), TEXT("LegacyBulkDataOffsets"), bLegacyBulkDataOffsets);
|
|
|
|
if (bLegacyBulkDataOffsets)
|
|
{
|
|
UE_LOG(LogIoStore, Error, TEXT("'LegacyBulkDataOffsets' is enabled for the target platform '%s', this needs to be disabled and the data recooked in order for the IoStore to work"), *Arguments.TargetPlatform->IniPlatformName());
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
|
|
if (!FParse::Value(FCommandLine::Get(), TEXT("CookedDirectory="), Arguments.CookedDir))
|
|
{
|
|
UE_LOG(LogIoStore, Error, TEXT("CookedDirectory must be specified"));
|
|
return 1;
|
|
}
|
|
|
|
for (const FContainerSourceSpec& Container : Arguments.Containers)
|
|
{
|
|
if (Container.Name.IsNone())
|
|
{
|
|
UE_LOG(LogIoStore, Error, TEXT("ContainerName argument missing for container '%s'"), *Container.OutputPath);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
UE_LOG(LogIoStore, Display, TEXT("Searching for cooked assets in folder '%s'"), *Arguments.CookedDir);
|
|
FCookedFileVisitor CookedFileVistor(Arguments.CookedFileStatMap, nullptr);
|
|
IFileManager::Get().IterateDirectoryStatRecursively(*Arguments.CookedDir, CookedFileVistor);
|
|
UE_LOG(LogIoStore, Display, TEXT("Found '%d' files"), Arguments.CookedFileStatMap.Num());
|
|
|
|
int32 ReturnValue = CreateTarget(Arguments, GeneralIoWriterSettings);
|
|
if (ReturnValue != 0)
|
|
{
|
|
return ReturnValue;
|
|
}
|
|
}
|
|
else if (FParse::Param(FCommandLine::Get(), TEXT("CreateContentPatch")))
|
|
{
|
|
for (const FContainerSourceSpec& Container : Arguments.Containers)
|
|
{
|
|
if (Container.PatchTargetFile.IsEmpty())
|
|
{
|
|
UE_LOG(LogIoStore, Error, TEXT("PatchTarget argument missing for container '%s'"), *Container.OutputPath);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
int32 ReturnValue = CreateContentPatch(Arguments, GeneralIoWriterSettings);
|
|
if (ReturnValue != 0)
|
|
{
|
|
return ReturnValue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogIoStore, Error, TEXT("Nothing to do!"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|