EM_Task/CoreUObject/Private/UObject/LinkerSave.cpp

355 lines
12 KiB
C++
Raw Normal View History

2026-02-13 16:18:33 +08:00
// Copyright Epic Games, Inc. All Rights Reserved.
#include "UObject/LinkerSave.h"
#include "HAL/FileManager.h"
#include "Misc/ConfigCacheIni.h"
#include "Serialization/LargeMemoryWriter.h"
#include "UObject/Package.h"
#include "UObject/Class.h"
#include "Templates/Casts.h"
#include "UObject/LazyObjectPtr.h"
#include "UObject/SoftObjectPtr.h"
#include "Internationalization/TextPackageNamespaceUtil.h"
#include "UObject/UObjectThreadContext.h"
#include "UObject/UnrealType.h"
#include "HAL/PlatformStackWalk.h"
/*----------------------------------------------------------------------------
FLinkerSave.
----------------------------------------------------------------------------*/
/** A mapping of package name to generated script SHA keys */
TMap<FString, TArray<uint8>> FLinkerSave::PackagesToScriptSHAMap;
FLinkerSave::FLinkerSave(UPackage* InParent, const TCHAR* InFilename, bool bForceByteSwapping, bool bInSaveUnversioned)
: FLinker(ELinkerType::Save, InParent, InFilename), Saver(nullptr)
{
if (FPlatformProperties::HasEditorOnlyData())
{
// Create file saver.
Saver = IFileManager::Get().CreateFileWriter(InFilename, 0);
if (!Saver)
{
UE_LOG(LogLinker, Fatal, TEXT("%s"), *FString::Printf(TEXT("Error opening file '%s'."), InFilename));
}
UPackage* Package = dynamic_cast<UPackage*>(LinkerRoot);
// Set main summary info.
Summary.Tag = PACKAGE_FILE_TAG;
Summary.SetFileVersions(GPackageFileUE4Version, GPackageFileLicenseeUE4Version, bInSaveUnversioned);
Summary.SavedByEngineVersion = FEngineVersion::Current();
Summary.CompatibleWithEngineVersion = FEngineVersion::CompatibleWith();
Summary.PackageFlags = Package ? (Package->GetPackageFlags() & ~PKG_NewlyCreated) : 0;
#if USE_STABLE_LOCALIZATION_KEYS
if (GIsEditor)
{
Summary.LocalizationId = TextNamespaceUtil::GetPackageNamespace(LinkerRoot);
}
#endif // USE_STABLE_LOCALIZATION_KEYS
if (Package)
{
#if WITH_EDITORONLY_DATA
Summary.FolderName = Package->GetFolderName().ToString();
#endif
Summary.ChunkIDs = Package->GetChunkIDs();
}
// Set status info.
this->SetIsSaving(true);
this->SetIsPersistent(true);
ArForceByteSwapping = bForceByteSwapping;
#if USE_STABLE_LOCALIZATION_KEYS
if (GIsEditor)
{
SetLocalizationNamespace(Summary.LocalizationId);
}
#endif // USE_STABLE_LOCALIZATION_KEYS
}
}
FLinkerSave::FLinkerSave(UPackage* InParent, FArchive* InSaver, bool bForceByteSwapping, bool bInSaveUnversioned)
: FLinker(ELinkerType::Save, InParent, TEXT("$$Memory$$")), Saver(nullptr)
{
if (FPlatformProperties::HasEditorOnlyData())
{
// Create file saver.
Saver = InSaver;
check(Saver);
#if WITH_EDITOR
ArDebugSerializationFlags = Saver->ArDebugSerializationFlags;
#endif
UPackage* Package = dynamic_cast<UPackage*>(LinkerRoot);
// Set main summary info.
Summary.Tag = PACKAGE_FILE_TAG;
Summary.SetFileVersions(GPackageFileUE4Version, GPackageFileLicenseeUE4Version, bInSaveUnversioned);
Summary.SavedByEngineVersion = FEngineVersion::Current();
Summary.CompatibleWithEngineVersion = FEngineVersion::CompatibleWith();
Summary.PackageFlags = Package ? (Package->GetPackageFlags() & ~PKG_NewlyCreated) : 0;
#if USE_STABLE_LOCALIZATION_KEYS
if (GIsEditor)
{
Summary.LocalizationId = TextNamespaceUtil::GetPackageNamespace(LinkerRoot);
}
#endif // USE_STABLE_LOCALIZATION_KEYS
if (Package)
{
#if WITH_EDITORONLY_DATA
Summary.FolderName = Package->GetFolderName().ToString();
#endif
Summary.ChunkIDs = Package->GetChunkIDs();
}
// Set status info.
this->SetIsSaving(true);
this->SetIsPersistent(true);
ArForceByteSwapping = bForceByteSwapping;
#if USE_STABLE_LOCALIZATION_KEYS
if (GIsEditor)
{
SetLocalizationNamespace(Summary.LocalizationId);
}
#endif // USE_STABLE_LOCALIZATION_KEYS
}
}
FLinkerSave::FLinkerSave(UPackage* InParent, bool bForceByteSwapping, bool bInSaveUnversioned)
: FLinker(ELinkerType::Save, InParent, TEXT("$$Memory$$")), Saver(nullptr)
{
if (FPlatformProperties::HasEditorOnlyData())
{
// Create file saver.
Saver = new FLargeMemoryWriter(0, false, *InParent->FileName.ToString());
check(Saver);
UPackage* Package = dynamic_cast<UPackage*>(LinkerRoot);
// Set main summary info.
Summary.Tag = PACKAGE_FILE_TAG;
Summary.SetFileVersions(GPackageFileUE4Version, GPackageFileLicenseeUE4Version, bInSaveUnversioned);
Summary.SavedByEngineVersion = FEngineVersion::Current();
Summary.CompatibleWithEngineVersion = FEngineVersion::CompatibleWith();
Summary.PackageFlags = Package ? (Package->GetPackageFlags() & ~PKG_NewlyCreated) : 0;
#if USE_STABLE_LOCALIZATION_KEYS
if (GIsEditor)
{
Summary.LocalizationId = TextNamespaceUtil::GetPackageNamespace(LinkerRoot);
}
#endif // USE_STABLE_LOCALIZATION_KEYS
if (Package)
{
#if WITH_EDITORONLY_DATA
Summary.FolderName = Package->GetFolderName().ToString();
#endif
Summary.ChunkIDs = Package->GetChunkIDs();
}
// Set status info.
this->SetIsSaving(true);
this->SetIsPersistent(true);
ArForceByteSwapping = bForceByteSwapping;
#if USE_STABLE_LOCALIZATION_KEYS
if (GIsEditor)
{
SetLocalizationNamespace(Summary.LocalizationId);
}
#endif // USE_STABLE_LOCALIZATION_KEYS
}
}
bool FLinkerSave::CloseAndDestroySaver()
{
bool bSuccess = true;
if (Saver)
{
// first, do an explicit close to check for archive errors
bSuccess = Saver->Close();
// then, destroy it
delete Saver;
}
Saver = nullptr;
return bSuccess;
}
FLinkerSave::~FLinkerSave()
{
CloseAndDestroySaver();
}
int32 FLinkerSave::MapName(FNameEntryId Id) const
{
const int32* IndexPtr = NameIndices.Find(Id);
if (IndexPtr)
{
return *IndexPtr;
}
return INDEX_NONE;
}
FPackageIndex FLinkerSave::MapObject(const UObject* Object) const
{
if (Object)
{
const FPackageIndex* Found = ObjectIndicesMap.Find(Object);
if (Found)
{
if (IsEventDrivenLoaderEnabledInCookedBuilds() &&
IsCooking() && CurrentlySavingExport.IsExport() &&
Object->GetOutermost()->GetFName() != GLongCoreUObjectPackageName && // We assume nothing in coreuobject ever loads assets in a constructor
*Found != CurrentlySavingExport) // would be weird, but I can't be a dependency on myself
{
const FObjectExport& SavingExport = Exp(CurrentlySavingExport);
bool bFoundDep = false;
if (SavingExport.FirstExportDependency >= 0)
{
int32 NumDeps = SavingExport.CreateBeforeCreateDependencies + SavingExport.CreateBeforeSerializationDependencies + SavingExport.SerializationBeforeCreateDependencies + SavingExport.SerializationBeforeSerializationDependencies;
for (int32 DepIndex = SavingExport.FirstExportDependency; DepIndex < SavingExport.FirstExportDependency + NumDeps; DepIndex++)
{
if (DepListForErrorChecking[DepIndex] == *Found)
{
bFoundDep = true;
}
}
}
if (!bFoundDep)
{
if (SavingExport.Object && SavingExport.Object->IsA(UClass::StaticClass()) && CastChecked<UClass>(SavingExport.Object)->GetDefaultObject() == Object)
{
bFoundDep = true; // the class is saving a ref to the CDO...which doesn't really work or do anything useful, but it isn't an error
}
}
if (!bFoundDep)
{
UE_LOG(LogLinker, Fatal, TEXT("Attempt to map an object during save that was not listed as a dependency. Saving Export %d %s in %s. Missing Dep on %s %s."),
CurrentlySavingExport.ForDebugging(), *SavingExport.ObjectName.ToString(), *GetArchiveName(),
Found->IsExport() ? TEXT("Export") : TEXT("Import"), *ImpExp(*Found).ObjectName.ToString());
}
}
return *Found;
}
}
return FPackageIndex();
}
void FLinkerSave::Seek(int64 InPos)
{
Saver->Seek(InPos);
}
int64 FLinkerSave::Tell()
{
return Saver->Tell();
}
void FLinkerSave::Serialize(void* V, int64 Length)
{
#if WITH_EDITOR
Saver->ArDebugSerializationFlags = ArDebugSerializationFlags;
Saver->SetSerializedPropertyChain(GetSerializedPropertyChain(), GetSerializedProperty());
#endif
Saver->Serialize(V, Length);
}
FString FLinkerSave::GetArchiveName() const
{
return Saver->GetArchiveName();
}
FArchive& FLinkerSave::operator<<(FName& InName)
{
int32 Save = MapName(InName.GetDisplayIndex());
check(GetSerializeContext());
ensureMsgf(Save != INDEX_NONE, TEXT("Name \"%s\" is not mapped when saving %s (object: %s, property: %s)"),
*InName.ToString(),
*GetArchiveName(),
*GetSerializeContext()->SerializedObject->GetFullName(),
*GetFullNameSafe(GetSerializedProperty()));
int32 Number = InName.GetNumber();
FArchive& Ar = *this;
return Ar << Save << Number;
}
FArchive& FLinkerSave::operator<<(UObject*& Obj)
{
FPackageIndex Save;
if (Obj)
{
Save = MapObject(Obj);
}
return *this << Save;
}
FArchive& FLinkerSave::operator<<(FLazyObjectPtr& LazyObjectPtr)
{
FUniqueObjectGuid ID;
ID = LazyObjectPtr.GetUniqueID();
return *this << ID;
}
void FLinkerSave::SetSerializeContext(FUObjectSerializeContext* InLoadContext)
{
SaveContext = InLoadContext;
if (Saver)
{
Saver->SetSerializeContext(InLoadContext);
}
}
FUObjectSerializeContext* FLinkerSave::GetSerializeContext()
{
return SaveContext;
}
void FLinkerSave::UsingCustomVersion(const struct FGuid& Guid)
{
FArchiveUObject::UsingCustomVersion(Guid);
// Here we're going to try and dump the callstack that added a new custom version after package summary has been serialized
if (Summary.GetCustomVersionContainer().GetVersion(Guid) == nullptr)
{
FCustomVersion RegisteredVersion = FCurrentCustomVersions::Get(Guid).GetValue();
FString CustomVersionWarning = FString::Printf(TEXT("Unexpected custom version \"%s\" used after package %s summary has been serialized. Callstack:\n"),
*RegisteredVersion.GetFriendlyName().ToString(), *LinkerRoot->GetName());
const int32 MaxStackFrames = 100;
uint64 StackFrames[MaxStackFrames];
int32 NumStackFrames = FPlatformStackWalk::CaptureStackBackTrace(StackFrames, MaxStackFrames);
// Convert the stack trace to text, ignore the first functions (ProgramCounterToHumanReadableString)
const int32 IgnoreStackLinesCount = 1;
ANSICHAR Buffer[1024];
const ANSICHAR* CutoffFunction = "UPackage::Save";
for (int32 Idx = IgnoreStackLinesCount; Idx < NumStackFrames; Idx++)
{
Buffer[0] = '\0';
FPlatformStackWalk::ProgramCounterToHumanReadableString(Idx, StackFrames[Idx], Buffer, sizeof(Buffer));
CustomVersionWarning += TEXT("\t");
CustomVersionWarning += Buffer;
CustomVersionWarning += "\n";
if (FCStringAnsi::Strstr(Buffer, CutoffFunction))
{
// Anything below UPackage::Save is not interesting from the point of view of what we're trying to find
break;
}
}
UE_LOG(LogLinker, Warning, TEXT("%s"), *CustomVersionWarning);
}
}