October3d55/M/RuntimeAdd95a179eaf4V8/Source/RuntimeArchiver/Private/RuntimeArchiverBase.cpp

775 lines
24 KiB
C++

// Georgy Treshchev 2024.
#include "RuntimeArchiverBase.h"
#include "RuntimeArchiverSubsystem.h"
#include "RuntimeArchiverDefines.h"
#include "Async/Async.h"
#include "Misc/Paths.h"
#include "Misc/FileHelper.h"
#include "HAL/PlatformFileManager.h"
URuntimeArchiverBase::URuntimeArchiverBase()
: Mode(ERuntimeArchiverMode::Undefined)
, Location(ERuntimeArchiverLocation::Undefined)
{
}
URuntimeArchiverBase* URuntimeArchiverBase::CreateRuntimeArchiver(UObject* WorldContextObject, TSubclassOf<URuntimeArchiverBase> ArchiverClass)
{
return NewObject<URuntimeArchiverBase>(WorldContextObject, ArchiverClass);
}
void URuntimeArchiverBase::BeginDestroy()
{
if (IsInitialized())
{
CloseArchive();
Reset();
}
Super::BeginDestroy();
}
bool URuntimeArchiverBase::CreateArchiveInStorage(FString ArchivePath)
{
if (!Initialize())
{
ReportError(ERuntimeArchiverErrorCode::NotInitialized, FString::Printf(TEXT("Unable to initialize archiver in storage path '%s'"), *ArchivePath));
Reset();
return false;
}
if (ArchivePath.IsEmpty())
{
ReportError(ERuntimeArchiverErrorCode::InvalidArgument, TEXT("Archive name not specified"));
Reset();
return false;
}
Mode = ERuntimeArchiverMode::Write;
Location = ERuntimeArchiverLocation::Storage;
return true;
}
bool URuntimeArchiverBase::CreateArchiveInMemory(int32 InitialAllocationSize)
{
if (!Initialize())
{
ReportError(ERuntimeArchiverErrorCode::NotInitialized, TEXT("Unable to initialize archiver in memory"));
Reset();
return false;
}
if (InitialAllocationSize < 0)
{
ReportError(ERuntimeArchiverErrorCode::InvalidArgument, FString::Printf(TEXT("The initial allication size must be >= 0 (specified size: %d)"), InitialAllocationSize));
Reset();
return false;
}
Mode = ERuntimeArchiverMode::Write;
Location = ERuntimeArchiverLocation::Memory;
return true;
}
bool URuntimeArchiverBase::OpenArchiveFromStorage(FString ArchivePath)
{
if (ArchivePath.IsEmpty() || !FPaths::FileExists(ArchivePath))
{
ReportError(ERuntimeArchiverErrorCode::InvalidArgument, FString::Printf(TEXT("Archive '%s' does not exist"), *ArchivePath));
return false;
}
if (!Initialize())
{
ReportError(ERuntimeArchiverErrorCode::NotInitialized, FString::Printf(TEXT("Unable to initialize archiver in path '%s'"), *ArchivePath));
Reset();
return false;
}
Mode = ERuntimeArchiverMode::Read;
Location = ERuntimeArchiverLocation::Storage;
return true;
}
bool URuntimeArchiverBase::OpenArchiveFromMemory(TArray<uint8> ArchiveData)
{
TArray64<uint8> ArchiveData64 = TArray64<uint8>(MoveTemp(ArchiveData));
return OpenArchiveFromMemory(ArchiveData64);
}
bool URuntimeArchiverBase::OpenArchiveFromMemory(const TArray64<uint8>& ArchiveData)
{
if (!Initialize())
{
ReportError(ERuntimeArchiverErrorCode::NotInitialized, TEXT("Unable to initialize archiver in memory"));
Reset();
return false;
}
Mode = ERuntimeArchiverMode::Read;
Location = ERuntimeArchiverLocation::Memory;
return true;
}
bool URuntimeArchiverBase::CloseArchive()
{
if (!IsInitialized())
{
ReportError(ERuntimeArchiverErrorCode::NotInitialized, TEXT("Archiver is not initialized"));
return false;
}
return true;
}
bool URuntimeArchiverBase::GetArchiveData(TArray<uint8>& ArchiveData)
{
TArray64<uint8> ArchiveData64;
if (!GetArchiveData(ArchiveData64))
{
return false;
}
if (ArchiveData64.Num() > TNumericLimits<TArray<uint8>::SizeType>::Max())
{
ReportError(ERuntimeArchiverErrorCode::ExtractError, FString::Printf(TEXT("Array with int32 size (max length: %d) cannot fit int64 size data (retrieved length: %lld)\nA standard byte array can hold a maximum of 2 GB of data"), static_cast<int32>(TNumericLimits<TArray<uint8>::SizeType>::Max()), ArchiveData64.Num()));
return false;
}
ArchiveData = TArray<uint8>(MoveTemp(ArchiveData64));
return true;
}
bool URuntimeArchiverBase::GetArchiveData(TArray64<uint8>& ArchiveData)
{
if (!IsInitialized())
{
ReportError(ERuntimeArchiverErrorCode::NotInitialized, TEXT("Archiver is not initialized"));
return false;
}
return true;
}
bool URuntimeArchiverBase::GetArchiveEntries(int32& NumOfArchiveEntries)
{
if (!IsInitialized())
{
ReportError(ERuntimeArchiverErrorCode::NotInitialized, TEXT("Archiver is not initialized"));
return false;
}
return true;
}
bool URuntimeArchiverBase::GetArchiveEntryInfoByName(FString EntryName, FRuntimeArchiveEntry& EntryInfo)
{
if (!IsInitialized())
{
ReportError(ERuntimeArchiverErrorCode::NotInitialized, TEXT("Archiver is not initialized"));
return false;
}
if (EntryName.IsEmpty())
{
ReportError(ERuntimeArchiverErrorCode::InvalidArgument, TEXT("Entry name not specified"));
return false;
}
return true;
}
bool URuntimeArchiverBase::GetArchiveEntryInfoByIndex(int32 EntryIndex, FRuntimeArchiveEntry& EntryInfo)
{
if (!IsInitialized())
{
ReportError(ERuntimeArchiverErrorCode::NotInitialized, TEXT("Archiver is not initialized"));
return false;
}
if (EntryIndex < 0)
{
ReportError(ERuntimeArchiverErrorCode::InvalidArgument, FString::Printf(TEXT("Unsupported entry index. Must be >= 0 (provided index: %d)"), EntryIndex));
return false;
}
return true;
}
bool URuntimeArchiverBase::AddEntryFromStorage(FString EntryName, FString FilePath, ERuntimeArchiverCompressionLevel CompressionLevel)
{
if (!IsInitialized())
{
ReportError(ERuntimeArchiverErrorCode::NotInitialized, TEXT("Archiver is not initialized"));
return false;
}
if (Mode != ERuntimeArchiverMode::Write)
{
ReportError(ERuntimeArchiverErrorCode::UnsupportedMode, FString::Printf(TEXT("Only '%s' mode is supported for adding entries (using mode: '%s')"), *UEnum::GetValueAsName(ERuntimeArchiverMode::Write).ToString(), *UEnum::GetValueAsName(Mode).ToString()));
return false;
}
if (EntryName.IsEmpty())
{
ReportError(ERuntimeArchiverErrorCode::InvalidArgument, TEXT("Entry name not specified"));
return false;
}
FPaths::NormalizeFilename(FilePath);
if (!FPaths::FileExists(FilePath))
{
ReportError(ERuntimeArchiverErrorCode::AddError, FString::Printf(TEXT("Path '%s' does not contain a file"), *FilePath));
return false;
}
TArray64<uint8> FileData;
if (!FFileHelper::LoadFileToArray(FileData, *FilePath))
{
ReportError(ERuntimeArchiverErrorCode::AddError, FString::Printf(TEXT("Unable to load file '%s' for entry '%s'"), *FilePath, *EntryName));
return false;
}
if (!AddEntryFromMemory(EntryName, FileData, CompressionLevel))
{
UE_LOG(LogRuntimeArchiver, Error, TEXT("Unable to add entry '%s' from file '%s'"), *EntryName, *FilePath);
return false;
}
UE_LOG(LogRuntimeArchiver, Log, TEXT("Successfully added entry '%s' from '%s'"), *EntryName, *FilePath);
return true;
}
void URuntimeArchiverBase::AddEntriesFromStorage(const FRuntimeArchiverAsyncOperationResult& OnResult, const FRuntimeArchiverAsyncOperationProgress& OnProgress, TArray<FString> FilePaths, ERuntimeArchiverCompressionLevel CompressionLevel)
{
if (!IsInitialized())
{
ReportError(ERuntimeArchiverErrorCode::NotInitialized, TEXT("Archiver is not initialized"));
OnResult.ExecuteIfBound(false);
return;
}
AsyncTask(ENamedThreads::AnyBackgroundHiPriTask, [WeakThis = MakeWeakObjectPtr(this), OnResult, OnProgress, FilePaths = MoveTemp(FilePaths), CompressionLevel]()
{
if (!WeakThis.IsValid())
{
UE_LOG(LogRuntimeArchiver, Error, TEXT("Failed to add entries from storage: archiver is no longer valid"));
return;
}
auto ExecuteResult = [OnResult](bool bResult)
{
AsyncTask(ENamedThreads::GameThread, [OnResult, bResult]()
{
OnResult.ExecuteIfBound(bResult);
});
};
auto ExecuteProgress = [OnProgress](int32 Percentage)
{
AsyncTask(ENamedThreads::GameThread, [OnProgress, Percentage]()
{
OnProgress.ExecuteIfBound(Percentage);
});
};
for (TArray<FString>::SizeType FilePathsIndex = 0; FilePathsIndex < FilePaths.Num(); ++FilePathsIndex)
{
FString FilePath = FilePaths[FilePathsIndex];
FPaths::NormalizeFilename(FilePath);
const FString EntryName = FPaths::GetCleanFilename(FilePath);
if (!WeakThis->AddEntryFromStorage(EntryName, FilePath, CompressionLevel))
{
WeakThis->ReportError(ERuntimeArchiverErrorCode::AddError, FString::Printf(TEXT("Cannot add '%s' entry. Aborting async adding entries"), *EntryName));
ExecuteResult(false);
return;
}
ExecuteProgress(static_cast<float>(FilePathsIndex + 1) / FilePaths.Num() * 100);
}
UE_LOG(LogRuntimeArchiver, Log, TEXT("Successfully added '%d' entries"), FilePaths.Num());
ExecuteResult(true);
});
}
void URuntimeArchiverBase::AddEntriesFromStorage_Directory(const FRuntimeArchiverAsyncOperationResult& OnResult, FString DirectoryPath, bool bAddParentDirectory, ERuntimeArchiverCompressionLevel CompressionLevel)
{
if (!IsInitialized())
{
ReportError(ERuntimeArchiverErrorCode::NotInitialized, TEXT("Archiver is not initialized"));
OnResult.ExecuteIfBound(false);
return;
}
// Normalizing directory path
FPaths::NormalizeDirectoryName(DirectoryPath);
// Making sure the directory exists
if (!FPaths::DirectoryExists(DirectoryPath))
{
ReportError(ERuntimeArchiverErrorCode::AddError, FString::Printf(TEXT("Directory '%s' does not exist"), *DirectoryPath));
OnResult.ExecuteIfBound(false);
}
// Composing the base path to have the correct entry names
// For example, if we scanned the "C:/Folder" directory and found "C:/Folder/File.ogg", then the entry name will be either "Folder/File.ogg" or "File.ogg" (depending on bAddParentDirectory)
const FString BaseDirectoryPathToExclude = [&DirectoryPath, bAddParentDirectory]()
{
FString BasePath = FPaths::GetPath(DirectoryPath);
if (!BasePath.IsEmpty())
{
if (!bAddParentDirectory)
{
BasePath += TEXT("/") + FPaths::GetCleanFilename(DirectoryPath);
}
BasePath += TEXT("/");
}
return BasePath;
}();
AsyncTask(ENamedThreads::AnyBackgroundHiPriTask, [WeakThis = MakeWeakObjectPtr(this), OnResult, BaseDirectoryPathToExclude, DirectoryPath = MoveTemp(DirectoryPath), CompressionLevel]()
{
if (!WeakThis.IsValid())
{
UE_LOG(LogRuntimeArchiver, Error, TEXT("Failed to add entries from directory '%s': archiver is no longer valid"), *DirectoryPath);
return;
}
const bool bResult = WeakThis->AddEntriesFromStorage_Directory_Internal(BaseDirectoryPathToExclude, DirectoryPath, CompressionLevel);
if (bResult)
{
UE_LOG(LogRuntimeArchiver, Log, TEXT("Successfully added entries from directory '%s'"), *DirectoryPath);
}
AsyncTask(ENamedThreads::GameThread, [bResult, OnResult]()
{
OnResult.ExecuteIfBound(bResult);
});
});
}
bool URuntimeArchiverBase::AddEntriesFromStorage_Directory_Internal(FString BaseDirectoryPathToExclude, FString DirectoryPath, ERuntimeArchiverCompressionLevel CompressionLevel)
{
class FDirectoryVisitor_EntryAppender : public IPlatformFile::FDirectoryVisitor
{
URuntimeArchiverBase* RuntimeArchiver;
const FString BaseDirectoryPathToExclude;
const ERuntimeArchiverCompressionLevel CompressionLevel;
public:
FDirectoryVisitor_EntryAppender(URuntimeArchiverBase* RuntimeArchiver, const FString& BaseDirectoryPathToExclude, ERuntimeArchiverCompressionLevel CompressionLevel)
: RuntimeArchiver(RuntimeArchiver)
, BaseDirectoryPathToExclude(BaseDirectoryPathToExclude)
, CompressionLevel(CompressionLevel)
{
}
virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) override
{
// We are only interested in files (directories are supposed to be added automatically by the archiver when specifying the path to the file)
if (bIsDirectory)
{
return true;
}
// Get the entry name by truncating the base directory from the found file
const FString EntryName = FString(FilenameOrDirectory).RightChop(BaseDirectoryPathToExclude.Len());
if (!RuntimeArchiver->AddEntryFromStorage(EntryName, FilenameOrDirectory, CompressionLevel))
{
RuntimeArchiver->ReportError(ERuntimeArchiverErrorCode::AddError, FString::Printf(TEXT("Cannot add '%s' entry. Aborting recursive adding entries"), *EntryName));
return false;
}
return true;
}
};
FDirectoryVisitor_EntryAppender DirectoryVisitor_EntryAppender(this, BaseDirectoryPathToExclude, CompressionLevel);
return FPlatformFileManager::Get().GetPlatformFile().IterateDirectoryRecursively(*DirectoryPath, DirectoryVisitor_EntryAppender);
}
bool URuntimeArchiverBase::AddEntryFromMemory(FString EntryName, TArray<uint8> DataToBeArchived, ERuntimeArchiverCompressionLevel CompressionLevel)
{
return AddEntryFromMemory(MoveTemp(EntryName), TArray64<uint8>(MoveTemp(DataToBeArchived)), CompressionLevel);
}
bool URuntimeArchiverBase::AddEntryFromMemory(FString EntryName, const TArray64<uint8>& DataToBeArchived, ERuntimeArchiverCompressionLevel CompressionLevel)
{
if (!IsInitialized())
{
ReportError(ERuntimeArchiverErrorCode::NotInitialized, TEXT("Archiver is not initialized"));
return false;
}
if (Mode != ERuntimeArchiverMode::Write)
{
ReportError(ERuntimeArchiverErrorCode::UnsupportedMode, FString::Printf(TEXT("Only '%s' mode is supported for adding entries (using mode: '%s')"), *UEnum::GetValueAsName(ERuntimeArchiverMode::Write).ToString(), *UEnum::GetValueAsName(Mode).ToString()));
return false;
}
if (EntryName.IsEmpty())
{
ReportError(ERuntimeArchiverErrorCode::InvalidArgument, TEXT("Entry name not specified"));
return false;
}
return true;
}
bool URuntimeArchiverBase::ExtractEntryToStorage(const FRuntimeArchiveEntry& EntryInfo, FString FilePath, bool bForceOverwrite)
{
if (!IsInitialized())
{
ReportError(ERuntimeArchiverErrorCode::NotInitialized, TEXT("Archiver is not initialized"));
return false;
}
if (Mode != ERuntimeArchiverMode::Read)
{
ReportError(ERuntimeArchiverErrorCode::UnsupportedMode, FString::Printf(TEXT("Only '%s' mode is supported for extracting the entry (using mode: '%s')"), *UEnum::GetValueAsName(ERuntimeArchiverMode::Read).ToString(), *UEnum::GetValueAsName(Mode).ToString()));
return false;
}
// If this is a directory
if (EntryInfo.bIsDirectory)
{
FPaths::NormalizeDirectoryName(FilePath);
if (FPaths::DirectoryExists(FilePath))
{
if (!bForceOverwrite)
{
ReportError(ERuntimeArchiverErrorCode::ExtractError, FString::Printf(TEXT("Directory '%s' already exists"), *FilePath));
return false;
}
UE_LOG(LogRuntimeArchiver, Warning, TEXT("Directory '%s' already exists. It will be overwritten"), *FilePath);
}
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
// Ensure we have a valid directory to extract entry to
{
const FString DirectoryPath = FPaths::GetPath(FilePath);
if (!PlatformFile.CreateDirectoryTree(*DirectoryPath))
{
ReportError(ERuntimeArchiverErrorCode::ExtractError, FString::Printf(TEXT("Unable to create subdirectory '%s' to extract entry '%s'"), *DirectoryPath, *EntryInfo.Name));
return false;
}
}
if (!PlatformFile.CreateDirectory(*FilePath))
{
ReportError(ERuntimeArchiverErrorCode::ExtractError, FString::Printf(TEXT("Unable to extract entry '%s' to directory '%s'"), *EntryInfo.Name, *FilePath));
return false;
}
}
// If this is a file
else
{
FPaths::NormalizeFilename(FilePath);
if (FPaths::FileExists(FilePath))
{
if (!bForceOverwrite)
{
ReportError(ERuntimeArchiverErrorCode::ExtractError, FString::Printf(TEXT("File '%s' already exists"), *FilePath));
return false;
}
UE_LOG(LogRuntimeArchiver, Warning, TEXT("File '%s' already exists. It will be overwritten"), *FilePath);
}
TArray64<uint8> EntryData;
if (!ExtractEntryToMemory(EntryInfo, EntryData))
{
ReportError(ERuntimeArchiverErrorCode::ExtractError, FString::Printf(TEXT("Unable to extract the entry '%s' from archive to memory for file '%s'"), *EntryInfo.Name, *FilePath));
return false;
}
if (!FFileHelper::SaveArrayToFile(EntryData, *FilePath))
{
ReportError(ERuntimeArchiverErrorCode::ExtractError, FString::Printf(TEXT("Unable to save the entry '%s' from memory to file '%s'"), *EntryInfo.Name, *FilePath));
return false;
}
UE_LOG(LogRuntimeArchiver, Log, TEXT("Successfully extracted entry '%s' to file '%s'"), *EntryInfo.Name, *FilePath);
}
return true;
}
void URuntimeArchiverBase::ExtractEntriesToStorage(const FRuntimeArchiverAsyncOperationResult& OnResult, const FRuntimeArchiverAsyncOperationProgress& OnProgress, TArray<FRuntimeArchiveEntry> EntryInfo, FString DirectoryPath, bool bForceOverwrite)
{
if (!IsInitialized())
{
ReportError(ERuntimeArchiverErrorCode::NotInitialized, TEXT("Archiver is not initialized"));
OnResult.ExecuteIfBound(false);
return;
}
FPaths::NormalizeDirectoryName(DirectoryPath);
AsyncTask(ENamedThreads::AnyBackgroundHiPriTask, [WeakThis = MakeWeakObjectPtr(this), OnResult, OnProgress, EntryInfo = MoveTemp(EntryInfo), DirectoryPath = MoveTemp(DirectoryPath), bForceOverwrite]()
{
if (!WeakThis.IsValid())
{
UE_LOG(LogRuntimeArchiver, Error, TEXT("Failed to extract entries to storage: archiver is no longer valid"));
return;
}
auto ExecuteResult = [OnResult](bool bResult)
{
AsyncTask(ENamedThreads::GameThread, [OnResult, bResult]()
{
OnResult.ExecuteIfBound(bResult);
});
};
auto ExecuteProgress = [OnProgress](int32 Percentage)
{
AsyncTask(ENamedThreads::GameThread, [OnProgress, Percentage]()
{
OnProgress.ExecuteIfBound(Percentage);
});
};
for (int32 EntryIndex = 0; EntryIndex < EntryInfo.Num(); ++EntryIndex)
{
const FRuntimeArchiveEntry& Entry = EntryInfo[EntryIndex];
const FString ExtractFilePath = [&Entry]()
{
FString FilePath = Entry.Name;
FPaths::NormalizeDirectoryName(FilePath);
return FilePath;
}();
if (!WeakThis->ExtractEntryToStorage(Entry, FPaths::Combine(DirectoryPath, TEXT("/"), ExtractFilePath), bForceOverwrite))
{
WeakThis->ReportError(ERuntimeArchiverErrorCode::AddError, FString::Printf(TEXT("Cannot extract '%s' entry. Aborting async extracting entries"), *Entry.Name));
ExecuteResult(false);
return;
}
ExecuteProgress(static_cast<float>(EntryIndex + 1) / EntryInfo.Num() * 100);
}
UE_LOG(LogRuntimeArchiver, Log, TEXT("Successfully extracted '%d' entries"), EntryInfo.Num());
ExecuteResult(true);
});
}
namespace
{
/**
* Check whether the entry name belongs to the base name. For example, the entry name "SubFolder/File.txt" belongs to the base name "SubFolder", but the entry name "SubFolderNew/File.txt" does not belong to the base name "SubFolder"
*/
bool CheckEntryNameBelongsToBaseName(const FString& BaseName, const FString& EntryName)
{
int32 BaseNameIndex, EntryNameIndex;
for (BaseNameIndex = EntryNameIndex = 0; BaseNameIndex < BaseName.Len() && EntryNameIndex < EntryName.Len(); ++BaseNameIndex, ++EntryNameIndex)
{
const TCHAR& BaseNameCharacter = BaseName[BaseNameIndex];
const TCHAR& EntryNameCharacter = EntryName[EntryNameIndex];
if (BaseNameCharacter != EntryNameCharacter)
{
return false;
}
if (BaseNameIndex == BaseName.Len() - 1 &&
EntryNameIndex + 1 < EntryName.Len() && EntryName[EntryNameIndex + 1] == TEXT('/'))
{
return true;
}
}
return false;
}
}
void URuntimeArchiverBase::ExtractEntriesToStorage_Directory(const FRuntimeArchiverAsyncOperationResult& OnResult, FString EntryName, FString DirectoryPath, bool bAddParentDirectory, bool bForceOverwrite)
{
if (!IsInitialized())
{
ReportError(ERuntimeArchiverErrorCode::NotInitialized, TEXT("Archiver is not initialized"));
OnResult.ExecuteIfBound(false);
return;
}
int32 NumOfEntries;
if (!GetArchiveEntries(NumOfEntries))
{
ReportError(ERuntimeArchiverErrorCode::GetError, TEXT("Cannot get the number of archive entries. Aborting recursive extracting entries"));
OnResult.ExecuteIfBound(false);
return;
}
FPaths::NormalizeDirectoryName(EntryName);
FPaths::NormalizeDirectoryName(DirectoryPath);
// Composing the base path to have the correct directory path
// For example, if we scanned the "Folder" directory and found "Folder/File.ogg" entry name, then the directory file system path will be either "Folder/File.ogg" or "File.ogg" (depending on bAddParentDirectory)
const FString BaseDirectoryPathToExclude = [&EntryName, bAddParentDirectory]()
{
FString BasePath{FPaths::GetPath(EntryName)};
if (!BasePath.IsEmpty())
{
if (!bAddParentDirectory)
{
BasePath += TEXT("/") + FPaths::GetCleanFilename(EntryName);
}
}
return BasePath;
}();
AsyncTask(ENamedThreads::AnyBackgroundHiPriTask, [WeakThis = MakeWeakObjectPtr(this), OnResult, NumOfEntries, EntryName, DirectoryPath, BaseDirectoryPathToExclude, bForceOverwrite]()
{
if (!WeakThis.IsValid())
{
UE_LOG(LogRuntimeArchiver, Error, TEXT("Failed to extract entries to storage: archiver is no longer valid"));
return;
}
bool bResult = true;
for (int32 EntryIndex = 0; EntryIndex < NumOfEntries; ++EntryIndex)
{
FRuntimeArchiveEntry ArchiveEntry;
if (!WeakThis->GetArchiveEntryInfoByIndex(EntryIndex, ArchiveEntry))
{
WeakThis->ReportError(ERuntimeArchiverErrorCode::AddError, FString::Printf(TEXT("Cannot get '%d' entry to extract. Aborting recursive extracting entries"), EntryIndex));
bResult = false;
break;
}
if (EntryName.IsEmpty() || CheckEntryNameBelongsToBaseName(EntryName, ArchiveEntry.Name))
{
// Get the file path by truncating the base directory from the found entry
const FString SpecificFilePath = FPaths::Combine(DirectoryPath, ArchiveEntry.Name.RightChop(BaseDirectoryPathToExclude.Len()));
if (!WeakThis->ExtractEntryToStorage(ArchiveEntry, SpecificFilePath, bForceOverwrite))
{
bResult = false;
break;
}
}
}
if (bResult)
{
UE_LOG(LogRuntimeArchiver, Log, TEXT("Successfully extracted entries from '%s'"), *EntryName);
}
AsyncTask(ENamedThreads::GameThread, [OnResult, bResult]()
{
OnResult.ExecuteIfBound(bResult);
});
});
}
bool URuntimeArchiverBase::ExtractEntryToMemory(const FRuntimeArchiveEntry& EntryInfo, TArray<uint8>& UnarchivedData)
{
TArray64<uint8> UnarchivedData64;
if (!ExtractEntryToMemory(EntryInfo, UnarchivedData64))
{
return false;
}
if (UnarchivedData64.Num() > TNumericLimits<TArray<uint8>::SizeType>::Max())
{
ReportError(ERuntimeArchiverErrorCode::ExtractError, FString::Printf(TEXT("Array with int32 size (max length: %d) cannot fit int64 size data (retrieved length: %lld)\nA standard byte array can hold a maximum of 2 GB of data"), static_cast<int32>(TNumericLimits<TArray<uint8>::SizeType>::Max()), UnarchivedData64.Num()));
return false;
}
UnarchivedData = TArray<uint8>(MoveTemp(UnarchivedData64));
return true;
}
bool URuntimeArchiverBase::ExtractEntryToMemory(const FRuntimeArchiveEntry& EntryInfo, TArray64<uint8>& UnarchivedData)
{
if (!IsInitialized())
{
ReportError(ERuntimeArchiverErrorCode::NotInitialized, TEXT("Archiver is not initialized"));
return false;
}
if (Mode != ERuntimeArchiverMode::Read)
{
ReportError(ERuntimeArchiverErrorCode::UnsupportedMode, FString::Printf(TEXT("Only '%s' mode is supported for extracting the entry (using mode: '%s')"), *UEnum::GetValueAsName(ERuntimeArchiverMode::Read).ToString(), *UEnum::GetValueAsName(Mode).ToString()));
return false;
}
if (EntryInfo.bIsDirectory)
{
ReportError(ERuntimeArchiverErrorCode::UnsupportedLocation, TEXT("It is impossible to extract directory entry into memory"));
return false;
}
return true;
}
bool URuntimeArchiverBase::Initialize()
{
return true;
}
bool URuntimeArchiverBase::IsInitialized() const
{
return Mode != ERuntimeArchiverMode::Undefined && Location != ERuntimeArchiverLocation::Undefined;
}
void URuntimeArchiverBase::Reset()
{
Mode = ERuntimeArchiverMode::Undefined;
Location = ERuntimeArchiverLocation::Undefined;
}
void URuntimeArchiverBase::ReportError(ERuntimeArchiverErrorCode ErrorCode, const FString& ErrorString) const
{
// Making sure we are in the game thread
if (!IsInGameThread())
{
AsyncTask(ENamedThreads::GameThread, [WeakThis = MakeWeakObjectPtr(this), ErrorCode, ErrorString]() { if (WeakThis.IsValid()) WeakThis->URuntimeArchiverBase::ReportError(ErrorCode, ErrorString); });
return;
}
if (const URuntimeArchiverSubsystem* ArchiveSubsystem = URuntimeArchiverSubsystem::GetArchiveSubsystem())
{
if (ArchiveSubsystem->OnError.IsBound())
{
ArchiveSubsystem->OnError.Broadcast(ErrorCode, ErrorString);
}
}
UE_LOG(LogRuntimeArchiver, Error, TEXT("%s"), *ErrorString);
}