October3d55/Matain/ZipItf49cfde1366dV11/Source/ZipIt/Private/Unzipper.cpp

339 lines
9.0 KiB
C++

// Copyright Pandores Marketplace 2024. All Rights Reserved.
#include "Unzipper.h"
#include "ZipIt.h"
#include "HAL/FileManager.h"
#include "Misc/Paths.h"
#include "Async/Async.h"
#include "Async/TaskGraphInterfaces.h"
#include "HAL/PlatformFilemanager.h"
#include "Misc/FileHelper.h"
THIRD_PARTY_INCLUDES_START
#pragma warning( push )
#pragma warning( disable : 4668)
# if PLATFORM_WINDOWS
# include "Windows/AllowWindowsPlatformTypes.h"
# include "MinZip/iowin32.h"
# include "Windows/HideWindowsPlatformTypes.h"
# endif
# include "MinZip/unzip.h"
#pragma warning( pop )
THIRD_PARTY_INCLUDES_END
UUnzipper* UUnzipper::CreateUnzipper()
{
return NewObject<UUnzipper>();
}
UUnzipper::UUnzipper()
: BufferSize(8192)
{}
void UUnzipper::SetBufferSize(const int64& NewBufferSize)
{
ensureMsgf(NewBufferSize > 0, TEXT("Buffer Size must be bigger than 0."));
BufferSize = NewBufferSize;
}
void UUnzipper::SetArchive(FString Path)
{
ArchivePath = FPaths::ConvertRelativePathToFull(MoveTemp(Path));
}
bool UUnzipper::IsValid() const
{
return FPaths::FileExists(ArchivePath);
}
bool UUnzipper::UnzipAll(const FString& TargetLocation, const FString& Password, const bool bForce)
{
const auto Callback = [](void* Data, const int64 FileIndex, const int64 TotalFileCount, const FString& ArchiveLocation, const FString& DiskLocation) -> void
{
UUnzipper* const This = (UUnzipper*)Data;
This->OnFileUnzipCompleted(FileIndex, TotalFileCount, ArchiveLocation, DiskLocation);
};
const int64 bUnzipSuccess = UnzipAllInternal(TargetLocation, ArchivePath, Password, bForce, BufferSize, Callback, this);
OnUnzipCompleted(bUnzipSuccess, (bool)bUnzipSuccess);
return (bool)bUnzipSuccess;
}
void UUnzipper::UnzipAllAsync(FString TargetLocation, FString Password, const bool bForce)
{
if (!IsValid())
{
UE_LOG(LogZipIt, Error, TEXT("File \"%s\" doesn't exist."), *ArchivePath);
OnUnzipCompleted(0, false);
return;
}
AddToRoot();
FString Archive = FPaths::ConvertRelativePathToFull(ArchivePath);
const int64 Buffer = BufferSize;
const bool bCallFileCallback = OnFileUnzippedDynamicEvent.IsBound() || OnFileUnzippedEvent.IsBound();
TWeakObjectPtr<UUnzipper> This = TWeakObjectPtr<UUnzipper>(this);
AsyncTask(ENamedThreads::AnyThread, [This, TargetLocation = MoveTemp(TargetLocation),
Archive = MoveTemp(Archive), Password= MoveTemp(Password), bForce, Buffer, bCallFileCallback]() -> void
{
auto FileUnzipedCallback = [](void* Data, const int64 FileIndex, const int64 TotalFileCount, const FString& ArchiveLocation, const FString& DiskLocation) -> void
{
check(!IsInGameThread());
check(Data != nullptr);
TWeakObjectPtr<UUnzipper>& Self = *(TWeakObjectPtr<UUnzipper>*)Data;
AsyncTask(ENamedThreads::GameThread, [Self, ArchiveLocation, DiskLocation, TotalFileCount, FileIndex]() -> void
{
if (Self.IsValid())
{
Self->OnFileUnzipCompleted(FileIndex, TotalFileCount, ArchiveLocation, DiskLocation);
}
});
};
const int64 bUnzipSuccess =
bCallFileCallback ?
UnzipAllInternal(TargetLocation, Archive, Password, bForce, Buffer, FileUnzipedCallback, (void*)&This) :
UnzipAllInternal(TargetLocation, Archive, Password, bForce, Buffer, nullptr, nullptr);
AsyncTask(ENamedThreads::GameThread, [This, bUnzipSuccess]() -> void
{
if (This.IsValid())
{
This->OnUnzipCompleted(bUnzipSuccess, (bool)bUnzipSuccess);
This->RemoveFromRoot();
}
else
{
UE_LOG(LogZipIt, Error, TEXT("Unzipper got garbage collected while unzipping."));
}
});
});
}
int64 UUnzipper::UnzipAllInternal(const FString& TargetLocation, const FString& ArchivePath, const FString& Password, const bool bForce, const int64 BufferSize, FOnFileUnzippedPtr Callback, void* CallbackData)
{
#if PLATFORM_WINDOWS
zlib_filefunc64_def Ffunc;
fill_win32_filefunc64W(&Ffunc);
unzFile File = unzOpen2_64(TCHAR_TO_WCHAR(*ArchivePath), &Ffunc);
#elif PLATFORM_ANDROID || PLATFORM_IOS
unzFile File = unzOpen64(TCHAR_TO_UTF8(*ArchivePath));
if (!File)
{
File = unzOpen64(TCHAR_TO_WCHAR(*ArchivePath));
}
#else
unzFile File = unzOpen64(TCHAR_TO_WCHAR(*ArchivePath));
if (!File)
{
File = unzOpen64(TCHAR_TO_UTF8(*ArchivePath));
}
#endif
if (File == NULL)
{
UE_LOG(LogZipIt, Error, TEXT("Failed to open file \"%s\": unzOpen64 returned NULL. Absolute path is \"%s\". UTF8 path is \"%s\" (an invalid path here means unsupported characters)."),
*ArchivePath, *FPaths::ConvertRelativePathToFull(ArchivePath), UTF8_TO_TCHAR(TCHAR_TO_UTF8(*ArchivePath)));
return 0;
}
unz_global_info64 GlobalInfo;
{
const int32 Error = unzGetGlobalInfo64(File, &GlobalInfo);
if (Error != UNZ_OK)
{
UE_LOG(LogZipIt, Error, TEXT("Failed to get file \"%s\" info. Error code: %d."), *ArchivePath, Error);
return 0;
}
}
UE_LOG(LogZipIt, Log, TEXT("Archive contains %d file(s)."), GlobalInfo.number_entry);
const int64 FileCount = (int64)GlobalInfo.number_entry;
void* Buffer = FMemory::Malloc(BufferSize);
const bool bUnzipSuccess = ([&]() -> bool
{
for (int64 i = 0; i < FileCount; ++i)
{
if (!UnzipCurrentFile(i + 1, FileCount, TargetLocation, Password, bForce, File, Buffer, BufferSize, Callback, CallbackData))
{
return false;
}
if (i < FileCount - 1)
{
const int32 Error = unzGoToNextFile(File);
if (Error != UNZ_OK)
{
UE_LOG(LogZipIt, Error, TEXT("Failed to read archive's next file. Code: %d."), Error);
return false;
}
}
}
return true;
})();
FMemory::Free(Buffer);
unzClose(File);
if (bUnzipSuccess)
{
UE_LOG(LogZipIt, Log, TEXT("Zip file \"%s\" extracted to \"%s\""), *ArchivePath, *TargetLocation);
}
else
{
UE_LOG(LogZipIt, Error, TEXT("Failed to unzip file \"%s\"."), *ArchivePath);
return 0;
}
return FileCount;
}
bool UUnzipper::UnzipCurrentFile(const int64 FileIndex, const int64 TotalFileCount, const FString& TargetLocation, const FString& ArchivePassword, const bool bForceUnzip, void* const File, void* Buffer, const int64 InBufferSize, FOnFileUnzippedPtr Callback, void* CallbackData)
{
unz_file_info64 FileInfo;
FString FileName;
{
constexpr int32 FileMaxSize = 512;
char RawFileName[FileMaxSize];
const int32 Error = unzGetCurrentFileInfo64(File, &FileInfo, RawFileName, FileMaxSize, nullptr, 0, nullptr, 0);
if (Error != UNZ_OK)
{
UE_LOG(LogZipIt, Warning, TEXT("Failed to get current file info. Code: %d."), Error);
return false;
}
FileName = UTF8_TO_TCHAR(RawFileName);
}
const FString CleanFileName = FPaths::GetCleanFilename(FileName);
if (CleanFileName.IsEmpty())
{
FPlatformFileManager::Get().GetPlatformFile().CreateDirectory(*(TargetLocation / FileName));
return true;
}
const FString WriteLocation = TargetLocation / FileName;
{
const int32 Error = ArchivePassword.IsEmpty() ? unzOpenCurrentFile(File) : unzOpenCurrentFilePassword(File, TCHAR_TO_UTF8(*ArchivePassword));
if (Error != UNZ_OK)
{
UE_LOG(LogZipIt, Warning, TEXT("Failed to open file \"%s\", with password: %d. Code: %d."), *FileName, ArchivePassword.IsEmpty() ? 0 : 1, Error);
return false;
}
}
if (FPaths::FileExists(WriteLocation))
{
if (bForceUnzip)
{
FPlatformFileManager::Get().GetPlatformFile().DeleteFile(*WriteLocation);
}
else
{
UE_LOG(LogZipIt, Warning, TEXT("File \"%s\" already exists. Skipping extraction. Use bForce = true to overwrite existing files."), *WriteLocation);
return true;
}
}
int32 Error = 1;
{
TUniquePtr<FArchive> Ar = TUniquePtr<FArchive>(IFileManager::Get().CreateFileWriter(*WriteLocation));
if (!Ar)
{
Error = -254;
UE_LOG(LogZipIt, Error, TEXT("Failed to create archive to write to \"%s\""), *WriteLocation);
}
while (Error > 0)
{
Error = unzReadCurrentFile(File, Buffer, InBufferSize);
if (Error < 0)
{
break;
}
if (Error > 0)
{
Ar->Serialize(Buffer, Error);
}
}
if (Ar)
{
Ar->Close();
}
if (Error != UNZ_OK)
{
UE_LOG(LogZipIt, Warning, TEXT("Error while reading file \"%s\". Code: %d."), *FileName, Error);
}
}
{
const int32 CloseError = unzCloseCurrentFile(File);
if (CloseError != UNZ_OK)
{
UE_LOG(LogZipIt, Warning, TEXT("Failed to close file \"%s\". Code: %d."), *FileName, CloseError);
}
}
if (Callback)
{
Callback(CallbackData, FileIndex, TotalFileCount, FileName, WriteLocation);
}
return Error == UNZ_OK;
}
void UUnzipper::OnUnzipCompleted(const int64 TotalFileCount, const bool bSuccess)
{
check(IsInGameThread());
if (IsRooted())
{
RemoveFromRoot();
}
OnFilesUnzippedEvent.Broadcast(bSuccess, TotalFileCount);
OnFilesUnzippedDynamicEvent.Broadcast(bSuccess, TotalFileCount);
}
void UUnzipper::OnFileUnzipCompleted(const int64 FileIndex, const int64 TotalFileCount, const FString& ArchiveLocation, const FString& DiskLocation)
{
check(IsInGameThread());
OnFileUnzippedEvent.Broadcast(ArchiveLocation, DiskLocation, FileIndex, TotalFileCount);
OnFileUnzippedDynamicEvent.Broadcast(ArchiveLocation, DiskLocation, FileIndex, TotalFileCount);
}
FOnFileUnzipped& UUnzipper::OnFileUnzipped()
{
return OnFileUnzippedEvent;
}
FOnFilesUnzipped& UUnzipper::OnFilesUnzipped()
{
return OnFilesUnzippedEvent;
}