From 825bd1275bc26a8532f07a887db5141cd635df13 Mon Sep 17 00:00:00 2001 From: Kenix3 Date: Thu, 8 Feb 2024 17:50:08 -0500 Subject: [PATCH] Refactors Archive class to have an ArchiveManager (#422) * Removes Pulse Audio. Config values will be migrated to sdl audio backend. #311 * Removes PulseAudio.h from classes.h * Refactors archives to have a separate manager class. * Run clang-format * Includes string in ResourceType.h * Only include tchar.h in Context.cpp when building for Windows. * Includes vector in ArchiveManager.h * fix: put `SDL_WndProc` behind `_WIN32` ifdef (#423) * Fix issues with Archive refactor (#424) * get in game * clang format * windows build * pr comments * return false * uncomment * fix fonts * pr comment * init dadon't * raw * Fixes issue where ArchiveManager never unloads Archives. * Fixes builds for StormLib issues. (#425) * this is a stupid hack but i'm too tired to keep trying to figure out how we're linking everything * link? * ports... * windows? --------- Co-authored-by: briaguya <70942617+briaguya-ai@users.noreply.github.com> --- include/libultraship/classes.h | 5 +- src/CMakeLists.txt | 24 +- src/Context.cpp | 14 +- src/graphic/Fast3D/gfx_sdl2.cpp | 3 + src/public/bridge/resourcebridge.cpp | 7 +- src/public/bridge/resourcebridge.h | 1 - src/resource/Archive.cpp | 547 ------------------------ src/resource/Archive.h | 67 --- src/resource/File.h | 20 +- src/resource/GameVersions.h | 2 +- src/resource/Resource.cpp | 1 - src/resource/Resource.h | 13 +- src/resource/ResourceFactory.h | 1 + src/resource/ResourceLoader.cpp | 123 ++---- src/resource/ResourceLoader.h | 8 +- src/resource/ResourceManager.cpp | 58 +-- src/resource/ResourceManager.h | 19 +- src/resource/ResourceType.h | 29 ++ src/resource/archive/Archive.cpp | 242 +++++++++++ src/resource/archive/Archive.h | 63 +++ src/resource/archive/ArchiveManager.cpp | 225 ++++++++++ src/resource/archive/ArchiveManager.h | 50 +++ src/resource/archive/O2rArchive.cpp | 38 ++ src/resource/archive/O2rArchive.h | 32 ++ src/resource/archive/OtrArchive.cpp | 105 +++++ src/resource/archive/OtrArchive.h | 39 ++ src/resource/factory/TextureFactory.cpp | 18 +- src/utils/binarytools/MemoryStream.cpp | 32 +- src/utils/binarytools/MemoryStream.h | 3 +- src/window/gui/GameOverlay.cpp | 11 +- src/window/gui/Gui.cpp | 2 +- 31 files changed, 990 insertions(+), 812 deletions(-) delete mode 100644 src/resource/Archive.cpp delete mode 100644 src/resource/Archive.h create mode 100644 src/resource/archive/Archive.cpp create mode 100644 src/resource/archive/Archive.h create mode 100644 src/resource/archive/ArchiveManager.cpp create mode 100644 src/resource/archive/ArchiveManager.h create mode 100644 src/resource/archive/O2rArchive.cpp create mode 100644 src/resource/archive/O2rArchive.h create mode 100644 src/resource/archive/OtrArchive.cpp create mode 100644 src/resource/archive/OtrArchive.h diff --git a/include/libultraship/classes.h b/include/libultraship/classes.h index 805d68134..7d444f05c 100644 --- a/include/libultraship/classes.h +++ b/include/libultraship/classes.h @@ -4,7 +4,10 @@ #ifndef _LIBULTRASHIP_CLASSES_H #define _LIBULTRASHIP_CLASSES_H -#include "resource/Archive.h" +#include "resource/archive/ArchiveManager.h" +#include "resource/archive/Archive.h" +#include "resource/archive/OtrArchive.h" +#include "resource/archive/O2rArchive.h" #include "resource/ResourceManager.h" #include "Context.h" #include "window/Window.h" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 98025856e..15b724dcb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -316,8 +316,6 @@ target_sources(libultraship PRIVATE ${Source_Files__Port}) #=================== Resource =================== set(Source_Files__Resource - ${CMAKE_CURRENT_SOURCE_DIR}/resource/Archive.h - ${CMAKE_CURRENT_SOURCE_DIR}/resource/Archive.cpp ${CMAKE_CURRENT_SOURCE_DIR}/resource/File.h ${CMAKE_CURRENT_SOURCE_DIR}/resource/Resource.h ${CMAKE_CURRENT_SOURCE_DIR}/resource/ResourceType.h @@ -362,7 +360,20 @@ set(Source_Files__Resource__Factories ${CMAKE_CURRENT_SOURCE_DIR}/resource/factory/VertexFactory.h ) source_group("resource/factory" FILES ${Source_Files__Resource__Factories}) -target_sources(libultraship PRIVATE ${Source_Files__Resource} ${Source_Files__Resource__Types} ${Source_Files__Resource__Factories}) + +set(Source_Files__Resource__Archive + ${CMAKE_CURRENT_SOURCE_DIR}/resource/archive/Archive.h + ${CMAKE_CURRENT_SOURCE_DIR}/resource/archive/Archive.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/resource/archive/OtrArchive.h + ${CMAKE_CURRENT_SOURCE_DIR}/resource/archive/OtrArchive.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/resource/archive/O2rArchive.h + ${CMAKE_CURRENT_SOURCE_DIR}/resource/archive/O2rArchive.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/resource/archive/ArchiveManager.h + ${CMAKE_CURRENT_SOURCE_DIR}/resource/archive/ArchiveManager.cpp +) +source_group("resource/archive" FILES ${Source_Files__Resource__Archive}) + +target_sources(libultraship PRIVATE ${Source_Files__Resource} ${Source_Files__Resource__Types} ${Source_Files__Resource__Factories} ${Source_Files__Resource__Archive}) #=================== Graphic =================== @@ -446,9 +457,14 @@ endif() #=================== Linking =================== +if (NOT CMAKE_SYSTEM_NAME STREQUAL "CafeOS" AND NOT CMAKE_SYSTEM_NAME STREQUAL "NintendoSwitch" AND NOT CMAKE_SYSTEM_NAME STREQUAL "Windows") + target_link_libraries(libultraship PUBLIC "$") +else() + target_link_libraries(libultraship PUBLIC storm) +endif() target_link_libraries(libultraship PRIVATE StrHash64 - PUBLIC ZAPDUtils ImGui storm tinyxml2 nlohmann_json::nlohmann_json + PUBLIC ZAPDUtils ImGui tinyxml2 nlohmann_json::nlohmann_json ) if (CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR CMAKE_SYSTEM_NAME STREQUAL "NintendoSwitch") diff --git a/src/Context.cpp b/src/Context.cpp index ad2e2d4ee..960a92bbf 100644 --- a/src/Context.cpp +++ b/src/Context.cpp @@ -6,6 +6,10 @@ #include #include "install_config.h" +#ifdef _WIN32 +#include +#endif + #ifdef __APPLE__ #include "utils/OSXFolderManager.h" #elif defined(__SWITCH__) @@ -190,9 +194,15 @@ void Context::InitResourceManager(const std::vector& otrFiles, mMainPath = GetConfig()->GetString("Game.Main Archive", GetAppDirectoryPath()); mPatchesPath = GetConfig()->GetString("Game.Patches Archive", GetAppDirectoryPath() + "/mods"); if (otrFiles.empty()) { - mResourceManager = std::make_shared(mMainPath, mPatchesPath, validHashes, reservedThreadCount); + std::vector paths = std::vector(); + paths.push_back(mMainPath); + paths.push_back(mPatchesPath); + + mResourceManager = std::make_shared(); + GetResourceManager()->Init(paths, validHashes, reservedThreadCount); } else { - mResourceManager = std::make_shared(otrFiles, validHashes, reservedThreadCount); + mResourceManager = std::make_shared(); + GetResourceManager()->Init(otrFiles, validHashes, reservedThreadCount); } if (!GetResourceManager()->DidLoadSuccessfully()) { diff --git a/src/graphic/Fast3D/gfx_sdl2.cpp b/src/graphic/Fast3D/gfx_sdl2.cpp index 852608096..9180e20ca 100644 --- a/src/graphic/Fast3D/gfx_sdl2.cpp +++ b/src/graphic/Fast3D/gfx_sdl2.cpp @@ -62,7 +62,10 @@ static void (*on_fullscreen_changed_callback)(bool is_now_fullscreen); static bool (*on_key_down_callback)(int scancode); static bool (*on_key_up_callback)(int scancode); static void (*on_all_keys_up_callback)(void); + +#ifdef _WIN32 LONG_PTR SDL_WndProc; +#endif const SDL_Scancode lus_to_sdl_table[] = { SDL_SCANCODE_UNKNOWN, diff --git a/src/public/bridge/resourcebridge.cpp b/src/public/bridge/resourcebridge.cpp index 83d41485c..09a85f145 100644 --- a/src/public/bridge/resourcebridge.cpp +++ b/src/public/bridge/resourcebridge.cpp @@ -26,7 +26,8 @@ uint64_t ResourceGetCrcByName(const char* name) { } const char* ResourceGetNameByCrc(uint64_t crc) { - const std::string* hashStr = LUS::Context::GetInstance()->GetResourceManager()->GetArchive()->HashToString(crc); + const std::string* hashStr = + LUS::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->HashToString(crc); return hashStr != nullptr ? hashStr->c_str() : nullptr; } @@ -146,7 +147,7 @@ size_t ResourceGetTexSizeByCrc(uint64_t crc) { } void ResourceGetGameVersions(uint32_t* versions, size_t versionsSize, size_t* versionsCount) { - auto list = LUS::Context::GetInstance()->GetResourceManager()->GetArchive()->GetGameVersions(); + auto list = LUS::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->GetGameVersions(); memcpy(versions, list.data(), std::min(versionsSize, list.size() * sizeof(uint32_t))); *versionsCount = list.size(); } @@ -156,7 +157,7 @@ void ResourceLoadDirectoryAsync(const char* name) { } uint32_t ResourceHasGameVersion(uint32_t hash) { - auto list = LUS::Context::GetInstance()->GetResourceManager()->GetArchive()->GetGameVersions(); + auto list = LUS::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->GetGameVersions(); return std::find(list.begin(), list.end(), hash) != list.end(); } diff --git a/src/public/bridge/resourcebridge.h b/src/public/bridge/resourcebridge.h index b822ea9df..811a08e75 100644 --- a/src/public/bridge/resourcebridge.h +++ b/src/public/bridge/resourcebridge.h @@ -6,7 +6,6 @@ #include "stdint.h" #ifdef __cplusplus -#include "resource/Archive.h" #include "resource/type/Texture.h" #include "resource/Resource.h" diff --git a/src/resource/Archive.cpp b/src/resource/Archive.cpp deleted file mode 100644 index 81a585222..000000000 --- a/src/resource/Archive.cpp +++ /dev/null @@ -1,547 +0,0 @@ -#include "Archive.h" -#include "Resource.h" -#include "File.h" -#include "window/Window.h" -#include "resource/ResourceManager.h" -#include "Utils/StringHelper.h" -#include -#include -#include "utils/binarytools/MemoryStream.h" -#include "utils/binarytools/FileHelper.h" - -#ifdef __SWITCH__ -#include "port/switch/SwitchImpl.h" -#endif - -namespace LUS { -Archive::Archive(const std::string& mainPath, bool enableWriting) - : Archive(mainPath, "", std::unordered_set(), enableWriting) { - mMainMpq = nullptr; -} - -Archive::Archive(const std::string& mainPath, const std::string& patchesPath, - const std::unordered_set& validHashes, bool enableWriting, bool generateCrcMap) - : mMainPath(mainPath), mPatchesPath(patchesPath), mOtrArchives({}), mValidHashes(validHashes) { - mMainMpq = nullptr; - Load(enableWriting, generateCrcMap); -} - -Archive::Archive(const std::vector& fileList, const std::unordered_set& validHashes, - bool enableWriting, bool generateCrcMap) - : mOtrArchives(fileList), mValidHashes(validHashes) { - mMainMpq = nullptr; - Load(enableWriting, generateCrcMap); -} - -Archive::~Archive() { - Unload(); -} - -bool Archive::IsMainMPQValid() { - return mMainMpq != nullptr; -} - -std::shared_ptr Archive::CreateArchive(const std::string& archivePath, size_t fileCapacity) { - auto archive = std::make_shared(archivePath, true); - - TCHAR* fileName = new TCHAR[archivePath.size() + 1]; - fileName[archivePath.size()] = 0; - std::copy(archivePath.begin(), archivePath.end(), fileName); - - bool success; - { - const std::lock_guard lock(archive->mMutex); - success = SFileCreateArchive(fileName, MPQ_CREATE_LISTFILE | MPQ_CREATE_ATTRIBUTES | MPQ_CREATE_ARCHIVE_V2, - fileCapacity, &archive->mMainMpq); - } - int32_t error = GetLastError(); - - delete[] fileName; - - if (success) { - archive->mMpqHandles[archivePath] = archive->mMainMpq; - return archive; - } else { - SPDLOG_ERROR("({}) We tried to create an archive, but it has fallen and cannot get up.", error); - return nullptr; - } -} - -std::shared_ptr Archive::LoadFileFromHandle(const std::string& filePath, bool includeParent, HANDLE mpqHandle) { - HANDLE fileHandle = NULL; - - std::shared_ptr fileToLoad = std::make_shared(); - fileToLoad->Path = filePath; - - if (mpqHandle == nullptr) { - mpqHandle = mMainMpq; - } - -#if _DEBUG - if (FileHelper::Exists("TestData/" + filePath)) { - auto byteData = FileHelper::ReadAllBytes("TestData/" + filePath); - fileToLoad->Buffer.resize(byteData.size() + 1); - memcpy(fileToLoad->Buffer.data(), byteData.data(), byteData.size() + 1); - - // Throw in a null terminator at the end incase we're loading a text file... - fileToLoad->Buffer[byteData.size()] = '\0'; - - fileToLoad->Parent = includeParent ? shared_from_this() : nullptr; - fileToLoad->IsLoaded = true; - } else { -#endif - bool attempt; - { - const std::lock_guard lock(mMutex); - attempt = SFileOpenFileEx(mpqHandle, filePath.c_str(), 0, &fileHandle); - } - - if (!attempt) { - SPDLOG_TRACE("({}) Failed to open file {} from mpq archive {}.", GetLastError(), filePath, mMainPath); - return nullptr; - } - - DWORD fileSize; - { - const std::lock_guard lock(mMutex); - fileSize = SFileGetFileSize(fileHandle, 0); - } - fileToLoad->Buffer.resize(fileSize); - DWORD countBytes; - - bool readFileSuccess; - { - const std::lock_guard lock(mMutex); - readFileSuccess = SFileReadFile(fileHandle, fileToLoad->Buffer.data(), fileSize, &countBytes, NULL); - } - if (!readFileSuccess) { - SPDLOG_ERROR("({}) Failed to read file {} from mpq archive {}", GetLastError(), filePath, mMainPath); - bool closeFileSuccess; - { - const std::lock_guard lock(mMutex); - closeFileSuccess = SFileCloseFile(fileHandle); - } - if (!closeFileSuccess) { - SPDLOG_ERROR("({}) Failed to close file {} from mpq after read failure in archive {}", GetLastError(), - filePath, mMainPath); - } - return nullptr; - } - - bool closeFileSuccess; - { - const std::lock_guard lock(mMutex); - closeFileSuccess = SFileCloseFile(fileHandle); - } - if (!closeFileSuccess) { - SPDLOG_ERROR("({}) Failed to close file {} from mpq archive {}", GetLastError(), filePath, mMainPath); - } - - fileToLoad->Parent = includeParent ? shared_from_this() : nullptr; - fileToLoad->IsLoaded = true; -#if _DEBUG - } -#endif - - return fileToLoad; -} - -std::shared_ptr Archive::LoadFile(const std::string& filePath, bool includeParent) { - return LoadFileFromHandle(filePath, includeParent, nullptr); -} - -bool Archive::AddFile(const std::string& filePath, uintptr_t fileData, DWORD fileSize) { - HANDLE hFile; -#ifdef _WIN32 - SYSTEMTIME sysTime; - GetSystemTime(&sysTime); - FILETIME t; - SystemTimeToFileTime(&sysTime, &t); - ULONGLONG theTime = static_cast(t.dwHighDateTime) << (sizeof(t.dwHighDateTime) * 8) | t.dwLowDateTime; -#else - time_t theTime; - time(&theTime); -#endif - - std::string updatedPath = filePath; - - StringHelper::ReplaceOriginal(updatedPath, "\\", "/"); - - bool createFileSuccess; - { - const std::lock_guard lock(mMutex); - createFileSuccess = - SFileCreateFile(mMainMpq, updatedPath.c_str(), theTime, fileSize, 0, MPQ_FILE_COMPRESS, &hFile); - } - if (!createFileSuccess) { - SPDLOG_ERROR("({}) Failed to create file of {} bytes {} in archive {}", GetLastError(), fileSize, updatedPath, - mMainPath); - return false; - } - - bool writeFileSuccess; - { - const std::lock_guard lock(mMutex); - writeFileSuccess = SFileWriteFile(hFile, (void*)fileData, fileSize, MPQ_COMPRESSION_ZLIB); - } - if (!writeFileSuccess) { - SPDLOG_ERROR("({}) Failed to write {} bytes to {} in archive {}", GetLastError(), fileSize, updatedPath, - mMainPath); - bool closeFileSuccess; - { - const std::lock_guard lock(mMutex); - closeFileSuccess = SFileCloseFile(hFile); - } - if (!closeFileSuccess) { - SPDLOG_ERROR("({}) Failed to close file {} after write failure in archive {}", GetLastError(), updatedPath, - mMainPath); - } - return false; - } - - bool finishFileSuccess; - { - const std::lock_guard lock(mMutex); - finishFileSuccess = SFileFinishFile(hFile); - } - if (!finishFileSuccess) { - SPDLOG_ERROR("({}) Failed to finish file {} in archive {}", GetLastError(), updatedPath, mMainPath); - bool closeFileSuccess; - { - const std::lock_guard lock(mMutex); - closeFileSuccess = SFileCloseFile(hFile); - } - if (!closeFileSuccess) { - SPDLOG_ERROR("({}) Failed to close file {} after finish failure in archive {}", GetLastError(), updatedPath, - mMainPath); - } - return false; - } - // SFileFinishFile already frees the handle, so no need to close it again. - - mAddedFiles.push_back(updatedPath); - mHashes[CRC64(updatedPath.c_str())] = updatedPath; - - return true; -} - -bool Archive::RemoveFile(const std::string& filePath) { - // TODO: Notify the resource manager and child Files - - bool removeFileSuccess; - { - const std::lock_guard lock(mMutex); - removeFileSuccess = SFileRemoveFile(mMainMpq, filePath.c_str(), 0); - } - if (!removeFileSuccess) { - SPDLOG_ERROR("({}) Failed to remove file {} in archive {}", GetLastError(), filePath, mMainPath); - return false; - } - - return true; -} - -bool Archive::RenameFile(const std::string& oldFilePath, const std::string& newFilePath) { - // TODO: Notify the resource manager and child Files - - bool renameFileSuccess; - { - const std::lock_guard lock(mMutex); - renameFileSuccess = SFileRenameFile(mMainMpq, oldFilePath.c_str(), newFilePath.c_str()); - } - if (!renameFileSuccess) { - SPDLOG_ERROR("({}) Failed to rename file {} to {} in archive {}", GetLastError(), oldFilePath, newFilePath, - mMainPath); - return false; - } - - return true; -} - -std::shared_ptr> Archive::FindFiles(const std::string& fileSearchMask) { - auto fileList = std::make_shared>(); - SFILE_FIND_DATA findContext; - HANDLE hFind; - - { - const std::lock_guard lock(mMutex); - hFind = SFileFindFirstFile(mMainMpq, fileSearchMask.c_str(), &findContext, nullptr); - } - if (hFind != nullptr) { - fileList->push_back(findContext); - - bool fileFound; - do { - { - const std::lock_guard lock(mMutex); - fileFound = SFileFindNextFile(hFind, &findContext); - } - if (fileFound) { - fileList->push_back(findContext); - } else if (!fileFound && GetLastError() != ERROR_NO_MORE_FILES) { - SPDLOG_ERROR("({}), Failed to search with mask {} in archive {}", GetLastError(), fileSearchMask, - mMainPath); - if (!SListFileFindClose(hFind)) { - SPDLOG_ERROR("({}) Failed to close file search {} after failure in archive {}", GetLastError(), - fileSearchMask, mMainPath); - } - return fileList; - } - } while (fileFound); - } else if (GetLastError() != ERROR_NO_MORE_FILES) { - SPDLOG_ERROR("({}), Failed to search with mask {} in archive {}", GetLastError(), fileSearchMask, mMainPath); - return fileList; - } - - if (hFind != nullptr) { - bool fileFindCloseSuccess; - { - const std::lock_guard lock(mMutex); - fileFindCloseSuccess = SFileFindClose(hFind); - } - if (!fileFindCloseSuccess) { - SPDLOG_ERROR("({}) Failed to close file search {} in archive {}", GetLastError(), fileSearchMask, - mMainPath); - } - - return fileList; - } - - return fileList; -} - -std::shared_ptr> Archive::ListFiles(const std::string& fileSearchMask) { - auto result = std::make_shared>(); - auto fileList = FindFiles(fileSearchMask); - - for (size_t i = 0; i < fileList->size(); i++) { - result->push_back(fileList->operator[](i).cFileName); - } - - return result; -} - -bool Archive::HasFile(const std::string& fileSearchMask) { - auto list = FindFiles(fileSearchMask); - return list->size() > 0; -} - -const std::string* Archive::HashToString(uint64_t hash) const { - auto it = mHashes.find(hash); - return it != mHashes.end() ? &it->second : nullptr; -} - -bool Archive::Load(bool enableWriting, bool generateCrcMap) { - return LoadMainMPQ(enableWriting, generateCrcMap) && LoadPatchMPQs(); -} - -bool Archive::Unload() { - bool success = true; - for (const auto& mpqHandle : mMpqHandles) { - bool closeArchiveSuccess; - { - const std::lock_guard lock(mMutex); - closeArchiveSuccess = SFileCloseArchive(mpqHandle.second); - } - if (!closeArchiveSuccess) { - SPDLOG_ERROR("({}) Failed to close mpq {}", GetLastError(), mpqHandle.first); - success = false; - } - } - - mMainMpq = nullptr; - - return success; -} - -bool Archive::LoadPatchMPQs() { - // OTRTODO: We also want to periodically scan the patch directories for new MPQs. When new MPQs are found we will - // load the contents to fileCache and then copy over to gameResourceAddresses - if (mPatchesPath.length() > 0) { - if (std::filesystem::is_directory(mPatchesPath)) { - for (const auto& p : std::filesystem::recursive_directory_iterator(mPatchesPath)) { - if (StringHelper::IEquals(p.path().extension().string(), ".otr") || - StringHelper::IEquals(p.path().extension().string(), ".mpq")) { - SPDLOG_ERROR("Reading {} mpq patch", p.path().string()); - if (!LoadPatchMPQ(p.path().string())) { - return false; - } - } - } - } - } - - return true; -} - -void Archive::GenerateCrcMap() { - auto listFile = LoadFile("(listfile)", false); - - // Use std::string_view to avoid unnecessary string copies - std::vector lines = - StringHelper::Split(std::string_view(listFile->Buffer.data(), listFile->Buffer.size()), "\n"); - - for (size_t i = 0; i < lines.size(); i++) { - // Use std::string_view to avoid unnecessary string copies - std::string_view line = lines[i].substr(0, lines[i].length() - 1); // Trim \r - std::string lineStr = std::string(line); - - // Not NULL terminated str - uint64_t hash = ~crc64(line.data(), line.length()); - mHashes.emplace(hash, std::move(lineStr)); - } -} - -bool Archive::ProcessOtrVersion(HANDLE mpqHandle) { - auto t = LoadFileFromHandle("version", false, mpqHandle); - if (t != nullptr && t->IsLoaded) { - auto stream = std::make_shared(t->Buffer.data(), t->Buffer.size()); - auto reader = std::make_shared(stream); - LUS::Endianness endianness = (LUS::Endianness)reader->ReadUByte(); - reader->SetEndianness(endianness); - uint32_t version = reader->ReadUInt32(); - // Game version found so track it if it matches or there is nothing to match against - if (mValidHashes.empty() || mValidHashes.contains(version)) { - PushGameVersion(version); - return true; - } - } - - // Allow the otr through if there are no valid hashes anyways - return mValidHashes.empty(); -} - -bool Archive::LoadMainMPQ(bool enableWriting, bool generateCrcMap) { - HANDLE mpqHandle = NULL; - if (mOtrArchives.empty()) { - if (mMainPath.length() > 0) { - if (std::filesystem::is_directory(mMainPath)) { - for (const auto& p : std::filesystem::recursive_directory_iterator(mMainPath)) { - if (StringHelper::IEquals(p.path().extension().string(), ".otr")) { - SPDLOG_ERROR("Reading {} mpq", p.path().string()); - mOtrArchives.push_back(p.path().string()); - } - } - } else if (std::filesystem::is_regular_file(mMainPath)) { - mOtrArchives.push_back(mMainPath); - } else { - SPDLOG_ERROR("The directory {} does not exist", mMainPath); - return false; - } - } else { - SPDLOG_ERROR("No OTR file list or Main Path provided."); - return false; - } - if (mOtrArchives.empty()) { - SPDLOG_ERROR("No OTR files present in {}", mMainPath); - return false; - } - } - bool baseLoaded = false; - size_t i = 0; - while (!baseLoaded && i < mOtrArchives.size()) { -#if defined(__SWITCH__) || defined(__WIIU__) - std::string fullPath = mOtrArchives[i]; -#else - std::string fullPath = std::filesystem::absolute(mOtrArchives[i]).string(); -#endif - bool openArchiveSuccess; - { - const std::lock_guard lock(mMutex); - openArchiveSuccess = - SFileOpenArchive(fullPath.c_str(), 0, enableWriting ? 0 : MPQ_OPEN_READ_ONLY, &mpqHandle); - } - if (openArchiveSuccess) { - SPDLOG_INFO("Opened mpq file {}.", fullPath); - mMainMpq = mpqHandle; - mMainPath = fullPath; - if (!ProcessOtrVersion(mMainMpq)) { - SPDLOG_WARN("Attempted to load invalid OTR file {}", mOtrArchives[i]); - { - const std::lock_guard lock(mMutex); - SFileCloseArchive(mpqHandle); - } - mMainMpq = nullptr; - } else { - mMpqHandles[fullPath] = mpqHandle; - if (generateCrcMap) { - GenerateCrcMap(); - } - baseLoaded = true; - } - } - i++; - } - // If we exited the above loop without setting baseLoaded to true, then we've - // attemtped to load all the OTRs available to us. - if (!baseLoaded) { - SPDLOG_ERROR("No valid OTR file was provided."); - return false; - } - for (size_t j = i; j < mOtrArchives.size(); j++) { -#if defined(__SWITCH__) || defined(__WIIU__) - std::string fullPath = mOtrArchives[j]; -#else - std::string fullPath = std::filesystem::absolute(mOtrArchives[j]).string(); -#endif - if (LoadPatchMPQ(fullPath, true)) { - SPDLOG_INFO("({}) Patched in mpq file.", fullPath); - } - if (generateCrcMap) { - GenerateCrcMap(); - } - } - - return true; -} - -bool Archive::LoadPatchMPQ(const std::string& otrPath, bool validateVersion) { - HANDLE patchHandle = NULL; -#if defined(__SWITCH__) || defined(__WIIU__) - std::string fullPath = otrPath; -#else - std::string fullPath = std::filesystem::absolute(otrPath).string(); -#endif - if (mMpqHandles.contains(fullPath)) { - return true; - } - bool openArchiveSuccess; - { - const std::lock_guard lock(mMutex); - openArchiveSuccess = SFileOpenArchive(fullPath.c_str(), 0, MPQ_OPEN_READ_ONLY, &patchHandle); - } - if (!openArchiveSuccess) { - SPDLOG_ERROR("({}) Failed to open patch mpq file {} while applying to {}.", GetLastError(), otrPath, mMainPath); - return false; - } else { - // We don't always want to validate the "version" file, only when we're loading standalone OTRs as patches - // i.e. Ocarina of Time along with Master Quest. - if (validateVersion) { - if (!ProcessOtrVersion(patchHandle)) { - SPDLOG_INFO("({}) Missing version file. Attempting to apply patch anyway.", otrPath); - } - } - } - bool openPatchArchiveSuccess; - { - const std::lock_guard lock(mMutex); - openPatchArchiveSuccess = SFileOpenPatchArchive(mMainMpq, fullPath.c_str(), "", 0); - } - if (!openPatchArchiveSuccess) { - SPDLOG_ERROR("({}) Failed to apply patch mpq file {} to main mpq {}.", GetLastError(), otrPath, mMainPath); - return false; - } - - mMpqHandles[fullPath] = patchHandle; - - return true; -} - -std::vector Archive::GetGameVersions() { - return mGameVersions; -} - -void Archive::PushGameVersion(uint32_t newGameVersion) { - mGameVersions.push_back(newGameVersion); -} -} // namespace LUS diff --git a/src/resource/Archive.h b/src/resource/Archive.h deleted file mode 100644 index 08aa732b2..000000000 --- a/src/resource/Archive.h +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once - -#undef _DLL - -#include - -#include -#include -#include -#include -#include -#include -#include "Resource.h" -#include -#include - -namespace LUS { -struct File; - -class Archive : public std::enable_shared_from_this { - public: - Archive(const std::string& mainPath, bool enableWriting); - Archive(const std::string& mainPath, const std::string& patchesPath, - const std::unordered_set& validHashes, bool enableWriting, bool generateCrcMap = true); - Archive(const std::vector& fileList, const std::unordered_set& validHashes, - bool enableWriting, bool generateCrcMap = true); - ~Archive(); - - static std::shared_ptr CreateArchive(const std::string& archivePath, size_t fileCapacity); - - bool IsMainMPQValid(); - std::shared_ptr LoadFile(const std::string& filePath, bool includeParent = true); - bool AddFile(const std::string& filePath, uintptr_t fileData, DWORD fileSize); - bool RemoveFile(const std::string& filePath); - bool RenameFile(const std::string& oldFilePath, const std::string& newFilePath); - std::shared_ptr> ListFiles(const std::string& fileSearchMask); - bool HasFile(const std::string& fileSearchMask); - const std::string* HashToString(uint64_t hash) const; - std::vector GetGameVersions(); - void PushGameVersion(uint32_t newGameVersion); - - protected: - std::shared_ptr> FindFiles(const std::string& fileSearchMask); - bool Load(bool enableWriting, bool generateCrcMap); - bool Unload(); - - private: - std::string mMainPath; - std::string mPatchesPath; - std::vector mOtrArchives; - std::unordered_set mValidHashes; - std::map mMpqHandles; - std::vector mAddedFiles; - std::vector mGameVersions; - std::unordered_map mHashes; - HANDLE mMainMpq; - std::mutex mMutex; - - bool LoadMainMPQ(bool enableWriting, bool generateCrcMap); - bool LoadPatchMPQs(); - bool LoadPatchMPQ(const std::string& otrPath, bool validateVersion = false); - void GenerateCrcMap(); - bool ProcessOtrVersion(HANDLE mpqHandle = nullptr); - std::shared_ptr LoadFileFromHandle(const std::string& filePath, bool includeParent = true, - HANDLE mpqHandle = nullptr); -}; -} // namespace LUS diff --git a/src/resource/File.h b/src/resource/File.h index d0213184c..648b4c49e 100644 --- a/src/resource/File.h +++ b/src/resource/File.h @@ -3,14 +3,30 @@ #include #include #include +#include +#include +#include "resource/ResourceType.h" +#include "utils/binarytools/BinaryReader.h" namespace LUS { class Archive; +struct ResourceInitData { + std::string Path; + Endianness ByteOrder; + uint32_t Type; + int32_t ResourceVersion; + uint64_t Id; + bool IsCustom; + bool IsXml; +}; + struct File { std::shared_ptr Parent; - std::string Path; - std::vector Buffer; + std::shared_ptr InitData; + std::shared_ptr> Buffer; + std::shared_ptr XmlDocument; + std::shared_ptr Reader; bool IsLoaded = false; }; } // namespace LUS diff --git a/src/resource/GameVersions.h b/src/resource/GameVersions.h index bfe75e16a..ce532d2eb 100644 --- a/src/resource/GameVersions.h +++ b/src/resource/GameVersions.h @@ -23,6 +23,6 @@ #define OOT_PAL_GC_MQ_DBG 0x917D18F6 #define OOT_IQUE_TW 0x3D81FB3E #define OOT_IQUE_CN 0xB1E1E07B -#define UNKNOWN 0xFFFFFFFF +#define UNKNOWN_GAME_VERSION 0xFFFFFFFF #endif diff --git a/src/resource/Resource.cpp b/src/resource/Resource.cpp index a677c4415..e7b9b66f0 100644 --- a/src/resource/Resource.cpp +++ b/src/resource/Resource.cpp @@ -1,6 +1,5 @@ #include "Resource.h" #include -#include "libultraship/libultra/gbi.h" namespace LUS { IResource::IResource(std::shared_ptr initData) : mInitData(initData) { diff --git a/src/resource/Resource.h b/src/resource/Resource.h index ede8fab03..b823d3b48 100644 --- a/src/resource/Resource.h +++ b/src/resource/Resource.h @@ -1,21 +1,10 @@ #pragma once -#include -#include "ResourceType.h" -#include "utils/binarytools/BinaryWriter.h" +#include "resource/File.h" namespace LUS { class ResourceManager; -struct ResourceInitData { - std::string Path; - Endianness ByteOrder; - ResourceType Type; - int32_t ResourceVersion; - uint64_t Id; - bool IsCustom; -}; - class IResource { public: inline static const std::string gAltAssetPrefix = "alt/"; diff --git a/src/resource/ResourceFactory.h b/src/resource/ResourceFactory.h index 35b48e640..1831057fa 100644 --- a/src/resource/ResourceFactory.h +++ b/src/resource/ResourceFactory.h @@ -2,6 +2,7 @@ #include #include "utils/binarytools/BinaryReader.h" +#include "utils/binarytools/BinaryWriter.h" #include #include "Resource.h" diff --git a/src/resource/ResourceLoader.cpp b/src/resource/ResourceLoader.cpp index ce53b2cb8..f49bda0a5 100644 --- a/src/resource/ResourceLoader.cpp +++ b/src/resource/ResourceLoader.cpp @@ -23,106 +23,67 @@ ResourceLoader::~ResourceLoader() { } void ResourceLoader::RegisterGlobalResourceFactories() { - RegisterResourceFactory(ResourceType::Texture, "Texture", std::make_shared()); - RegisterResourceFactory(ResourceType::Vertex, "Vertex", std::make_shared()); - RegisterResourceFactory(ResourceType::DisplayList, "DisplayList", std::make_shared()); - RegisterResourceFactory(ResourceType::Matrix, "Matrix", std::make_shared()); - RegisterResourceFactory(ResourceType::Array, "Array", std::make_shared()); - RegisterResourceFactory(ResourceType::Blob, "Blob", std::make_shared()); + RegisterResourceFactory(static_cast(ResourceType::Texture), std::make_shared()); + RegisterResourceFactory(static_cast(ResourceType::Vertex), std::make_shared()); + RegisterResourceFactory(static_cast(ResourceType::DisplayList), std::make_shared()); + RegisterResourceFactory(static_cast(ResourceType::Matrix), std::make_shared()); + RegisterResourceFactory(static_cast(ResourceType::Array), std::make_shared()); + RegisterResourceFactory(static_cast(ResourceType::Blob), std::make_shared()); } -bool ResourceLoader::RegisterResourceFactory(ResourceType resourceType, std::string resourceTypeXML, - std::shared_ptr factory) { +bool ResourceLoader::RegisterResourceFactory(uint32_t resourceType, std::shared_ptr factory) { if (mFactories.contains(resourceType)) { return false; } mFactories[resourceType] = factory; - mFactoriesStr[resourceTypeXML] = factory; - mFactoriesTypes[resourceTypeXML] = resourceType; return true; } +uint32_t ResourceLoader::GetVersionFromString(const std::string& version) { + return mFactoriesTypes[version]; +} + std::shared_ptr ResourceLoader::LoadResource(std::shared_ptr fileToLoad) { std::shared_ptr result = nullptr; - if (fileToLoad != nullptr) { - auto stream = std::make_shared(fileToLoad->Buffer.data(), fileToLoad->Buffer.size()); - auto reader = std::make_shared(stream); - - // Determine if file is binary or XML... - uint8_t firstByte = reader->ReadInt8(); - - auto resourceInitData = std::make_shared(); - resourceInitData->Path = fileToLoad->Path; - resourceInitData->Id = 0xDEADBEEFDEADBEEF; - resourceInitData->Type = ResourceType::None; - resourceInitData->ResourceVersion = -1; - resourceInitData->IsCustom = false; - resourceInitData->ByteOrder = Endianness::Native; - - // If first byte is '<' then we are loading XML, else we are loading OTR binary. - if (firstByte == '<') { - // XML - resourceInitData->IsCustom = true; - reader->Seek(-1, SeekOffsetType::Current); - - std::string xmlStr = reader->ReadCString(); - - tinyxml2::XMLDocument doc; - tinyxml2::XMLError eResult = doc.Parse(xmlStr.data()); - - // OTRTODO: Error checking - - auto root = doc.FirstChildElement(); - - std::string nodeName = root->Name(); - resourceInitData->ResourceVersion = root->IntAttribute("Version"); - - auto factory = mFactoriesStr[nodeName]; - resourceInitData->Type = mFactoriesTypes[nodeName]; + if (fileToLoad == nullptr) { + SPDLOG_ERROR("Failed to load resource: File not loaded"); + return result; + } - if (factory != nullptr) { - result = factory->ReadResourceXML(resourceInitData, root); - } - } else { - // OTR HEADER BEGIN - // Byte Order - resourceInitData->ByteOrder = (Endianness)firstByte; - reader->SetEndianness(resourceInitData->ByteOrder); - // Is this asset custom? - resourceInitData->IsCustom = (bool)reader->ReadInt8(); - // Unused two bytes - for (int i = 0; i < 2; i++) { - reader->ReadInt8(); - } - // The type of the resource - resourceInitData->Type = (ResourceType)reader->ReadUInt32(); - // Resource version - resourceInitData->ResourceVersion = reader->ReadUInt32(); - // Unique asset ID - resourceInitData->Id = reader->ReadUInt64(); - // ???? - reader->ReadUInt32(); - // ROM CRC - reader->ReadUInt64(); - // ROM Enum - reader->ReadUInt32(); - // Reserved for future file format versions... - reader->Seek(64, SeekOffsetType::Start); - // OTR HEADER END + if (fileToLoad->InitData == nullptr) { + SPDLOG_ERROR("Failed to load resource: ResourceInitData not loaded"); + return result; + } - auto factory = mFactories[resourceInitData->Type]; + auto factory = mFactories[fileToLoad->InitData->Type]; + if (factory == nullptr) { + SPDLOG_ERROR("Failed to load resource: Factory does not exist ({} - {})", fileToLoad->InitData->Type, + fileToLoad->InitData->Path); + } - if (factory != nullptr) { - result = factory->ReadResource(resourceInitData, reader); - } + if (fileToLoad->InitData->IsXml) { + if (fileToLoad->XmlDocument == nullptr) { + SPDLOG_ERROR("Failed to load resource: File has no XML document ({} - {})", fileToLoad->InitData->Type, + fileToLoad->InitData->Path); + return result; } - if (result == nullptr) { - SPDLOG_ERROR("Failed to load resource of type {} \"{}\"", (uint32_t)resourceInitData->Type, - resourceInitData->Path); + result = factory->ReadResourceXML(fileToLoad->InitData, fileToLoad->XmlDocument->FirstChildElement()); + } else { + if (fileToLoad->Reader == nullptr) { + SPDLOG_ERROR("Failed to load resource: File has Reader ({} - {})", fileToLoad->InitData->Type, + fileToLoad->InitData->Path); + return result; } + + result = factory->ReadResource(fileToLoad->InitData, fileToLoad->Reader); + } + + if (result == nullptr) { + SPDLOG_ERROR("Failed to load resource of type {} \"{}\"", fileToLoad->InitData->Type, + fileToLoad->InitData->Path); } return result; diff --git a/src/resource/ResourceLoader.h b/src/resource/ResourceLoader.h index 04e6e3c7a..ff5120b3c 100644 --- a/src/resource/ResourceLoader.h +++ b/src/resource/ResourceLoader.h @@ -15,15 +15,13 @@ class ResourceLoader { ~ResourceLoader(); std::shared_ptr LoadResource(std::shared_ptr fileToLoad); - bool RegisterResourceFactory(ResourceType resourceType, std::string resourceTypeXML, - std::shared_ptr factory); + bool RegisterResourceFactory(uint32_t resourceType, std::shared_ptr factory); + uint32_t GetVersionFromString(const std::string& version); protected: void RegisterGlobalResourceFactories(); private: - std::unordered_map> mFactories; - std::unordered_map> mFactoriesStr; - std::unordered_map mFactoriesTypes; + std::unordered_map> mFactories; }; } // namespace LUS diff --git a/src/resource/ResourceManager.cpp b/src/resource/ResourceManager.cpp index 8c705f1cf..e6dd0e11f 100644 --- a/src/resource/ResourceManager.cpp +++ b/src/resource/ResourceManager.cpp @@ -1,41 +1,28 @@ #include "ResourceManager.h" #include -#include "File.h" -#include "Archive.h" +#include "resource/File.h" +#include "resource/archive/Archive.h" #include #include #include #include "public/bridge/consolevariablebridge.h" #include "Context.h" +// TODO: Delete me and find an implementation // Comes from stormlib. May not be the most efficient, but it's also important to be consistent. // NOLINTNEXTLINE extern bool SFileCheckWildCard(const char* szString, const char* szWildCard); namespace LUS { -ResourceManager::ResourceManager(const std::string& mainPath, const std::string& patchesPath, - const std::unordered_set& validHashes, int32_t reservedThreadCount) { - mResourceLoader = std::make_shared(); - mArchive = std::make_shared(mainPath, patchesPath, validHashes, false); -#if defined(__SWITCH__) || defined(__WIIU__) - size_t threadCount = 1; -#else - // the extra `- 1` is because we reserve an extra thread for spdlog - size_t threadCount = std::max(1, (int32_t)(std::thread::hardware_concurrency() - reservedThreadCount - 1)); -#endif - mThreadPool = std::make_shared(threadCount); - - if (!DidLoadSuccessfully()) { - // Nothing ever unpauses the thread pool since nothing will ever try to load the archive again. - mThreadPool->pause(); - } +ResourceManager::ResourceManager() { } -ResourceManager::ResourceManager(const std::vector& otrFiles, - const std::unordered_set& validHashes, int32_t reservedThreadCount) { +void ResourceManager::Init(const std::vector& otrFiles, const std::unordered_set& validHashes, + int32_t reservedThreadCount) { mResourceLoader = std::make_shared(); - mArchive = std::make_shared(otrFiles, validHashes, false); + mArchiveManager = std::make_shared(); + GetArchiveManager()->Init(otrFiles, validHashes); #if defined(__SWITCH__) || defined(__WIIU__) size_t threadCount = 1; #else @@ -55,13 +42,13 @@ ResourceManager::~ResourceManager() { } bool ResourceManager::DidLoadSuccessfully() { - return mArchive != nullptr && mArchive->IsMainMPQValid(); + return mArchiveManager != nullptr && mArchiveManager->IsArchiveLoaded(); } std::shared_ptr ResourceManager::LoadFileProcess(const std::string& filePath) { - auto file = mArchive->LoadFile(filePath, true); + auto file = mArchiveManager->LoadFile(filePath); if (file != nullptr) { - SPDLOG_TRACE("Loaded File {} on ResourceManager", file->Path); + SPDLOG_TRACE("Loaded File {} on ResourceManager", file->InitData->Path); } else { SPDLOG_TRACE("Could not load File {} in ResourceManager", filePath); } @@ -249,7 +236,7 @@ ResourceManager::GetCachedResource(std::variant>>> ResourceManager::LoadDirectoryAsync(const std::string& searchMask, bool priority) { auto loadedList = std::make_shared>>>(); - auto fileList = GetArchive()->ListFiles(searchMask); + auto fileList = GetArchiveManager()->ListFiles(searchMask); loadedList->reserve(fileList->size()); for (size_t i = 0; i < fileList->size(); i++) { @@ -274,21 +261,8 @@ std::shared_ptr>> ResourceManager::LoadDi return loadedList; } -std::shared_ptr> ResourceManager::FindLoadedFiles(const std::string& searchMask) { - const char* wildCard = searchMask.c_str(); - auto list = std::make_shared>(); - - for (const auto& [key, value] : mResourceCache) { - if (SFileCheckWildCard(key.c_str(), wildCard)) { - list->push_back(key); - } - } - - return list; -} - void ResourceManager::DirtyDirectory(const std::string& searchMask) { - auto list = FindLoadedFiles(searchMask); + auto list = GetArchiveManager()->ListFiles(searchMask); for (const auto& key : *list.get()) { auto resource = GetCachedResource(key); @@ -302,15 +276,15 @@ void ResourceManager::DirtyDirectory(const std::string& searchMask) { } void ResourceManager::UnloadDirectory(const std::string& searchMask) { - auto list = FindLoadedFiles(searchMask); + auto list = GetArchiveManager()->ListFiles(searchMask); for (const auto& key : *list.get()) { UnloadResource(key); } } -std::shared_ptr ResourceManager::GetArchive() { - return mArchive; +std::shared_ptr ResourceManager::GetArchiveManager() { + return mArchiveManager; } std::shared_ptr ResourceManager::GetResourceLoader() { diff --git a/src/resource/ResourceManager.h b/src/resource/ResourceManager.h index 3d30d6ed9..7b48dd2a9 100644 --- a/src/resource/ResourceManager.h +++ b/src/resource/ResourceManager.h @@ -1,13 +1,14 @@ #pragma once #include +#include #include #include #include #include -#include "Resource.h" -#include "ResourceLoader.h" -#include "Archive.h" +#include "resource/Resource.h" +#include "resource/ResourceLoader.h" +#include "resource/archive/ArchiveManager.h" #include "thread-pool/BS_thread_pool.hpp" namespace LUS { @@ -20,14 +21,13 @@ class ResourceManager { typedef enum class ResourceLoadError { None, NotCached, NotFound } ResourceLoadError; public: - ResourceManager(const std::string& mainPath, const std::string& patchesPath, - const std::unordered_set& validHashes, int32_t reservedThreadCount = 1); - ResourceManager(const std::vector& otrFiles, const std::unordered_set& validHashes, - int32_t reservedThreadCount = 1); + ResourceManager(); + void Init(const std::vector& otrFiles, const std::unordered_set& validHashes, + int32_t reservedThreadCount = 1); ~ResourceManager(); bool DidLoadSuccessfully(); - std::shared_ptr GetArchive(); + std::shared_ptr GetArchiveManager(); std::shared_ptr GetResourceLoader(); std::shared_future> LoadFileAsync(const std::string& filePath, bool priority = false); std::shared_ptr LoadFile(const std::string& filePath); @@ -40,7 +40,6 @@ class ResourceManager { std::shared_ptr>> LoadDirectory(const std::string& searchMask); std::shared_ptr>>> LoadDirectoryAsync(const std::string& searchMask, bool priority = false); - std::shared_ptr> FindLoadedFiles(const std::string& searchMask); void DirtyDirectory(const std::string& searchMask); void UnloadDirectory(const std::string& searchMask); bool OtrSignatureCheck(const char* fileName); @@ -54,7 +53,7 @@ class ResourceManager { private: std::unordered_map>> mResourceCache; std::shared_ptr mResourceLoader; - std::shared_ptr mArchive; + std::shared_ptr mArchiveManager; std::shared_ptr mThreadPool; std::mutex mMutex; }; diff --git a/src/resource/ResourceType.h b/src/resource/ResourceType.h index cc0512a86..59d1cfae3 100644 --- a/src/resource/ResourceType.h +++ b/src/resource/ResourceType.h @@ -1,5 +1,8 @@ #pragma once +#include +#include + namespace LUS { enum class ResourceType { @@ -32,4 +35,30 @@ enum class ResourceType { SOH_Background = 0x4F424749, // OBGI SOH_SceneCommand = 0x4F52434D, // ORCM }; + +inline std::unordered_map mFactoriesTypes{ + { "None", static_cast(ResourceType::None) }, + { "Archive", static_cast(ResourceType::Archive) }, + { "DisplayList", static_cast(ResourceType::DisplayList) }, + { "Vertex", static_cast(ResourceType::Vertex) }, + { "Matrix", static_cast(ResourceType::Matrix) }, + { "Array", static_cast(ResourceType::Array) }, + { "Blob", static_cast(ResourceType::Blob) }, + { "Texture", static_cast(ResourceType::Texture) }, + { "SOH_Animation", static_cast(ResourceType::SOH_Animation) }, + { "SOH_PlayerAnimation", static_cast(ResourceType::SOH_PlayerAnimation) }, + { "SOH_Room", static_cast(ResourceType::SOH_Room) }, + { "SOH_CollisionHeader", static_cast(ResourceType::SOH_CollisionHeader) }, + { "SOH_Skeleton", static_cast(ResourceType::SOH_Skeleton) }, + { "SOH_SkeletonLimb", static_cast(ResourceType::SOH_SkeletonLimb) }, + { "SOH_Path", static_cast(ResourceType::SOH_Path) }, + { "SOH_Cutscene", static_cast(ResourceType::SOH_Cutscene) }, + { "SOH_Text", static_cast(ResourceType::SOH_Text) }, + { "SOH_Audio", static_cast(ResourceType::SOH_Audio) }, + { "SOH_AudioSample", static_cast(ResourceType::SOH_AudioSample) }, + { "SOH_AudioSoundFont", static_cast(ResourceType::SOH_AudioSoundFont) }, + { "SOH_AudioSequence", static_cast(ResourceType::SOH_AudioSequence) }, + { "SOH_Background", static_cast(ResourceType::SOH_Background) }, + { "SOH_SceneCommand", static_cast(ResourceType::SOH_SceneCommand) }, +}; } // namespace LUS diff --git a/src/resource/archive/Archive.cpp b/src/resource/archive/Archive.cpp new file mode 100644 index 000000000..433a31b7f --- /dev/null +++ b/src/resource/archive/Archive.cpp @@ -0,0 +1,242 @@ +#include "Archive.h" + +#include "spdlog/spdlog.h" + +#include "Context.h" +#include "resource/GameVersions.h" +#include "resource/File.h" +#include "resource/ResourceLoader.h" +#include "utils/binarytools/MemoryStream.h" +#include + +// TODO: Delete me and find an implementation +// Comes from stormlib. May not be the most efficient, but it's also important to be consistent. +// NOLINTNEXTLINE +extern bool SFileCheckWildCard(const char* szString, const char* szWildCard); + +namespace LUS { +Archive::Archive(const std::string& path) + : mHasGameVersion(false), mGameVersion(UNKNOWN_GAME_VERSION), mPath(path), mIsLoaded(false) { + mHashes = std::make_shared>(); +} + +Archive::~Archive() { + SPDLOG_TRACE("destruct archive: {}", GetPath()); +} + +void Archive::Load() { + bool opened = LoadRaw(); + + auto t = LoadFileRaw("version"); + bool isGameVersionValid = false; + if (t != nullptr && t->IsLoaded) { + mHasGameVersion = true; + auto stream = std::make_shared(t->Buffer->data(), t->Buffer->size()); + auto reader = std::make_shared(stream); + LUS::Endianness endianness = (Endianness)reader->ReadUByte(); + reader->SetEndianness(endianness); + SetGameVersion(reader->ReadUInt32()); + isGameVersionValid = + Context::GetInstance()->GetResourceManager()->GetArchiveManager()->IsGameVersionValid(GetGameVersion()); + + if (!isGameVersionValid) { + SPDLOG_WARN("Attempting to load Archive \"{}\" with invalid version {}", GetPath(), GetGameVersion()); + } + } + + SetLoaded(opened && (!mHasGameVersion || isGameVersionValid)); + + if (!IsLoaded()) { + Unload(); + } +} + +void Archive::Unload() { + SetLoaded(false); +} + +std::shared_ptr> Archive::ListFiles() { + return mHashes; +} + +std::shared_ptr> Archive::ListFiles(const std::string& filter) { + auto result = std::make_shared>(); + + std::copy_if(mHashes->begin(), mHashes->end(), std::inserter(*result, result->begin()), + [filter](const std::pair entry) { + return SFileCheckWildCard(entry.second.c_str(), filter.c_str()); + }); + + return result; +} + +bool Archive::HasFile(const std::string& filePath) { + return HasFile(CRC64(filePath.c_str())); +} + +bool Archive::HasFile(uint64_t hash) { + return mHashes->count(hash) > 0; +} + +bool Archive::HasGameVersion() { + return mHasGameVersion; +} + +uint32_t Archive::GetGameVersion() { + return mGameVersion; +} + +const std::string& Archive::GetPath() { + return mPath; +} + +bool Archive::IsLoaded() { + return mIsLoaded; +} + +void Archive::SetLoaded(bool isLoaded) { + mIsLoaded = isLoaded; +} + +void Archive::SetGameVersion(uint32_t gameVersion) { + mGameVersion = gameVersion; +} + +void Archive::AddFile(const std::string& filePath) { + (*mHashes)[CRC64(filePath.c_str())] = filePath; +} + +std::shared_ptr Archive::LoadFile(const std::string& filePath) { + auto fileToLoad = LoadFileRaw(filePath); + + // Determine if file is binary or XML... + if (fileToLoad->Buffer->at(0) == '<') { + // File is XML + // Read the xml document + auto stream = std::make_shared(fileToLoad->Buffer); + auto reader = fileToLoad->Reader = std::make_shared(stream); + fileToLoad->XmlDocument = std::make_shared(); + fileToLoad->XmlDocument->Parse(reader->ReadCString().data()); + if (fileToLoad->XmlDocument->Error()) { + SPDLOG_ERROR("Failed to parse XML file {}. Error: {}", filePath, fileToLoad->XmlDocument->ErrorStr()); + return nullptr; + } + fileToLoad->InitData = ReadResourceInitDataXml(filePath, fileToLoad->XmlDocument); + } else { + // File is Binary + auto fileToLoadMeta = LoadFileMeta(filePath); + + // Split out the header for reading. + if (fileToLoadMeta != nullptr) { + fileToLoad->Buffer = + std::make_shared>(fileToLoad->Buffer->begin(), fileToLoad->Buffer->end()); + } else { + auto headerBuffer = std::make_shared>(fileToLoad->Buffer->begin(), + fileToLoad->Buffer->begin() + OTR_HEADER_SIZE); + + if (headerBuffer->size() < OTR_HEADER_SIZE) { + SPDLOG_ERROR("Failed to parse ResourceInitData, buffer size too small. File: {}. Got {} bytes and " + "needed {} bytes.", + filePath, headerBuffer->size(), OTR_HEADER_SIZE); + return nullptr; + } + + fileToLoad->Buffer = std::make_shared>(fileToLoad->Buffer->begin() + OTR_HEADER_SIZE, + fileToLoad->Buffer->end()); + + // Create a reader for the header buffer + auto headerStream = std::make_shared(headerBuffer); + auto headerReader = std::make_shared(headerStream); + fileToLoadMeta = ReadResourceInitDataBinary(filePath, headerReader); + } + + // Create a reader for the data buffer + auto stream = std::make_shared(fileToLoad->Buffer); + fileToLoad->Reader = std::make_shared(stream); + + fileToLoad->InitData = fileToLoadMeta; + fileToLoad->Reader->SetEndianness(fileToLoad->InitData->ByteOrder); + } + + return fileToLoad; +} + +std::shared_ptr Archive::LoadFile(uint64_t hash) { + const std::string& filePath = + *Context::GetInstance()->GetResourceManager()->GetArchiveManager()->HashToString(hash); + return LoadFile(filePath); +} + +std::shared_ptr Archive::CreateDefaultResourceInitData() { + auto resourceInitData = std::make_shared(); + resourceInitData->Id = 0xDEADBEEFDEADBEEF; + resourceInitData->Type = static_cast(ResourceType::None); + resourceInitData->ResourceVersion = -1; + resourceInitData->IsCustom = false; + resourceInitData->IsXml = false; + resourceInitData->ByteOrder = Endianness::Native; + return resourceInitData; +} + +std::shared_ptr Archive::ReadResourceInitDataBinary(const std::string& filePath, + std::shared_ptr headerReader) { + auto resourceInitData = CreateDefaultResourceInitData(); + resourceInitData->Path = filePath; + + if (headerReader == nullptr) { + SPDLOG_ERROR("Error reading OTR header from XML: No header buffer document for file {}", filePath); + return resourceInitData; + } + + // OTR HEADER BEGIN + // Byte Order + resourceInitData->ByteOrder = (Endianness)headerReader->ReadInt8(); + headerReader->SetEndianness(resourceInitData->ByteOrder); + // Is this asset custom? + resourceInitData->IsCustom = (bool)headerReader->ReadInt8(); + // Unused two bytes + for (int i = 0; i < 2; i++) { + headerReader->ReadInt8(); + } + // The type of the resource + resourceInitData->Type = headerReader->ReadUInt32(); + // Resource version + resourceInitData->ResourceVersion = headerReader->ReadUInt32(); + // Unique asset ID + resourceInitData->Id = headerReader->ReadUInt64(); + // Reserved + // reader->ReadUInt32(); + // ROM CRC + // reader->ReadUInt64(); + // ROM Enum + // reader->ReadUInt32(); + // Reserved for future file format versions... + // reader->Seek(OTR_HEADER_SIZE, SeekOffsetType::Start); + // OTR HEADER END + + headerReader->Seek(0, SeekOffsetType::Start); + return resourceInitData; +} + +std::shared_ptr Archive::ReadResourceInitDataXml(const std::string& filePath, + std::shared_ptr document) { + auto resourceInitData = CreateDefaultResourceInitData(); + resourceInitData->Path = filePath; + + if (document == nullptr) { + SPDLOG_ERROR("Error reading OTR header from XML: No XML document for file {}", filePath); + return resourceInitData; + } + + // XML + resourceInitData->IsCustom = true; + resourceInitData->IsXml = true; + + auto root = document->FirstChildElement(); + resourceInitData->Type = mFactoriesTypes[root->Name()]; + resourceInitData->ResourceVersion = root->IntAttribute("Version"); + + return resourceInitData; +} + +} // namespace LUS diff --git a/src/resource/archive/Archive.h b/src/resource/archive/Archive.h new file mode 100644 index 000000000..32991e1bf --- /dev/null +++ b/src/resource/archive/Archive.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "utils/binarytools/BinaryReader.h" + +namespace LUS { +#define OTR_HEADER_SIZE ((size_t)64) + +struct File; +struct ResourceInitData; + +class Archive { + friend class ArchiveManager; + + public: + Archive(const std::string& path); + ~Archive(); + + void Load(); + void Unload(); + + virtual std::shared_ptr LoadFile(const std::string& filePath); + virtual std::shared_ptr LoadFile(uint64_t hash); + std::shared_ptr> ListFiles(); + std::shared_ptr> ListFiles(const std::string& filter); + bool HasFile(const std::string& filePath); + bool HasFile(uint64_t hash); + bool HasGameVersion(); + uint32_t GetGameVersion(); + const std::string& GetPath(); + bool IsLoaded(); + + virtual bool LoadRaw() = 0; + virtual bool UnloadRaw() = 0; + virtual std::shared_ptr LoadFileRaw(const std::string& filePath) = 0; + virtual std::shared_ptr LoadFileRaw(uint64_t hash) = 0; + + protected: + static std::shared_ptr ReadResourceInitDataBinary(const std::string& filePath, + std::shared_ptr headerReader); + static std::shared_ptr ReadResourceInitDataXml(const std::string& filePath, + std::shared_ptr document); + virtual std::shared_ptr LoadFileMeta(const std::string& filePath) = 0; + virtual std::shared_ptr LoadFileMeta(uint64_t hash) = 0; + + void SetLoaded(bool isLoaded); + void SetGameVersion(uint32_t gameVersion); + void AddFile(const std::string& filePath); + + private: + static std::shared_ptr CreateDefaultResourceInitData(); + + bool mIsLoaded; + bool mHasGameVersion; + uint32_t mGameVersion; + std::string mPath; + std::shared_ptr> mHashes; +}; +} // namespace LUS diff --git a/src/resource/archive/ArchiveManager.cpp b/src/resource/archive/ArchiveManager.cpp new file mode 100644 index 000000000..b84cf866f --- /dev/null +++ b/src/resource/archive/ArchiveManager.cpp @@ -0,0 +1,225 @@ +#include "ArchiveManager.h" + +#include +#include "spdlog/spdlog.h" + +#include "resource/archive/Archive.h" +#include "resource/archive/OtrArchive.h" +#include "resource/archive/O2rArchive.h" +#include "Utils/StringHelper.h" +#include + +// TODO: Delete me and find an implementation +// Comes from stormlib. May not be the most efficient, but it's also important to be consistent. +// NOLINTNEXTLINE +extern bool SFileCheckWildCard(const char* szString, const char* szWildCard); + +namespace LUS { +ArchiveManager::ArchiveManager() { +} + +void ArchiveManager::Init(const std::vector& archivePaths) { + Init(archivePaths, {}); +} + +void ArchiveManager::Init(const std::vector& archivePaths, + const std::unordered_set& validGameVersions) { + mValidGameVersions = validGameVersions; + auto archives = GetArchiveListInPaths(archivePaths); + for (const auto archive : archives) { + AddArchive(archive); + } +} + +ArchiveManager::~ArchiveManager() { + SPDLOG_TRACE("destruct archive manager"); + SetArchives({}); +} + +bool ArchiveManager::IsArchiveLoaded() { + return !mArchives.empty(); +} + +std::shared_ptr ArchiveManager::LoadFile(const std::string& filePath) { + if (filePath == "") { + return nullptr; + } + + const auto archive = mFileToArchive[CRC64(filePath.c_str())]; + if (archive == nullptr) { + return nullptr; + } + + return archive->LoadFile(filePath); +} + +std::shared_ptr ArchiveManager::LoadFile(uint64_t hash) { + const auto archive = mFileToArchive[hash]; + if (archive == nullptr) { + return nullptr; + } + + return archive->LoadFile(hash); +} + +std::shared_ptr ArchiveManager::LoadFileRaw(const std::string& filePath) { + if (filePath == "") { + return nullptr; + } + + const auto archive = mFileToArchive[CRC64(filePath.c_str())]; + if (archive == nullptr) { + return nullptr; + } + + return archive->LoadFileRaw(filePath); +} + +std::shared_ptr ArchiveManager::LoadFileRaw(uint64_t hash) { + const auto archive = mFileToArchive[hash]; + if (archive == nullptr) { + return nullptr; + } + + return archive->LoadFileRaw(hash); +} + +bool ArchiveManager::HasFile(const std::string& filePath) { + return HasFile(CRC64(filePath.c_str())); +} + +bool ArchiveManager::HasFile(uint64_t hash) { + return mFileToArchive.count(hash) > 0; +} + +std::shared_ptr> ArchiveManager::ListFiles(const std::string& filter) { + auto list = ListFiles(); + auto result = std::make_shared>(); + + std::copy_if(list->begin(), list->end(), std::back_inserter(*result), [filter](const std::string& filePath) { + return SFileCheckWildCard(filePath.c_str(), filter.c_str()); + }); + + return result; +} + +std::shared_ptr> ArchiveManager::ListFiles() { + auto list = std::make_shared>(); + for (const auto& [hash, path] : mHashes) { + list->push_back(path); + } + return list; +} + +std::vector ArchiveManager::GetGameVersions() { + return mGameVersions; +} + +void ArchiveManager::AddGameVersion(uint32_t newGameVersion) { + mGameVersions.push_back(newGameVersion); +} + +std::vector> ArchiveManager::GetArchives() { + return mArchives; +} + +void ArchiveManager::SetArchives(const std::vector>& archives) { + for (const auto& archive : mArchives) { + archive->Unload(); + } + mArchives.clear(); + mGameVersions.clear(); + mHashes.clear(); + mFileToArchive.clear(); + for (const auto& archive : archives) { + if (!archive->IsLoaded()) { + archive->Load(); + } + AddArchive(archive); + } +} + +const std::string* ArchiveManager::HashToString(uint64_t hash) const { + auto it = mHashes.find(hash); + return it != mHashes.end() ? &it->second : nullptr; +} + +std::vector ArchiveManager::GetArchiveListInPaths(const std::vector& archivePaths) { + std::vector fileList = {}; + + for (const auto& archivePath : archivePaths) { + if (archivePath.length() > 0) { + if (std::filesystem::is_directory(archivePath)) { + for (const auto& p : std::filesystem::recursive_directory_iterator(archivePath)) { + if (StringHelper::IEquals(p.path().extension().string(), ".otr") || + StringHelper::IEquals(p.path().extension().string(), ".zip") || + StringHelper::IEquals(p.path().extension().string(), ".mpq") || + StringHelper::IEquals(p.path().extension().string(), ".o2r")) { + fileList.push_back(std::filesystem::absolute(p).string()); + } + } + } else if (std::filesystem::is_regular_file(archivePath)) { + fileList.push_back(std::filesystem::absolute(archivePath).string()); + } else { + SPDLOG_WARN("The archive at path {} does not exist", std::filesystem::absolute(archivePath).string()); + } + } else { + SPDLOG_WARN("No archive path supplied"); + } + } + + return fileList; +} + +std::shared_ptr ArchiveManager::AddArchive(const std::string& archivePath) { + const std::filesystem::path path = archivePath; + const std::string extension = path.extension().string(); + std::shared_ptr archive = nullptr; + + SPDLOG_INFO("Reading archive: {}", path.string()); + + if (StringHelper::IEquals(extension, ".zip") || StringHelper::IEquals(extension, ".zip")) { + archive = dynamic_pointer_cast(std::make_shared(archivePath)); + } else if (StringHelper::IEquals(extension, ".otr") || StringHelper::IEquals(extension, ".mpq")) { + archive = dynamic_pointer_cast(std::make_shared(archivePath)); + } else { + // Not recognized file extension, trying with o2r + SPDLOG_WARN("File extension \"{}\" not recognized, trying to create an o2r archive.", extension); + archive = std::make_shared(archivePath); + } + + archive->Load(); + return AddArchive(archive); +} + +std::shared_ptr ArchiveManager::AddArchive(std::shared_ptr archive) { + if (!archive->IsLoaded()) { + SPDLOG_WARN("Attempting to add unloaded Archive at {} to Archive Manager", archive->GetPath()); + return nullptr; + } + + if (!mValidGameVersions.empty() && !mValidGameVersions.contains(archive->GetGameVersion())) { + SPDLOG_WARN("Attempting to add Archive at {} with invalid Game Version {} to Archive Manager", + archive->GetPath(), archive->GetGameVersion()); + return nullptr; + } + + SPDLOG_INFO("Adding Archive {} to Archive Manager", archive->GetPath()); + + mArchives.push_back(archive); + if (archive->HasGameVersion()) { + mGameVersions.push_back(archive->GetGameVersion()); + } + const auto fileList = archive->ListFiles(); + for (auto& [hash, filename] : *fileList.get()) { + mHashes[hash] = filename; + mFileToArchive[hash] = archive; + } + return archive; +} + +bool ArchiveManager::IsGameVersionValid(uint32_t gameVersion) { + return mValidGameVersions.empty() || mValidGameVersions.contains(gameVersion); +} + +} // namespace LUS diff --git a/src/resource/archive/ArchiveManager.h b/src/resource/archive/ArchiveManager.h new file mode 100644 index 000000000..1bcdb4503 --- /dev/null +++ b/src/resource/archive/ArchiveManager.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace LUS { +struct File; +class Archive; + +class ArchiveManager { + public: + ArchiveManager(); + void Init(const std::vector& archivePaths); + void Init(const std::vector& archivePaths, const std::unordered_set& validGameVersions); + ~ArchiveManager(); + + bool IsArchiveLoaded(); + std::shared_ptr LoadFile(const std::string& filePath); + std::shared_ptr LoadFile(uint64_t hash); + std::shared_ptr LoadFileRaw(const std::string& filePath); + std::shared_ptr LoadFileRaw(uint64_t hash); + bool HasFile(const std::string& filePath); + bool HasFile(uint64_t hash); + std::shared_ptr> ListFiles(const std::string& filter); + std::shared_ptr> ListFiles(); + std::vector GetGameVersions(); + std::vector> GetArchives(); + void SetArchives(const std::vector>& archives); + const std::string* HashToString(uint64_t hash) const; + bool IsGameVersionValid(uint32_t gameVersion); + + protected: + static std::vector GetArchiveListInPaths(const std::vector& archivePaths); + + std::shared_ptr AddArchive(const std::string& archivePath); + std::shared_ptr AddArchive(std::shared_ptr archive); + void AddGameVersion(uint32_t newGameVersion); + + private: + std::vector> mArchives; + std::vector mGameVersions; + std::unordered_set mValidGameVersions; + std::unordered_map mHashes; + std::unordered_map> mFileToArchive; +}; +} // namespace LUS diff --git a/src/resource/archive/O2rArchive.cpp b/src/resource/archive/O2rArchive.cpp new file mode 100644 index 000000000..199565315 --- /dev/null +++ b/src/resource/archive/O2rArchive.cpp @@ -0,0 +1,38 @@ +#include "O2rArchive.h" + +#include "spdlog/spdlog.h" + +namespace LUS { +O2rArchive::O2rArchive(const std::string& archivePath) : Archive(archivePath) { +} + +O2rArchive::~O2rArchive() { + SPDLOG_TRACE("destruct o2rarchive: {}", GetPath()); +} + +std::shared_ptr O2rArchive::LoadFileRaw(uint64_t hash) { + return nullptr; +} + +std::shared_ptr O2rArchive::LoadFileRaw(const std::string& filePath) { + return nullptr; +} + +std::shared_ptr O2rArchive::LoadFileMeta(const std::string& filePath) { + // Search for file with .meta postfix. + // If exists, return a ResourceInitData with that data parsed out. Else return a default ResourceInitData + return nullptr; +} + +std::shared_ptr O2rArchive::LoadFileMeta(uint64_t hash) { + return nullptr; +} + +bool O2rArchive::LoadRaw() { + return false; +} + +bool O2rArchive::UnloadRaw() { + return false; +} +} // namespace LUS diff --git a/src/resource/archive/O2rArchive.h b/src/resource/archive/O2rArchive.h new file mode 100644 index 000000000..cc00c34d5 --- /dev/null +++ b/src/resource/archive/O2rArchive.h @@ -0,0 +1,32 @@ +#pragma once + +#undef _DLL + +#include +#include +#include + +#include "resource/File.h" +#include "resource/Resource.h" +#include "resource/archive/Archive.h" + +namespace LUS { +struct File; + +class O2rArchive : virtual public Archive { + public: + O2rArchive(const std::string& archivePath); + ~O2rArchive(); + + bool LoadRaw(); + bool UnloadRaw(); + std::shared_ptr LoadFileRaw(const std::string& filePath); + std::shared_ptr LoadFileRaw(uint64_t hash); + + protected: + std::shared_ptr LoadFileMeta(const std::string& filePath); + std::shared_ptr LoadFileMeta(uint64_t hash); + + private: +}; +} // namespace LUS diff --git a/src/resource/archive/OtrArchive.cpp b/src/resource/archive/OtrArchive.cpp new file mode 100644 index 000000000..8f60ede50 --- /dev/null +++ b/src/resource/archive/OtrArchive.cpp @@ -0,0 +1,105 @@ +#include "OtrArchive.h" + +#include "Context.h" +#include "utils/binarytools/FileHelper.h" +#include "resource/ResourceManager.h" +#include "resource/archive/ArchiveManager.h" + +#include "spdlog/spdlog.h" + +namespace LUS { +OtrArchive::OtrArchive(const std::string& archivePath) : Archive(archivePath) { +} + +OtrArchive::~OtrArchive() { + SPDLOG_TRACE("destruct otrarchive: {}", GetPath()); +} + +std::shared_ptr OtrArchive::LoadFileRaw(const std::string& filePath) { + HANDLE fileHandle; + bool attempt = SFileOpenFileEx(mHandle, filePath.c_str(), 0, &fileHandle); + if (!attempt) { + SPDLOG_TRACE("({}) Failed to open file {} from mpq archive {}.", GetLastError(), filePath, GetPath()); + return nullptr; + } + + auto fileToLoad = std::make_shared(); + DWORD fileSize = SFileGetFileSize(fileHandle, 0); + DWORD readBytes; + fileToLoad->Buffer = std::make_shared>(fileSize); + bool readFileSuccess = SFileReadFile(fileHandle, fileToLoad->Buffer->data(), fileSize, &readBytes, NULL); + + if (!readFileSuccess) { + SPDLOG_ERROR("({}) Failed to read file {} from mpq archive {}", GetLastError(), filePath, GetPath()); + bool closeFileSuccess = SFileCloseFile(fileHandle); + if (!closeFileSuccess) { + SPDLOG_ERROR("({}) Failed to close file {} from mpq after read failure in archive {}", GetLastError(), + filePath, GetPath()); + } + return nullptr; + } + + bool closeFileSuccess = SFileCloseFile(fileHandle); + if (!closeFileSuccess) { + SPDLOG_ERROR("({}) Failed to close file {} from mpq archive {}", GetLastError(), filePath, GetPath()); + } + + fileToLoad->Parent = dynamic_pointer_cast(std::make_shared(std::move(*this))); + fileToLoad->IsLoaded = true; + + return fileToLoad; +} + +std::shared_ptr OtrArchive::LoadFileRaw(uint64_t hash) { + const std::string& filePath = + *Context::GetInstance()->GetResourceManager()->GetArchiveManager()->HashToString(hash); + return LoadFileRaw(filePath); +} + +std::shared_ptr OtrArchive::LoadFileMeta(const std::string& filePath) { + return nullptr; +} + +std::shared_ptr OtrArchive::LoadFileMeta(uint64_t hash) { + return nullptr; +} + +bool OtrArchive::LoadRaw() { + const bool opened = SFileOpenArchive(GetPath().c_str(), 0, MPQ_OPEN_READ_ONLY, &mHandle); + if (opened) { + SPDLOG_INFO("Opened mpq file \"{}\"", GetPath()); + } else { + SPDLOG_ERROR("Failed to load mpq file \"{}\"", GetPath()); + mHandle = nullptr; + return false; + } + + // Generate the file list by reading the list file. + // This can also be done via the StormLib API, but this was copied from the LUS1.x implementation in GenerateCrcMap. + auto listFile = LoadFileRaw("(listfile)"); + + // Use std::string_view to avoid unnecessary string copies + std::vector lines = + StringHelper::Split(std::string_view(listFile->Buffer->data(), listFile->Buffer->size()), "\n"); + + for (size_t i = 0; i < lines.size(); i++) { + // Use std::string_view to avoid unnecessary string copies + std::string_view line = lines[i].substr(0, lines[i].length() - 1); // Trim \r + std::string lineStr = std::string(line); + + AddFile(lineStr); + } + + return opened; +} + +bool OtrArchive::UnloadRaw() { + bool closed = SFileCloseArchive(mHandle); + if (!closed) { + SPDLOG_ERROR("({}) Failed to close mpq {}", GetLastError(), mHandle); + } + + return closed; +} + +} // namespace LUS diff --git a/src/resource/archive/OtrArchive.h b/src/resource/archive/OtrArchive.h new file mode 100644 index 000000000..4fab6e8c9 --- /dev/null +++ b/src/resource/archive/OtrArchive.h @@ -0,0 +1,39 @@ +#pragma once + +#undef _DLL + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "resource/Resource.h" +#include "resource/archive/Archive.h" + +namespace LUS { +struct File; + +class OtrArchive : virtual public Archive { + public: + OtrArchive(const std::string& archivePath); + ~OtrArchive(); + + bool LoadRaw(); + bool UnloadRaw(); + std::shared_ptr LoadFileRaw(const std::string& filePath); + std::shared_ptr LoadFileRaw(uint64_t hash); + + protected: + std::shared_ptr LoadFileMeta(const std::string& filePath); + std::shared_ptr LoadFileMeta(uint64_t hash); + + private: + HANDLE mHandle; +}; +} // namespace LUS diff --git a/src/resource/factory/TextureFactory.cpp b/src/resource/factory/TextureFactory.cpp index 56234fc93..1058461f3 100644 --- a/src/resource/factory/TextureFactory.cpp +++ b/src/resource/factory/TextureFactory.cpp @@ -35,13 +35,10 @@ void TextureFactoryV0::ParseFileBinary(std::shared_ptr reader, std texture->Type = (TextureType)reader->ReadUInt32(); texture->Width = reader->ReadUInt32(); texture->Height = reader->ReadUInt32(); + texture->ImageDataSize = reader->ReadUInt32(); + texture->ImageData = new uint8_t[texture->ImageDataSize]; - uint32_t dataSize = reader->ReadUInt32(); - - texture->ImageDataSize = dataSize; - texture->ImageData = new uint8_t[dataSize]; - - reader->Read((char*)texture->ImageData, dataSize); + reader->Read((char*)texture->ImageData, texture->ImageDataSize); } void TextureFactoryV1::ParseFileBinary(std::shared_ptr reader, std::shared_ptr resource) { @@ -54,12 +51,9 @@ void TextureFactoryV1::ParseFileBinary(std::shared_ptr reader, std texture->Flags = reader->ReadUInt32(); texture->HByteScale = reader->ReadFloat(); texture->VPixelScale = reader->ReadFloat(); + texture->ImageDataSize = reader->ReadUInt32(); + texture->ImageData = new uint8_t[texture->ImageDataSize]; - uint32_t dataSize = reader->ReadUInt32(); - - texture->ImageDataSize = dataSize; - texture->ImageData = new uint8_t[dataSize]; - - reader->Read((char*)texture->ImageData, dataSize); + reader->Read((char*)texture->ImageData, texture->ImageDataSize); } } // namespace LUS diff --git a/src/utils/binarytools/MemoryStream.cpp b/src/utils/binarytools/MemoryStream.cpp index d94b7d348..9830d72d8 100644 --- a/src/utils/binarytools/MemoryStream.cpp +++ b/src/utils/binarytools/MemoryStream.cpp @@ -6,23 +6,29 @@ #endif LUS::MemoryStream::MemoryStream() { - mBuffer = std::vector(); + mBuffer = std::make_shared>(); // mBuffer.reserve(1024 * 16); mBufferSize = 0; mBaseAddress = 0; } LUS::MemoryStream::MemoryStream(char* nBuffer, size_t nBufferSize) : MemoryStream() { - mBuffer = std::vector(nBuffer, nBuffer + nBufferSize); + mBuffer = std::make_shared>(nBuffer, nBuffer + nBufferSize); mBufferSize = nBufferSize; mBaseAddress = 0; } +LUS::MemoryStream::MemoryStream(std::shared_ptr> buffer) : MemoryStream() { + mBuffer = buffer; + mBufferSize = buffer->size(); + mBaseAddress = 0; +} + LUS::MemoryStream::~MemoryStream() { } uint64_t LUS::MemoryStream::GetLength() { - return mBuffer.size(); + return mBuffer->size(); } void LUS::MemoryStream::Seek(int32_t offset, SeekOffsetType seekType) { @@ -38,42 +44,42 @@ void LUS::MemoryStream::Seek(int32_t offset, SeekOffsetType seekType) { std::unique_ptr LUS::MemoryStream::Read(size_t length) { std::unique_ptr result = std::make_unique(length); - memcpy_s(result.get(), length, &mBuffer[mBaseAddress], length); + memcpy_s(result.get(), length, &mBuffer->at(mBaseAddress), length); mBaseAddress += length; return result; } void LUS::MemoryStream::Read(const char* dest, size_t length) { - memcpy_s((void*)dest, length, &mBuffer[mBaseAddress], length); + memcpy_s((void*)dest, length, &mBuffer->at(mBaseAddress), length); mBaseAddress += length; } int8_t LUS::MemoryStream::ReadByte() { - return mBuffer[mBaseAddress++]; + return mBuffer->at(mBaseAddress++); } void LUS::MemoryStream::Write(char* srcBuffer, size_t length) { - if (mBaseAddress + length >= mBuffer.size()) { - mBuffer.resize(mBaseAddress + length); + if (mBaseAddress + length >= mBuffer->size()) { + mBuffer->resize(mBaseAddress + length); mBufferSize += length; } - memcpy_s(&mBuffer[mBaseAddress], length, srcBuffer, length); + memcpy_s(&((*mBuffer)[mBaseAddress]), length, srcBuffer, length); mBaseAddress += length; } void LUS::MemoryStream::WriteByte(int8_t value) { - if (mBaseAddress >= mBuffer.size()) { - mBuffer.resize(mBaseAddress + 1); + if (mBaseAddress >= mBuffer->size()) { + mBuffer->resize(mBaseAddress + 1); mBufferSize = mBaseAddress; } - mBuffer[mBaseAddress++] = value; + mBuffer->at(mBaseAddress++) = value; } std::vector LUS::MemoryStream::ToVector() { - return mBuffer; + return *mBuffer; } void LUS::MemoryStream::Flush() { diff --git a/src/utils/binarytools/MemoryStream.h b/src/utils/binarytools/MemoryStream.h index 9f381699c..8d3815ae5 100644 --- a/src/utils/binarytools/MemoryStream.h +++ b/src/utils/binarytools/MemoryStream.h @@ -9,6 +9,7 @@ class MemoryStream : public Stream { public: MemoryStream(); MemoryStream(char* nBuffer, size_t nBufferSize); + MemoryStream(std::shared_ptr> buffer); ~MemoryStream(); uint64_t GetLength() override; @@ -28,7 +29,7 @@ class MemoryStream : public Stream { void Close() override; protected: - std::vector mBuffer; + std::shared_ptr> mBuffer; std::size_t mBufferSize; }; } // namespace LUS diff --git a/src/window/gui/GameOverlay.cpp b/src/window/gui/GameOverlay.cpp index 73f101a51..5cbe66d03 100644 --- a/src/window/gui/GameOverlay.cpp +++ b/src/window/gui/GameOverlay.cpp @@ -2,7 +2,7 @@ #include "public/bridge/consolevariablebridge.h" #include "resource/File.h" -#include "resource/Archive.h" +#include "resource/archive/Archive.h" #include "resource/ResourceManager.h" #include "Context.h" #include @@ -17,13 +17,12 @@ GameOverlay::~GameOverlay() { void GameOverlay::LoadFont(const std::string& name, const std::string& path, float fontSize) { ImGuiIO& io = ImGui::GetIO(); - std::shared_ptr base = Context::GetInstance()->GetResourceManager()->GetArchive(); - std::shared_ptr font = base->LoadFile(path, false); + std::shared_ptr font = Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFileRaw(path); if (font->IsLoaded) { // TODO: Nothing is ever unloading the font or this fontData array. - char* fontData = new char[font->Buffer.size()]; - memcpy(fontData, font->Buffer.data(), font->Buffer.size()); - mFonts[name] = io.Fonts->AddFontFromMemoryTTF(fontData, font->Buffer.size(), fontSize); + char* fontData = new char[font->Buffer->size()]; + memcpy(fontData, font->Buffer->data(), font->Buffer->size()); + mFonts[name] = io.Fonts->AddFontFromMemoryTTF(fontData, font->Buffer->size(), fontSize); } } diff --git a/src/window/gui/Gui.cpp b/src/window/gui/Gui.cpp index d393e2c86..ff45363b3 100644 --- a/src/window/gui/Gui.cpp +++ b/src/window/gui/Gui.cpp @@ -238,7 +238,7 @@ void Gui::LoadTexture(const std::string& name, const std::string& path) { asset.RendererTextureId = api->new_texture(); asset.Width = 0; asset.Height = 0; - uint8_t* imgData = stbi_load_from_memory(reinterpret_cast(res->Buffer.data()), res->Buffer.size(), + uint8_t* imgData = stbi_load_from_memory(reinterpret_cast(res->Buffer->data()), res->Buffer->size(), &asset.Width, &asset.Height, nullptr, 4); if (imgData == nullptr) {