bool ULevelDBPluginBPLibrary::WriteObjectToLevelDB(ULevelDBObject* DatabaseObject, FString Key, UObject* Value) { if (IsValid(Value)) { //Vars TArray<uint8> ObjectBytes; FMemoryWriter MemoryWriter(ObjectBytes, true); //Write Object Class name so we can deserialize it one day FString ObjectClassName = Value->GetClass()->GetName(); MemoryWriter << ObjectClassName; //Serialize Object and put into the byte array as well FObjectAndNameAsStringProxyArchive Ar(MemoryWriter, false); Value->Serialize(Ar); //marshall pointer to first array entry auto ptr = reinterpret_cast<const char*>(ObjectBytes.GetData()); //level db options leveldb::WriteOptions writeOptions; //create a slice to store leveldb::Slice data = leveldb::Slice(ptr, ObjectBytes.Num()); //Smash it into the db if (DatabaseObject->db != NULL) DatabaseObject->db->Put(writeOptions, TCHAR_TO_UTF8(*Key), data); else return false; return true; } return false; }
void UDataTable::CleanBeforeStructChange() { RowsSerializedWithTags.Reset(); TemporarilyReferencedObjects.Empty(); { class FRawStructWriter : public FObjectWriter { TSet<UObject*>& TemporarilyReferencedObjects; public: FRawStructWriter(TArray<uint8>& InBytes, TSet<UObject*>& InTemporarilyReferencedObjects) : FObjectWriter(InBytes), TemporarilyReferencedObjects(InTemporarilyReferencedObjects) {} virtual FArchive& operator<<(class UObject*& Res) override { FObjectWriter::operator<<(Res); TemporarilyReferencedObjects.Add(Res); return *this; } }; FRawStructWriter MemoryWriter(RowsSerializedWithTags, TemporarilyReferencedObjects); SaveStructData(MemoryWriter); } EmptyTable(); Modify(); }
MemoryWriter Image::encode(ImageFormat format) const { if (isEmpty()) { return MemoryWriter(); } if (format == ImageFormat::Unspecified) { format = ImageFormat::PNG; } return Siv3DEngine::GetImageFormat()->encode(*this, format); }
bool CloudySaveManagerImpl::Cloudy_SaveGameToSlot(USaveGame* SaveGameObject, const FString& SlotName, const int32 UserIndex, const int32 PCID) { bool bSuccess = false; // Appends the player controller ID to the save file, // so that the split screen players do not overwrite 1 save file. FString NewSlotName = SlotName + "-" + FString::FromInt(PCID); ISaveGameSystem* SaveSystem = IPlatformFeaturesModule::Get().GetSaveGameSystem(); // If we have a system and an object to save and a save name... if (SaveSystem && (SaveGameObject != NULL) && (NewSlotName.Len() > 0)) { TArray<uint8> ObjectBytes; FMemoryWriter MemoryWriter(ObjectBytes, true); // write file type tag. identifies this file type and indicates it's using proper versioning // since older UE4 versions did not version this data. int32 FileTypeTag = UE4_SAVEGAME_FILE_TYPE_TAG; MemoryWriter << FileTypeTag; // Write version for this file format int32 SavegameFileVersion = UE4_SAVEGAME_FILE_VERSION; MemoryWriter << SavegameFileVersion; // Write out engine and UE4 version information MemoryWriter << GPackageFileUE4Version; FEngineVersion SavedEngineVersion = GEngineVersion; MemoryWriter << SavedEngineVersion; // Write the class name so we know what class to load to FString SaveGameClassName = SaveGameObject->GetClass()->GetName(); MemoryWriter << SaveGameClassName; // Then save the object state, replacing object refs and names with strings FObjectAndNameAsStringProxyArchive Ar(MemoryWriter, false); SaveGameObject->Serialize(Ar); // Stuff that data into the save system with the desired file name SaveSystem->SaveGame(false, *NewSlotName, UserIndex, ObjectBytes); bSuccess = ICloudyWebAPI::Get().UploadFile(NewSlotName, PCID); } return bSuccess; }
void FStatsWriteFile::WriteHeader( bool bIsRawStatsFile ) { FMemoryWriter MemoryWriter( OutData, false, true ); FArchive& Ar = File ? *File : MemoryWriter; uint32 Magic = EStatMagicWithHeader::MAGIC; // Serialize magic value. Ar << Magic; // Serialize dummy header, overwritten in Finalize. Header.Version = EStatMagicWithHeader::VERSION_4; Header.PlatformName = FPlatformProperties::PlatformName(); Header.bRawStatsFile = bIsRawStatsFile; Ar << Header; // Serialize metadata. WriteMetadata( Ar ); Ar.Flush(); }
/** Saves the global shader map as a file for the target platform. */ FString SaveGlobalShaderFile(EShaderPlatform Platform, FString SavePath) { TShaderMap<FGlobalShaderType>* GlobalShaderMap = GetGlobalShaderMap(Platform); // Wait until all global shaders are compiled if (GShaderCompilingManager) { GShaderCompilingManager->ProcessAsyncResults(false, true); } TArray<uint8> GlobalShaderData; FMemoryWriter MemoryWriter(GlobalShaderData); SerializeGlobalShaders(MemoryWriter, GlobalShaderMap); // make the final name FString FullPath = SavePath / GetGlobalShaderCacheFilename(Platform); verify(FFileHelper::SaveArrayToFile(GlobalShaderData, *FullPath)); return FullPath; }
/** * Serialize just the bulk data portion to/ from the passed in memory. * * @param Ar Archive to serialize with * @param Data Memory to serialize either to or from */ void FUntypedBulkData::SerializeBulkData( FArchive& Ar, void* Data ) { // skip serializing of unused data if( BulkDataFlags & BULKDATA_Unused ) { return; } // Skip serialization for bulk data of zero length const int32 BulkDataSize = GetBulkDataSize(); if(BulkDataSize == 0) { return; } // Allow backward compatible serialization by forcing bulk serialization off if required. Saving also always uses single // element serialization so errors or oversight when changing serialization code is recoverable. bool bSerializeInBulk = true; if( RequiresSingleElementSerialization( Ar ) // Set when serialized like a lazy array. || (BulkDataFlags & BULKDATA_ForceSingleElementSerialization) // We use bulk serialization even when saving 1 byte types (texture & sound bulk data) as an optimization for those. || (Ar.IsSaving() && (GetElementSize() > 1) ) ) { bSerializeInBulk = false; } // Raw serialize the bulk data without any possiblity for potential endian conversion. if( bSerializeInBulk ) { // Serialize data compressed. if( BulkDataFlags & BULKDATA_SerializeCompressed ) { Ar.SerializeCompressed( Data, GetBulkDataSize(), GetDecompressionFlags()); } // Uncompressed/ regular serialization. else { Ar.Serialize( Data, GetBulkDataSize() ); } } // Serialize an element at a time via the virtual SerializeElement function potentialy allowing and dealing with // endian conversion. Dealing with compression makes this a bit more complex as SerializeCompressed expects the // full data to be compresed en block and not piecewise. else { // Serialize data compressed. if( BulkDataFlags & BULKDATA_SerializeCompressed ) { // Placeholder for to be serialized data. TArray<uint8> SerializedData; // Loading, data is compressed in archive and needs to be decompressed. if( Ar.IsLoading() ) { // Create space for uncompressed data. SerializedData.Empty( GetBulkDataSize() ); SerializedData.AddUninitialized( GetBulkDataSize() ); // Serialize data with passed in archive and compress. Ar.SerializeCompressed( SerializedData.GetData(), SerializedData.Num(), GetDecompressionFlags()); // Initialize memory reader with uncompressed data array and propagate forced byte swapping FMemoryReader MemoryReader( SerializedData, true ); MemoryReader.SetByteSwapping( Ar.ForceByteSwapping() ); // Serialize each element individually via memory reader. for( int32 ElementIndex=0; ElementIndex<ElementCount; ElementIndex++ ) { SerializeElement( MemoryReader, Data, ElementIndex ); } } // Saving, data is uncompressed in memory and needs to be compressed. else if( Ar.IsSaving() ) { // Initialize memory writer with blank data array and propagate forced byte swapping FMemoryWriter MemoryWriter( SerializedData, true ); MemoryWriter.SetByteSwapping( Ar.ForceByteSwapping() ); // Serialize each element individually via memory writer. for( int32 ElementIndex=0; ElementIndex<ElementCount; ElementIndex++ ) { SerializeElement( MemoryWriter, Data, ElementIndex ); } // Serialize data with passed in archive and compress. Ar.SerializeCompressed( SerializedData.GetData(), SerializedData.Num(), GetDecompressionFlags() ); } } // Uncompressed/ regular serialization. else { // We can use the passed in archive if we're not compressing the data. for( int32 ElementIndex=0; ElementIndex<ElementCount; ElementIndex++ ) { SerializeElement( Ar, Data, ElementIndex ); } } } }
void FCrashUpload::CompressAndSendData() { UE_LOG(CrashReportClientLog, Log, TEXT("CompressAndSendData have %d pending files"), PendingFiles.Num()); // Compress all files into one archive. const int32 BufferSize = 16*1024*1024; TArray<uint8> UncompressedData; UncompressedData.Reserve( BufferSize ); FMemoryWriter MemoryWriter( UncompressedData, false, true ); int32 CurrentFileIndex = 0; // Loop to keep trying files until a send succeeds or we run out of files while (PendingFiles.Num() != 0) { const FString PathOfFileToUpload = PendingFiles.Pop(); if (FPlatformFileManager::Get().GetPlatformFile().FileSize(*PathOfFileToUpload) > MaxFileSizeToUpload) { UE_LOG(CrashReportClientLog, Warning, TEXT("Skipping large crash report file")); continue; } if (!FFileHelper::LoadFileToArray(PostData, *PathOfFileToUpload)) { UE_LOG(CrashReportClientLog, Warning, TEXT("Failed to load crash report file")); continue; } const bool bSkipLogFile = !FCrashReportClientConfig::Get().GetSendLogFile() && PathOfFileToUpload.EndsWith( TEXT( ".log" ) ); if (bSkipLogFile) { UE_LOG( CrashReportClientLog, Warning, TEXT( "Skipping the log file" ) ); continue; } UE_LOG(CrashReportClientLog, Log, TEXT("CompressAndSendData compressing %d bytes ('%s')"), PostData.Num(), *PathOfFileToUpload); FString Filename = FPaths::GetCleanFilename(PathOfFileToUpload); if (Filename == "diagnostics.txt") { // Ensure diagnostics file is capitalized for server Filename[0] = 'D'; } FCompressedCrashFile FileToCompress( CurrentFileIndex, Filename, PostData ); CurrentFileIndex++; MemoryWriter << FileToCompress; } uint8* CompressedDataRaw = new uint8[BufferSize]; int32 CompressedSize = BufferSize; int32 UncompressedSize = UncompressedData.Num(); const bool bResult = FCompression::CompressMemory( COMPRESS_ZLIB, CompressedDataRaw, CompressedSize, UncompressedData.GetData(), UncompressedSize ); if( !bResult ) { UE_LOG(CrashReportClientLog, Warning, TEXT("Couldn't compress the crash report files")); SetCurrentState(EUploadState::Cancelled); return; } const FString Filename = ErrorReport.GetReportDirectoryLeafName() + TEXT(".ue4crash"); // Copy compressed data into the array. TArray<uint8> CompressedData; CompressedData.Append( CompressedDataRaw, CompressedSize ); delete [] CompressedDataRaw; CompressedDataRaw = nullptr; // Set up request for upload auto Request = CreateHttpRequest(); Request->SetVerb(TEXT("POST")); Request->SetHeader(TEXT("Content-Type"), TEXT("application/octet-stream")); Request->SetURL(UrlPrefix / TEXT("UploadReportFile")); Request->SetContent(CompressedData); Request->SetHeader(TEXT("DirectoryName"), *ErrorReport.GetReportDirectoryLeafName()); Request->SetHeader(TEXT("FileName"), Filename); Request->SetHeader(TEXT("FileLength"), TTypeToString<int32>::ToString(CompressedData.Num()) ); Request->SetHeader(TEXT("CompressedSize"), TTypeToString<int32>::ToString(CompressedSize) ); Request->SetHeader(TEXT("UncompressedSize"), TTypeToString<int32>::ToString(UncompressedSize) ); Request->SetHeader(TEXT("NumberOfFiles"), TTypeToString<int32>::ToString(CurrentFileIndex) ); UE_LOG( CrashReportClientLog, Log, TEXT( "Sending HTTP request: %s" ), *Request->GetURL() ); if (Request->ProcessRequest()) { return; } else { UE_LOG(CrashReportClientLog, Warning, TEXT("Failed to send file upload request")); SetCurrentState(EUploadState::Cancelled); } }
PRAGMA_ENABLE_OPTIMIZATION #endif /** * Handles Byte-swapping outgoing animation data to an array of BYTEs * * @param Seq An Animation Sequence to write. * @param SerializedData The output buffer. * @param ForceByteSwapping true is byte swapping is not optional. */ void AnimEncodingLegacyBase::ByteSwapOut( UAnimSequence& Seq, TArray<uint8>& SerializedData, bool ForceByteSwapping) { FMemoryWriter MemoryWriter( SerializedData, true ); MemoryWriter.SetByteSwapping( ForceByteSwapping ); uint8* StreamBase = Seq.CompressedByteStream.GetData(); const int32 NumTracks = Seq.CompressedTrackOffsets.Num()/4; bool bHasValidScale = Seq.CompressedScaleOffsets.IsValid(); for ( int32 TrackIndex = 0; TrackIndex < NumTracks; ++TrackIndex ) { const int32 OffsetTrans = Seq.CompressedTrackOffsets[TrackIndex*4]; const int32 NumKeysTrans = Seq.CompressedTrackOffsets[TrackIndex*4+1]; const int32 OffsetRot = Seq.CompressedTrackOffsets[TrackIndex*4+2]; const int32 NumKeysRot = Seq.CompressedTrackOffsets[TrackIndex*4+3]; // Translation data. checkSlow( (OffsetTrans % 4) == 0 && "CompressedByteStream not aligned to four bytes" ); uint8* TransTrackData = StreamBase + OffsetTrans; if (Seq.TranslationCodec != NULL) { ((AnimEncodingLegacyBase*)Seq.TranslationCodec)->ByteSwapTranslationOut(Seq, MemoryWriter, TransTrackData, NumKeysTrans); } else { UE_LOG(LogAnimationCompression, Fatal, TEXT("%i: unknown or unsupported animation format"), (int32)Seq.KeyEncodingFormat ); }; // Like the compressed byte stream, pad the serialization stream to four bytes. PadMemoryWriter(&MemoryWriter, TransTrackData, 4); // Rotation data. checkSlow( (OffsetRot % 4) == 0 && "CompressedByteStream not aligned to four bytes" ); uint8* RotTrackData = StreamBase + OffsetRot; checkSlow(Seq.RotationCodec != NULL); ((AnimEncodingLegacyBase*)Seq.RotationCodec)->ByteSwapRotationOut(Seq, MemoryWriter, RotTrackData, NumKeysRot); // Like the compressed byte stream, pad the serialization stream to four bytes. PadMemoryWriter(&MemoryWriter, RotTrackData, 4); if (bHasValidScale) { const int32 OffsetScale = Seq.CompressedScaleOffsets.GetOffsetData(TrackIndex, 0); const int32 NumKeysScale = Seq.CompressedScaleOffsets.GetOffsetData(TrackIndex, 1); // Scale data. checkSlow( (OffsetScale % 4) == 0 && "CompressedByteStream not aligned to four bytes" ); uint8* ScaleTrackData = StreamBase + OffsetScale; if (Seq.ScaleCodec != NULL) { ((AnimEncodingLegacyBase*)Seq.ScaleCodec)->ByteSwapScaleOut(Seq, MemoryWriter, ScaleTrackData, NumKeysScale); } else { UE_LOG(LogAnimationCompression, Fatal, TEXT("%i: unknown or unsupported animation format"), (int32)Seq.KeyEncodingFormat ); }; // Like the compressed byte stream, pad the serialization stream to four bytes. PadMemoryWriter(&MemoryWriter, ScaleTrackData, 4); } } }
void FTextLocalizationManager::RegenerateResources( const FString& ConfigFilePath, IInternationalizationArchiveSerializer& ArchiveSerializer, IInternationalizationManifestSerializer& ManifestSerializer ) { // Add one to the revision index, so all FText's refresh. ++HeadCultureRevisionIndex; FInternationalization& I18N = FInternationalization::Get(); FString SectionName = TEXT("RegenerateResources"); // Get source path. FString SourcePath; if( !( GConfig->GetString( *SectionName, TEXT("SourcePath"), SourcePath, ConfigFilePath ) ) ) { UE_LOG(LogTextLocalizationManager, Error, TEXT("No source path specified.")); return; } // Get destination path. FString DestinationPath; if( !( GConfig->GetString( *SectionName, TEXT("DestinationPath"), DestinationPath, ConfigFilePath ) ) ) { UE_LOG(LogTextLocalizationManager, Error, TEXT("No destination path specified.")); return; } // Get manifest name. FString ManifestName; if( !( GConfig->GetString( *SectionName, TEXT("ManifestName"), ManifestName, ConfigFilePath ) ) ) { UE_LOG(LogTextLocalizationManager, Error, TEXT("No manifest name specified.")); return; } // Get resource name. FString ResourceName; if( !( GConfig->GetString( *SectionName, TEXT("ResourceName"), ResourceName, ConfigFilePath ) ) ) { UE_LOG(LogTextLocalizationManager, Error, TEXT("No resource name specified.")); return; } TArray<FString> LocaleNames; { const FString CultureName = I18N.GetCurrentCulture()->GetName(); LocaleNames.Add(CultureName); const FString BaseLanguageName = I18N.GetCurrentCulture()->GetTwoLetterISOLanguageName(); if(BaseLanguageName != CultureName) { LocaleNames.Add(BaseLanguageName); } } // Source path needs to be relative to Engine or Game directory FString ConfigFullPath = FPaths::ConvertRelativePathToFull(ConfigFilePath); FString EngineFullPath = FPaths::ConvertRelativePathToFull(FPaths::EngineConfigDir()); bool IsEngineManifest = false; if (ConfigFullPath.StartsWith(EngineFullPath)) { IsEngineManifest = true; } if (IsEngineManifest) { SourcePath = FPaths::Combine(*(FPaths::EngineDir()), *SourcePath); DestinationPath = FPaths::Combine(*(FPaths::EngineDir()), *DestinationPath); } else { SourcePath = FPaths::Combine(*(FPaths::GameDir()), *SourcePath); DestinationPath = FPaths::Combine(*(FPaths::GameDir()), *DestinationPath); } TArray<TArray<uint8>> BackingBuffers; BackingBuffers.SetNum(LocaleNames.Num()); for(int32 i = 0; i < BackingBuffers.Num(); ++i) { TArray<uint8>& BackingBuffer = BackingBuffers[i]; FMemoryWriter MemoryWriter(BackingBuffer, true); // Read the manifest file from the source path. FString ManifestFilePath = (SourcePath / ManifestName); ManifestFilePath = FPaths::ConvertRelativePathToFull(ManifestFilePath); TSharedRef<FInternationalizationManifest> InternationalizationManifest = MakeShareable(new FInternationalizationManifest); #if 0 // @todo Json: Serializing from FArchive is currently broken FArchive* ManifestFile = IFileManager::Get().CreateFileReader(*ManifestFilePath); if (ManifestFile == nullptr) { UE_LOG(LogTextLocalizationManager, Error, TEXT("No manifest found at %s."), *ManifestFilePath); return; } ManifestSerializer.DeserializeManifest(*ManifestFile, InternationalizationManifest); #else FString ManifestContent; if (!FFileHelper::LoadFileToString(ManifestContent, *ManifestFilePath)) { UE_LOG(LogTextLocalizationManager, Error, TEXT("Failed to load file %s."), *ManifestFilePath); continue; } ManifestSerializer.DeserializeManifest(ManifestContent, InternationalizationManifest); #endif // Write resource. FTextLocalizationResourceGenerator::Generate(SourcePath, InternationalizationManifest, LocaleNames[i], &(MemoryWriter), ArchiveSerializer); MemoryWriter.Close(); } // Prioritized array of localization entry trackers. TArray<FLocalizationEntryTracker> LocalizationEntryTrackers; for(int32 i = 0; i < BackingBuffers.Num(); ++i) { TArray<uint8>& BackingBuffer = BackingBuffers[i]; FMemoryReader MemoryReader(BackingBuffer, true); const FString CulturePath = DestinationPath / LocaleNames[i]; const FString ResourceFilePath = FPaths::ConvertRelativePathToFull(CulturePath / ResourceName); FLocalizationEntryTracker& CultureTracker = LocalizationEntryTrackers[LocalizationEntryTrackers.Add(FLocalizationEntryTracker())]; CultureTracker.ReadFromArchive(MemoryReader, ResourceFilePath); CultureTracker.ReportCollisions(); MemoryReader.Close(); } // Don't filter updates by table name, or we can't live preview strings that had no translation originally UpdateLiveTable(LocalizationEntryTrackers); }
bool FTextPropertyTest::RunTest (const FString& Parameters) { UClass* const TextPropertyTestObjectClass = UTextPropertyTestObject::StaticClass(); UTextProperty* const DefaultedTextProperty = FindField<UTextProperty>(TextPropertyTestObjectClass, "DefaultedText"); UTextProperty* const UndefaultedTextProperty = FindField<UTextProperty>(TextPropertyTestObjectClass, "UndefaultedText"); UTextPropertyTestObject* const TextPropertyTestCDO = Cast<UTextPropertyTestObject>( TextPropertyTestObjectClass->ClassDefaultObject ); { UTextPropertyTestObject* NewObject = Cast<UTextPropertyTestObject>( StaticConstructObject( TextPropertyTestObjectClass ) ); // Test Identical - Newly constructed object properties should be identical to class default object properties. if( (DefaultedTextProperty->Identical(&(NewObject->DefaultedText), &(TextPropertyTestCDO->DefaultedText), 0) != true) || (UndefaultedTextProperty->Identical(&(NewObject->UndefaultedText), &(TextPropertyTestCDO->UndefaultedText), 0) != true) ) { AddError(TEXT("UTextProperty::Identical failed to return true comparing a newly constructed object and the class default object.")); } // Test ExportText - Export text should provide the localized form of the text. { FString ExportedStringValue; DefaultedTextProperty->ExportTextItem(ExportedStringValue, &(NewObject->DefaultedText), NULL, NULL, 0, NULL); if( ExportedStringValue != NewObject->DefaultedText.ToString() ) { AddError(TEXT("UTextProperty::ExportTextItem failed to provide the display string.")); } } // Test ImportText - Import text should set the source string to the input string. { FString ImportedStringValue = TEXT("ImportValue"); DefaultedTextProperty->ImportText(*ImportedStringValue, &(NewObject->DefaultedText), 0, NULL); const FString* const SourceString = FTextInspector::GetSourceString(NewObject->DefaultedText); if( !SourceString || ImportedStringValue != *SourceString ) { AddError(TEXT("UTextProperty::ImportText failed to alter the source string to the provided value.")); } } } // Test Identical - Altered text properties should not be identical to class default object properties. { UTextPropertyTestObject* NewObject = Cast<UTextPropertyTestObject>( StaticConstructObject( TextPropertyTestObjectClass ) ); NewObject->DefaultedText = LOCTEXT("ModifiedDefaultedText", "Modified DefaultedText Value"); NewObject->UndefaultedText = LOCTEXT("ModifiedUndefaultedText", "Modified UndefaultedText Value"); if( DefaultedTextProperty->Identical(&(NewObject->DefaultedText), &(TextPropertyTestCDO->DefaultedText), 0) || UndefaultedTextProperty->Identical(&(NewObject->UndefaultedText), &(TextPropertyTestCDO->UndefaultedText), 0) ) { AddError(TEXT("UTextProperty::Identical failed to return false comparing a modified object and the class default object.")); } } { TArray<uint8> BackingStore; UTextPropertyTestObject* SavedObject = Cast<UTextPropertyTestObject>(StaticConstructObject(UTextPropertyTestObject::StaticClass())); FText::FindText( TEXT("TextPropertyTest"), TEXT("DefaultedText"), /*OUT*/SavedObject->DefaultedText ); SavedObject->UndefaultedText = LOCTEXT("ModifiedUndefaultedText", "Modified UndefaultedText Value"); const FText TransientText = FText::Format( LOCTEXT("TransientTest", "{0}"), LOCTEXT("TransientTestMessage", "Testing Transient serialization detection") ); SavedObject->TransientText = TransientText; // Test Identical - Text properties with the same source as class default object properties should be considered identical. if( !( DefaultedTextProperty->Identical(&(SavedObject->DefaultedText), &(TextPropertyTestCDO->DefaultedText), 0) ) ) { AddError(TEXT("UTextProperty::Identical failed to return true comparing an FText with an identical source string to the class default object.")); } // Save. { FMemoryWriter MemoryWriter(BackingStore, true); SavedObject->Serialize(MemoryWriter); } UTextPropertyTestObject* LoadedObject = Cast<UTextPropertyTestObject>(StaticConstructObject(UTextPropertyTestObject::StaticClass())); // Load. { FMemoryReader MemoryReader(BackingStore, true); LoadedObject->Serialize(MemoryReader); } // Test Serialization - Loaded object should be identical to saved object. if( !( DefaultedTextProperty->Identical(&(LoadedObject->DefaultedText), &(SavedObject->DefaultedText), 0) ) || !( UndefaultedTextProperty->Identical(&(LoadedObject->UndefaultedText), &(SavedObject->UndefaultedText), 0) ) ) { AddError(TEXT("Saving and loading a serialized object containing FText properties failed to maintain FText values.")); } // Test Identical - Text properties with the same source as the class default object property should save and load as the class default object property. if( !( DefaultedTextProperty->Identical(&(LoadedObject->DefaultedText), &(TextPropertyTestCDO->DefaultedText), 0) ) ) { AddError(TEXT("UTextProperty::Identical failed to collapse identical source strings into the same namespace and key during serialization.")); } // Test Transient - Transient text properties should save out an error message instead of their actual string value const FString* const LoadedTransientTextString = FTextInspector::GetSourceString(LoadedObject->TransientText); const FString* const TransientTextString = FTextInspector::GetSourceString(TransientText); if ( GIsEditor && LoadedTransientTextString && TransientTextString && *(LoadedTransientTextString) != *(TransientTextString) ) { AddError(TEXT("Transient Texts should not exist in the editor.")); } else if ( !GIsEditor && LoadedObject->TransientText.ToString() != FText::Format( FText::SerializationFailureError, TransientText ).ToString() ) { //AddError(TEXT("Transient Texts should persist an error message when they are serialized.")); } } return true; }