diff --git a/.idea/runConfigurations/Copy_Debug_DLLs_to_Debug_Version_of_Far_Manager.xml b/.idea/runConfigurations/Copy_x64_debug_to_Far.xml similarity index 59% rename from .idea/runConfigurations/Copy_Debug_DLLs_to_Debug_Version_of_Far_Manager.xml rename to .idea/runConfigurations/Copy_x64_debug_to_Far.xml index bce80b7..e0a01cc 100644 --- a/.idea/runConfigurations/Copy_Debug_DLLs_to_Debug_Version_of_Far_Manager.xml +++ b/.idea/runConfigurations/Copy_x64_debug_to_Far.xml @@ -1,14 +1,14 @@ - + \ No newline at end of file diff --git a/.idea/runConfigurations/Copy_Release_DLLs_to_Debug_Version_of_Far_Manager.xml b/.idea/runConfigurations/Copy_x64_release_to_Far.xml similarity index 59% rename from .idea/runConfigurations/Copy_Release_DLLs_to_Debug_Version_of_Far_Manager.xml rename to .idea/runConfigurations/Copy_x64_release_to_Far.xml index b2500a3..0c2d86e 100644 --- a/.idea/runConfigurations/Copy_Release_DLLs_to_Debug_Version_of_Far_Manager.xml +++ b/.idea/runConfigurations/Copy_x64_release_to_Far.xml @@ -1,14 +1,14 @@ - + \ No newline at end of file diff --git a/.idea/runConfigurations/Copy_x86_debug_to_Far.xml b/.idea/runConfigurations/Copy_x86_debug_to_Far.xml new file mode 100644 index 0000000..2096b9b --- /dev/null +++ b/.idea/runConfigurations/Copy_x86_debug_to_Far.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Copy_x86_release_to_Far.xml b/.idea/runConfigurations/Copy_x86_release_to_Far.xml new file mode 100644 index 0000000..808b0e3 --- /dev/null +++ b/.idea/runConfigurations/Copy_x86_release_to_Far.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Far_Manager__Debug_.xml b/.idea/runConfigurations/Far_Manager__Debug_.xml deleted file mode 100644 index 3fe8131..0000000 --- a/.idea/runConfigurations/Far_Manager__Debug_.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations/Far_Manager__Release_.xml b/.idea/runConfigurations/Far_Manager__Release_.xml deleted file mode 100644 index 3da7b08..0000000 --- a/.idea/runConfigurations/Far_Manager__Release_.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations/Far_x64_debug.xml b/.idea/runConfigurations/Far_x64_debug.xml new file mode 100644 index 0000000..bed2e21 --- /dev/null +++ b/.idea/runConfigurations/Far_x64_debug.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Far_x64_release.xml b/.idea/runConfigurations/Far_x64_release.xml new file mode 100644 index 0000000..3fe2c78 --- /dev/null +++ b/.idea/runConfigurations/Far_x64_release.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Far_x86_debug.xml b/.idea/runConfigurations/Far_x86_debug.xml new file mode 100644 index 0000000..f1492bb --- /dev/null +++ b/.idea/runConfigurations/Far_x86_debug.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Far_x86_release.xml b/.idea/runConfigurations/Far_x86_release.xml new file mode 100644 index 0000000..3dd962a --- /dev/null +++ b/.idea/runConfigurations/Far_x86_release.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index f387421..6508d49 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,7 +26,7 @@ find_package(ZLIB REQUIRED) find_path(ZSTR_INCLUDE_DIRS "zstr.hpp") set(ALL_MODULES renpy rpgmaker zanzarah) -set(RELEASED_MODULES renpy zanzarah) +set(RELEASED_MODULES renpy rpgmaker zanzarah) set(RENPY_DIR src/modules/renpy) add_library(renpy SHARED @@ -55,6 +55,7 @@ add_executable(tests src/tests/framework/observer.cpp src/tests/framework/testcase.cpp src/tests/renpy.cpp + src/tests/rpgmaker.cpp src/tests/zanzarah.cpp ) target_link_libraries(tests PRIVATE Catch2::Catch2WithMain) diff --git a/README.md b/README.md index 73916ce..5063a2e 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,15 @@ specific files as needed without having to unpack the entire archive. - Compatibility: RPA-2.0, RPA-3.0 - Description: Archives used in Ren'Py visual novels to store game resources (graphics, sound, scripts) +### RPG Maker + +- Supported extensions: `.rgss3a` +- Compatibility: RPG Maker VX Ace (RGSS3) +- Description: Encrypted archives containing game assets like graphics, audio, and data files + ### Zanzarah: The Hidden Portal -- Supported extension: `.pak` +- Supported extensions: `.pak` - Compatibility: Supports archives from both the original game version and the Steam version - Description: Archive used to store all game resources diff --git a/copy_dlls.cmd b/copy_dlls.cmd index d5d52c2..e12a55d 100644 --- a/copy_dlls.cmd +++ b/copy_dlls.cmd @@ -1,5 +1,5 @@ @echo off -pushd %~dp0\build\%1 +pushd %~dp0build\%1 set MODULES_DIR=%DEBUGFARHOME%\Plugins\Observer\modules copy /Y *.so %MODULES_DIR% || exit 1 copy /Y *.pdb %MODULES_DIR% diff --git a/src/archive.cpp b/src/archive.cpp index aae595c..e284497 100644 --- a/src/archive.cpp +++ b/src/archive.cpp @@ -86,6 +86,7 @@ namespace archive throw read_error(); } + uint32_t magic = file.magic; int64_t bytes_left = file.compressed_body_size_in_bytes; while (bytes_left > 0) { const auto chunk_size = static_cast(std::min(bytes_left, buffer_size)); @@ -96,8 +97,11 @@ namespace archive throw read_error(); } + buffer.resize(static_cast(chunk_size)); + magic = extractor_->decrypt(magic, buffer); + try { - output.write(buffer.data(), chunk_size); + output.write(buffer.data(), buffer.size()); } catch (std::ios_base::failure &) { throw write_error(); } diff --git a/src/modules/extractor.h b/src/modules/extractor.h index 86da9a5..5b5e566 100644 --- a/src/modules/extractor.h +++ b/src/modules/extractor.h @@ -47,15 +47,20 @@ namespace extractor int64_t offset; int64_t compressed_body_size_in_bytes; int64_t uncompressed_body_size_in_bytes; + uint32_t magic = 0; }; class extractor { public: + virtual ~extractor() = default; + static std::vector get_signature() noexcept; archive_info get_archive_info(const std::span &data) noexcept; std::vector > list_files(std::ifstream &stream); + + uint32_t decrypt(uint32_t magic, std::vector &data) const; }; } diff --git a/src/modules/renpy/renpy.cpp b/src/modules/renpy/renpy.cpp index d3c9399..63b047b 100644 --- a/src/modules/renpy/renpy.cpp +++ b/src/modules/renpy/renpy.cpp @@ -127,4 +127,9 @@ namespace extractor } return files; } + + uint32_t extractor::decrypt(uint32_t magic, std::vector &data) const + { + return magic; + } } diff --git a/src/modules/rpgmaker/rpgmaker.cpp b/src/modules/rpgmaker/rpgmaker.cpp index 65c55c0..af6baf0 100644 --- a/src/modules/rpgmaker/rpgmaker.cpp +++ b/src/modules/rpgmaker/rpgmaker.cpp @@ -25,12 +25,7 @@ namespace extractor // ReSharper disable once CppMemberFunctionMayBeStatic archive_info extractor::get_archive_info(const std::span &data) noexcept // NOLINT(*-convert-member-functions-to-static) { - // TODO implement: RGSS => RPG Maker XP, 0xDEADCAFE, XOR => zlib - // TODO implement: RGSS2 => RPG Maker VX, 0xCAFEDEAD, XOR => zlib - // TODO RPG Maker MV - // TODO RPG Maker MZ - // TODO .rvdata, .rvdata2 - return archive_info{L"RGSS3", L"DEFLATE", L"RPG Maker VX Ace"}; + return archive_info{L"RGSS3", L"-", L"RPG Maker VX Ace"}; } static uint32_t read_u32(std::ifstream &stream) @@ -68,8 +63,39 @@ namespace extractor new_file->offset = offset; new_file->compressed_body_size_in_bytes = size; new_file->uncompressed_body_size_in_bytes = size; + new_file->magic = file_magic; files.push_back(std::move(new_file)); } return files; } + + static uint32_t advance_magic(uint32_t &magic) + { + const uint32_t old = magic; + magic = magic * 7 + 3; + return old; + } + + // ReSharper disable once CppMemberFunctionMayBeStatic + uint32_t extractor::decrypt(uint32_t magic, std::vector &data) const // NOLINT(*-convert-member-functions-to-static) + { + const size_t size = data.size(); + size_t i = 0; + + while (i + 4 <= size) { + uint32_t value; + std::memcpy(&value, &data[i], sizeof(value)); + value ^= advance_magic(magic); + std::memcpy(&data[i], &value, sizeof(value)); + i += 4; + } + + while (i < size) { + const auto byte_value = static_cast(magic >> (i % 4 * 8)); + data[i] = static_cast(static_cast(data[i]) ^ byte_value); + ++i; + } + + return magic; + } } diff --git a/src/modules/zanzarah/zanzarah.cpp b/src/modules/zanzarah/zanzarah.cpp index ae619be..e4cb88f 100644 --- a/src/modules/zanzarah/zanzarah.cpp +++ b/src/modules/zanzarah/zanzarah.cpp @@ -87,4 +87,9 @@ namespace extractor return files; } + + uint32_t extractor::decrypt(uint32_t magic, std::vector &data) const + { + return magic; + } } diff --git a/src/tests/framework/observer.cpp b/src/tests/framework/observer.cpp index 5d80282..127930a 100644 --- a/src/tests/framework/observer.cpp +++ b/src/tests/framework/observer.cpp @@ -2,7 +2,6 @@ #include "../../api.h" #include -#include #include #include @@ -143,6 +142,7 @@ namespace test observer::observer() { modules_.push_back(std::make_unique("renpy.so")); + modules_.push_back(std::make_unique("rpgmaker.so")); modules_.push_back(std::make_unique("zanzarah.so")); } diff --git a/src/tests/rpgmaker.cpp b/src/tests/rpgmaker.cpp new file mode 100644 index 0000000..3e7840c --- /dev/null +++ b/src/tests/rpgmaker.cpp @@ -0,0 +1,50 @@ +#include + +#include "framework/testcase.h" + +using namespace test; + +TEST_CASE("rpgmaker: GirlsXLust_v1.0") +{ + test_on("rpgmaker\\GirlsXLust_v1.0.rgss3a"); +} + +TEST_CASE("rpgmaker: Maid-X-Demon-Mari-s-First-Job") +{ + test_on("rpgmaker\\Maid-X-Demon-Mari-s-First-Job.rgss3a"); +} + +TEST_CASE("rpgmaker: MaiDensnow_Eve") +{ + test_on("rpgmaker\\MaiDensnow_Eve.rgss3a"); +} + +TEST_CASE("rpgmaker: MaidsPerfect_v1.0a") +{ + test_on("rpgmaker\\MaidsPerfect_v1.0a.rgss3a"); +} + +TEST_CASE("rpgmaker: NotSoOrdinaryStory") +{ + test_on("rpgmaker\\NotSoOrdinaryStory.rgss3a"); +} + +TEST_CASE("rpgmaker: A_Song_of_Elfpai_and_Tentacles_1.20_ENGLISH_") +{ + test_on("rpgmaker\\RJ135050_-_A_Song_of_Elfpai_and_Tentacles_1.20_ENGLISH_.rgss3a"); +} + +TEST_CASE("rpgmaker: ThroughTheStaticAlpha_v0.1") +{ + test_on("rpgmaker\\ThroughTheStaticAlpha_v0.1.rgss3a"); +} + +TEST_CASE("rpgmaker: WarlockAndBoobs_v0.350.1.4") +{ + test_on("rpgmaker\\WarlockAndBoobs_v0.350.1.4.rgss3a"); +} + +TEST_CASE("rpgmaker: WarlockAndBoobs_v0.422.0.1") +{ + test_on("rpgmaker\\WarlockAndBoobs_v0.422.0.1.rgss3a"); +} \ No newline at end of file