diff --git a/.github/workflows/build-validation.yml b/.github/workflows/build-validation.yml index f858a73..29efc03 100644 --- a/.github/workflows/build-validation.yml +++ b/.github/workflows/build-validation.yml @@ -11,7 +11,7 @@ jobs: steps: - name: Install dependencies run: | - brew install sdl2 libpng glew ninja + brew install sdl2 libpng glew ninja libzip - uses: actions/checkout@v2 - name: ccache uses: hendrikmuhs/ccache-action@v1.2 @@ -34,7 +34,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y libsdl2-dev libpng-dev libglew-dev ninja-build + sudo apt-get install -y libsdl2-dev libpng-dev libglew-dev libzip-dev zipcmp zipmerge ziptool ninja-build - uses: actions/checkout@v2 - name: ccache uses: hendrikmuhs/ccache-action@v1.2 diff --git a/.github/workflows/tidy-format-validation.yml b/.github/workflows/tidy-format-validation.yml index 4ad7a93..5311827 100644 --- a/.github/workflows/tidy-format-validation.yml +++ b/.github/workflows/tidy-format-validation.yml @@ -10,7 +10,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y libsdl2-dev libpng-dev libglew-dev ninja-build clang-tidy clang-format-12 + sudo apt-get install -y libsdl2-dev libpng-dev libglew-dev libzip-dev zipcmp zipmerge ziptool ninja-build clang-tidy clang-format-12 - uses: actions/checkout@v3 with: fetch-depth: 2 diff --git a/CMakeLists.txt b/CMakeLists.txt index c71d708..e15e58b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,7 +27,7 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Windows" AND USE_AUTO_VCPKG) endif() vcpkg_bootstrap() - vcpkg_install_packages(zlib bzip2 sdl2 glew) + vcpkg_install_packages(zlib bzip2 sdl2 glew libzip) endif() add_subdirectory("extern") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index de1c2c6..6e56561 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -470,6 +470,9 @@ target_link_libraries(libultraship PUBLIC ZAPDUtils ImGui tinyxml2 nlohmann_json::nlohmann_json ) +find_package(libzip REQUIRED) +target_link_libraries(libultraship PRIVATE libzip::zip) + if (CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR CMAKE_SYSTEM_NAME STREQUAL "NintendoSwitch") set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) diff --git a/src/resource/archive/Archive.cpp b/src/resource/archive/Archive.cpp index 99d9096..926a660 100644 --- a/src/resource/archive/Archive.cpp +++ b/src/resource/archive/Archive.cpp @@ -26,7 +26,7 @@ Archive::~Archive() { } void Archive::Load() { - bool opened = LoadRaw(); + bool opened = Open(); auto t = LoadFileRaw("version"); bool isGameVersionValid = false; @@ -53,6 +53,7 @@ void Archive::Load() { } void Archive::Unload() { + Close(); SetLoaded(false); } @@ -103,9 +104,9 @@ void Archive::SetGameVersion(uint32_t gameVersion) { mGameVersion = gameVersion; } -void Archive::AddFile(const std::string& filePath) { +void Archive::IndexFile(const std::string& filePath) { if (filePath.length() > 5 && filePath.substr(filePath.length() - 5) == ".meta") { - AddFile(filePath.substr(0, filePath.length() - 5)); + IndexFile(filePath.substr(0, filePath.length() - 5)); return; } diff --git a/src/resource/archive/Archive.h b/src/resource/archive/Archive.h index d5d3d9f..8ca75e5 100644 --- a/src/resource/archive/Archive.h +++ b/src/resource/archive/Archive.h @@ -34,15 +34,15 @@ class Archive { const std::string& GetPath(); bool IsLoaded(); - virtual bool LoadRaw() = 0; - virtual bool UnloadRaw() = 0; + virtual bool Open() = 0; + virtual bool Close() = 0; virtual std::shared_ptr LoadFileRaw(const std::string& filePath) = 0; virtual std::shared_ptr LoadFileRaw(uint64_t hash) = 0; protected: void SetLoaded(bool isLoaded); void SetGameVersion(uint32_t gameVersion); - void AddFile(const std::string& filePath); + void IndexFile(const std::string& filePath); private: static std::shared_ptr CreateDefaultResourceInitData(); diff --git a/src/resource/archive/O2rArchive.cpp b/src/resource/archive/O2rArchive.cpp index 6acaf39..d5d5bf3 100644 --- a/src/resource/archive/O2rArchive.cpp +++ b/src/resource/archive/O2rArchive.cpp @@ -1,5 +1,6 @@ #include "O2rArchive.h" +#include "Context.h" #include "spdlog/spdlog.h" namespace LUS { @@ -11,18 +12,76 @@ O2rArchive::~O2rArchive() { } std::shared_ptr O2rArchive::LoadFileRaw(uint64_t hash) { - return nullptr; + const std::string& filePath = + *Context::GetInstance()->GetResourceManager()->GetArchiveManager()->HashToString(hash); + return LoadFileRaw(filePath); } std::shared_ptr O2rArchive::LoadFileRaw(const std::string& filePath) { - return nullptr; + if (mZipArchive == nullptr) { + SPDLOG_TRACE("Failed to open file {} from zip archive {}. Archive not open.", filePath, GetPath()); + return nullptr; + } + + auto zipEntryIndex = zip_name_locate(mZipArchive, filePath.c_str(), 0); + if (zipEntryIndex < 0) { + SPDLOG_TRACE("Failed to find file {} in zip archive {}.", filePath, GetPath()); + return nullptr; + } + + struct zip_stat zipEntryStat; + zip_stat_init(&zipEntryStat); + if (zip_stat_index(mZipArchive, zipEntryIndex, 0, &zipEntryStat) != 0) { + SPDLOG_TRACE("Failed to get entry information for file {} in zip archive {}.", filePath, GetPath()); + return nullptr; + } + + struct zip_file* zipEntryFile = zip_fopen_index(mZipArchive, zipEntryIndex, 0); + if (!zipEntryFile) { + SPDLOG_TRACE("Failed to open file {} in zip archive {}.", filePath, GetPath()); + return nullptr; + } + + auto fileToLoad = std::make_shared(); + fileToLoad->Buffer = std::make_shared>(zipEntryStat.size); + + if (zip_fread(zipEntryFile, fileToLoad->Buffer->data(), zipEntryStat.size) < 0) { + SPDLOG_TRACE("Error reading file {} in zip archive {}.", filePath, GetPath()); + } + + if (zip_fclose(zipEntryFile) != 0) { + SPDLOG_TRACE("Error closing file {} in zip archive {}.", filePath, GetPath()); + } + + fileToLoad->Parent = dynamic_pointer_cast(std::make_shared(std::move(*this))); + fileToLoad->IsLoaded = true; + + return fileToLoad; } -bool O2rArchive::LoadRaw() { - return false; +bool O2rArchive::Open() { + mZipArchive = zip_open(GetPath().c_str(), ZIP_RDONLY, nullptr); + if (mZipArchive == nullptr) { + SPDLOG_ERROR("Failed to load zip file \"{}\"", GetPath()); + return false; + } + + auto zipNumEntries = zip_get_num_entries(mZipArchive, 0); + for (auto i = 0; i < zipNumEntries; i++) { + auto zipEntryName = zip_get_name(mZipArchive, i, 0); + + IndexFile(zipEntryName); + } + + return true; } -bool O2rArchive::UnloadRaw() { - return false; +bool O2rArchive::Close() { + if (zip_close(mZipArchive) == -1) { + SPDLOG_ERROR("Failed to close zip file \"{}\"", GetPath()); + return false; + } + + return true; } } // namespace LUS diff --git a/src/resource/archive/O2rArchive.h b/src/resource/archive/O2rArchive.h index 168f4f9..3d198d0 100644 --- a/src/resource/archive/O2rArchive.h +++ b/src/resource/archive/O2rArchive.h @@ -6,6 +6,8 @@ #include #include +#include "zip.h" + #include "resource/File.h" #include "resource/Resource.h" #include "resource/archive/Archive.h" @@ -18,12 +20,13 @@ class O2rArchive : virtual public Archive { O2rArchive(const std::string& archivePath); ~O2rArchive(); - bool LoadRaw(); - bool UnloadRaw(); + bool Open(); + bool Close(); std::shared_ptr LoadFileRaw(const std::string& filePath); std::shared_ptr LoadFileRaw(uint64_t hash); protected: private: + zip_t* mZipArchive; }; } // namespace LUS diff --git a/src/resource/archive/OtrArchive.cpp b/src/resource/archive/OtrArchive.cpp index 45b7c6f..dd93c0e 100644 --- a/src/resource/archive/OtrArchive.cpp +++ b/src/resource/archive/OtrArchive.cpp @@ -9,6 +9,7 @@ namespace LUS { OtrArchive::OtrArchive(const std::string& archivePath) : Archive(archivePath) { + mHandle = nullptr; } OtrArchive::~OtrArchive() { @@ -16,6 +17,11 @@ OtrArchive::~OtrArchive() { } std::shared_ptr OtrArchive::LoadFileRaw(const std::string& filePath) { + if (mHandle == nullptr) { + SPDLOG_TRACE("Failed to open file {} from mpq archive {}. Archive not open.", filePath, GetPath()); + return nullptr; + } + HANDLE fileHandle; bool attempt = SFileOpenFileEx(mHandle, filePath.c_str(), 0, &fileHandle); if (!attempt) { @@ -56,7 +62,7 @@ std::shared_ptr OtrArchive::LoadFileRaw(uint64_t hash) { return LoadFileRaw(filePath); } -bool OtrArchive::LoadRaw() { +bool OtrArchive::Open() { const bool opened = SFileOpenArchive(GetPath().c_str(), 0, MPQ_OPEN_READ_ONLY, &mHandle); if (opened) { SPDLOG_INFO("Opened mpq file \"{}\"", GetPath()); @@ -79,13 +85,13 @@ bool OtrArchive::LoadRaw() { std::string_view line = lines[i].substr(0, lines[i].length() - 1); // Trim \r std::string lineStr = std::string(line); - AddFile(lineStr); + IndexFile(lineStr); } return opened; } -bool OtrArchive::UnloadRaw() { +bool OtrArchive::Close() { bool closed = SFileCloseArchive(mHandle); if (!closed) { SPDLOG_ERROR("({}) Failed to close mpq {}", GetLastError(), mHandle); diff --git a/src/resource/archive/OtrArchive.h b/src/resource/archive/OtrArchive.h index 424c2b8..9a69b7b 100644 --- a/src/resource/archive/OtrArchive.h +++ b/src/resource/archive/OtrArchive.h @@ -24,8 +24,8 @@ class OtrArchive : virtual public Archive { OtrArchive(const std::string& archivePath); ~OtrArchive(); - bool LoadRaw(); - bool UnloadRaw(); + bool Open(); + bool Close(); std::shared_ptr LoadFileRaw(const std::string& filePath); std::shared_ptr LoadFileRaw(uint64_t hash);