// 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> 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(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(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(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(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); } }