diff --git a/.gitignore b/.gitignore index eb8e582..59bd76b 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ subprojects/safetyhook subprojects/zxing-cpp-2.2.1 subprojects/stb subprojects/zlib +subprojects/pugixml-1.14 libtomcrypt-1.18.2 dist.7z .vscode diff --git a/Makefile b/Makefile index deb08f3..928ba76 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,9 @@ all: @strip build/bnusio.dll setup: + @meson setup build --cross cross-mingw-64.txt + +clean-setup: @meson setup --wipe build --cross cross-mingw-64.txt clean: diff --git a/PLUGINS.md b/PLUGINS.md index 18f1e96..72a6df9 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -2,30 +2,30 @@ Plugins are just libraries with certain exported functions that go in the plugins folder. -``` +```c++ void Init() ``` Runs on bngrw_Init, may be a bit late for some things but should generally allow functions which would cause loader locks to run fine if a bit late. -``` +```c++ void Exit() ``` Runs on bnusio_Close, dispose of any data here. -``` +```c++ void Update() ``` Runs once per frame. -``` +```c++ void WaitTouch(i32 (*callback) (i32, i32, u8[168], u64), u64 data) ``` Runs on bngrw_reqWaitTouch. Call the callback like so when you wish to have a card scanned. -``` +```c++ u8 cardData[168]= { 0x01, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x92, 0x2E, 0x58, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x5C, 0x97, 0x44, 0xF0, 0x88, 0x04, 0x00, 0x43, 0x26, 0x2C, 0x33, 0x00, 0x04, 0x06, 0x10, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4E, 0x42, 0x47, 0x49, 0x43, 0x36, 0x00, 0x00, 0xFA, 0xE9, 0x69, 0x00, 0xF6, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; memcpy (cardData + 0x2C, chipId, 33); memcpy (cardData + 0x50, accessCode, 21); callback(0, 0, cardData, data); ``` -``` +```c++ void CardInsert() ``` Runs when user presses CARD_INSERT, causes TAL to not insert a card if any plugins have this present diff --git a/README.md b/README.md index 9d08c02..209430d 100644 --- a/README.md +++ b/README.md @@ -54,18 +54,9 @@ unlock_songs = true [patches.jpn39] # sync test mode language to attract etc fix_language = false - # stop timer count down - freeze_timer = false # use cn font and chineseS wordlist value chs_patch = false - # send result per song - instant_result = false - # enable one piece collab mode - mode_collabo025 = false - # enable ai soshina mode - mode_collabo026 = false - # enable aoharu no tatsujin mode - mode_aprilfool001 = false + # more options is now moved to testmode [emulation] # If usio emulation is disabled, you need to place bnusio_original.dll (unmodified bnusio.dll) in the executable folder. @@ -135,6 +126,21 @@ datatable_key = "" fumen_key = "" ``` +## TestMode options (JPN39 only) + +TaikoArcadeLoader offers several patches to select in TestMode + +The follow options are available in "MOD MANAGER" menu: +- FREEZE TIMER (stop timer count down) +- KIMETSU MODE (enable collabo024, will show a blank title) +- ONE PIECE MODE (enable collabo025) +- AI SOSHINA MODE (enable collabo026) +- AOHARU MODE (enable aprilfool001) +- INSTANT RESULT (send result per song) + +Enhanced original option: +- Louder volume (Speaker Volume is now up to 300%, **WARNING: May damage your speakers**) + ## Building TaikoArcadeLoader can be a bit tricky to build if you've never done it before. @@ -147,8 +153,8 @@ pip3 install meson npm install n -g && n latest && npm install --global xpm@latest npx xpm init && npx xpm install @xpack-dev-tools/mingw-w64-gcc@latest -# make sure to edit "path_to_tal" with the actual TaikoArcadeLoader folder path -export PATH=/path_to_tal/xpacks/.bin:$PATH +# make sure you entered TaikoArcadeLoader folder +export PATH=`pwd`/xpacks/.bin:$PATH make setup ``` diff --git a/dist/config.toml b/dist/config.toml index c2d50a0..09e5de9 100644 --- a/dist/config.toml +++ b/dist/config.toml @@ -18,12 +18,7 @@ unlock_songs = true [patches.jpn39] fix_language = false - freeze_timer = false chs_patch = false - instant_result = false - mode_collabo025 = false - mode_collabo026 = false - mode_aprilfool001 = false [emulation] usio = true diff --git a/dist/keyconfig.toml b/dist/keyconfig.toml index 35ac3e0..ad076e4 100644 --- a/dist/keyconfig.toml +++ b/dist/keyconfig.toml @@ -16,10 +16,10 @@ P1_LEFT_BLUE = ["D", "SDL_LTRIGGER"] P1_LEFT_RED = ["F", "SDL_LSTICK_PRESS"] P1_RIGHT_RED = ["J", "SDL_RSTICK_PRESS"] P1_RIGHT_BLUE = ["K", "SDL_RTRIGGER"] -P2_LEFT_BLUE = [] -P2_LEFT_RED = [] -P2_RIGHT_RED = [] -P2_RIGHT_BLUE = [] +P2_LEFT_BLUE = ["Z"] +P2_LEFT_RED = ["X"] +P2_RIGHT_RED = ["C"] +P2_RIGHT_BLUE = ["V"] # ESCAPE F1 through F12 # ` 1 through 0 -= BACKSPACE ^ YEN diff --git a/meson.build b/meson.build index 573dec6..69541b9 100644 --- a/meson.build +++ b/meson.build @@ -43,6 +43,11 @@ zlib_proj = subproject('zlib') zlib_dep = zlib_proj.get_variable('zlib_dep') libtomcrypt = subproject('libtomcrypt') libtomcrypt_dep = libtomcrypt.get_variable('tomcrypt_dep') +opt_var = cmake.subproject_options() +opt_var.set_override_option('cpp_std', 'c++23') +opt_var.add_cmake_defines({'PUGIXML_WCHAR_MODE': true}) +pugixml = cmake.subproject('pugixml', options: opt_var) +pugixml_dep = pugixml.get_variable('pugixml_static_dep') library( 'bnusio', @@ -69,6 +74,7 @@ library( zydis_dep, zlib_dep, libtomcrypt_dep, + pugixml_dep, ], sources : [ 'src/dllmain.cpp', @@ -81,6 +87,7 @@ library( 'src/patches/audio.cpp', 'src/patches/qr.cpp', 'src/patches/layeredfs.cpp', + 'src/patches/testmode.cpp', 'src/patches/versions/JPN00.cpp', 'src/patches/versions/JPN08.cpp', 'src/patches/versions/JPN39.cpp', diff --git a/src/bnusio.cpp b/src/bnusio.cpp index b773513..69a82aa 100644 --- a/src/bnusio.cpp +++ b/src/bnusio.cpp @@ -47,6 +47,7 @@ Keybindings P2_RIGHT_BLUE = {}; bool testEnabled = false; int coin_count = 0; +int service_count = 0; bool inited = false; HWND windowHandle = nullptr; HKL currentLayout; @@ -92,7 +93,6 @@ RETURN_FALSE (i64, bnusio_SetHopperLimit, u16 a1, i16 a2); RETURN_FALSE (i64, bnusio_SramRead, i32 a1, u8 a2, i32 a3, u16 a4); RETURN_FALSE (i64, bnusio_SramWrite, i32 a1, u8 a2, i32 a3, u16 a4); RETURN_FALSE (void *, bnusio_GetCoinError, i32 a1); -RETURN_FALSE (void *, bnusio_GetService, i32 a1); RETURN_FALSE (void *, bnusio_GetServiceError, i32 a1); RETURN_FALSE (i64, bnusio_DecCoin, i32 a1, u16 a2); RETURN_FALSE (i64, bnusio_DecService, i32 a1, u16 a2); @@ -183,7 +183,8 @@ bnusio_GetAnalogIn (u8 which) { } } -u16 bnusio_GetCoin (i32 a1) { return coin_count; } +u16 __fastcall bnusio_GetCoin (i32 a1) { return coin_count; } +u16 __fastcall bnusio_GetService (i32 a1) { return service_count; } } FUNCTION_PTR (i64, bnusio_Open_Original, PROC_ADDRESS ("bnusio_original.dll", "bnusio_Open")); @@ -417,6 +418,7 @@ Update () { UpdatePoll (windowHandle); if (IsButtonTapped (COIN_ADD) && !testEnabled) coin_count++; + if (IsButtonTapped (SERVICE) && !testEnabled) service_count++; if (IsButtonTapped (TEST)) testEnabled = !testEnabled; if (IsButtonTapped (EXIT)) ExitProcess (0); if (waitingForTouch) { diff --git a/src/dllmain.cpp b/src/dllmain.cpp index c7cceb9..2b31aa3 100644 --- a/src/dllmain.cpp +++ b/src/dllmain.cpp @@ -28,8 +28,6 @@ bool useLayeredFs = false; bool emulateUsio = true; bool emulateCardReader = true; bool emulateQr = true; -std::string datatableKey = "0000000000000000000000000000000000000000000000000000000000000000"; -std::string fumenKey = "0000000000000000000000000000000000000000000000000000000000000000"; HWND hGameWnd; HOOK (i32, ShowMouse, PROC_ADDRESS ("user32.dll", "ShowCursor"), bool) { return originalShowMouse.call (true); } @@ -180,12 +178,6 @@ DllMain (HMODULE module, DWORD reason, LPVOID reserved) { autoIme = readConfigBool (keyboard, "auto_ime", autoIme); jpLayout = readConfigBool (keyboard, "jp_layout", jpLayout); } - auto layeredFs = openConfigSection (config, "layeredfs"); - if (layeredFs) { - useLayeredFs = readConfigBool (layeredFs, "enabled", useLayeredFs); - datatableKey = readConfigString (layeredFs, "datatable_key", datatableKey); - fumenKey = readConfigString (layeredFs, "fumen_key", fumenKey); - } } if (version == "auto") { @@ -260,7 +252,8 @@ DllMain (HMODULE module, DWORD reason, LPVOID reserved) { patches::Audio::Init (); patches::Dxgi::Init (); patches::AmAuth::Init (); - if (useLayeredFs) patches::LayeredFs::Init (); + patches::LayeredFs::Init (); + patches::TestMode::Init (); } return true; } diff --git a/src/helpers.cpp b/src/helpers.cpp index ca437d8..14b4edc 100644 --- a/src/helpers.cpp +++ b/src/helpers.cpp @@ -108,4 +108,40 @@ printColour (int colour, const char *format, ...) { SetConsoleTextAttribute (consoleHandle, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED); va_end (args); +} + +std::wstring +replace (const std::wstring orignStr, const std::wstring oldStr, const std::wstring newStr) { + size_t pos = 0; + std::wstring tempStr = orignStr; + std::wstring::size_type newStrLen = newStr.length(); + std::wstring::size_type oldStrLen = oldStr.length(); + while(true) + { + pos = tempStr.find(oldStr, pos); + if (pos == std::wstring::npos) break; + + tempStr.replace(pos, oldStrLen, newStr); + pos += newStrLen; + } + + return tempStr; +} + +std::string +replace (const std::string orignStr, const std::string oldStr, const std::string newStr) { + size_t pos = 0; + std::string tempStr = orignStr; + std::string::size_type newStrLen = newStr.length(); + std::string::size_type oldStrLen = oldStr.length(); + while(true) + { + pos = tempStr.find(oldStr, pos); + if (pos == std::string::npos) break; + + tempStr.replace(pos, oldStrLen, newStr); + pos += newStrLen; + } + + return tempStr; } \ No newline at end of file diff --git a/src/helpers.h b/src/helpers.h index 9fa99d5..a4095dd 100644 --- a/src/helpers.h +++ b/src/helpers.h @@ -140,4 +140,6 @@ int64_t readConfigInt (toml_table_t *table, const std::string &key, int64_t notF const std::string readConfigString (toml_table_t *table, const std::string &key, const std::string ¬FoundValue); std::vector readConfigIntArray (toml_table_t *table, const std::string &key, std::vector notFoundValue); void printColour (int colour, const char *format, ...); +std::wstring replace (const std::wstring orignStr, const std::wstring oldStr, const std::wstring newStr); +std::string replace (const std::string orignStr, const std::string oldStr, const std::string newStr); std::vector directHooks = {}; \ No newline at end of file diff --git a/src/patches/layeredfs.cpp b/src/patches/layeredfs.cpp index 0148e4c..e34dc10 100644 --- a/src/patches/layeredfs.cpp +++ b/src/patches/layeredfs.cpp @@ -2,12 +2,23 @@ #include #include -extern std::string datatableKey; -extern std::string fumenKey; +bool useLayeredFs = false; +std::string datatableKey = "0000000000000000000000000000000000000000000000000000000000000000"; +std::string fumenKey = "0000000000000000000000000000000000000000000000000000000000000000"; #define CRCPOLY 0x82F63B78 namespace patches::LayeredFs { +class RegisteredHandler { +public: + std::function handlerMethod; + RegisteredHandler (const std::function &handlerMethod) { + this->handlerMethod = handlerMethod; + } +}; + +std::vector beforeHandlers = {}; +std::vector afterHandlers = {}; uint32_t CRC32C (uint32_t crc, const unsigned char *buf, size_t len) { @@ -167,9 +178,9 @@ IsFumenEncrypted (const std::string &filename) { return buffer != expected_bytes; } -HOOK (HANDLE, CreateFileAHook, PROC_ADDRESS ("kernel32.dll", "CreateFileA"), LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, - LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) { - std::filesystem::path path (lpFileName); +std::string +LayeredFsHandler (const std::string originalFileName, const std::string currentFileName) { + std::filesystem::path path (originalFileName.c_str()); if (!path.is_absolute ()) path = std::filesystem::absolute (path); auto originalDataFolder = std::filesystem::current_path ().parent_path ().parent_path () / "Data" / "x64"; auto originalLayeredFsFolder = std::filesystem::current_path ().parent_path ().parent_path () / "Data_mods" / "x64"; @@ -187,8 +198,7 @@ HOOK (HANDLE, CreateFileAHook, PROC_ADDRESS ("kernel32.dll", "CreateFileA"), LPC if (std::filesystem::exists (newPath)) { // If a file exists in the datamod folder if (IsFumenEncrypted (newPath)) { // And if it's an encrypted fumen or a different type of file, use it. std::cout << "Redirecting " << std::filesystem::relative (path).string () << std::endl; - return originalCreateFileAHook.call (newPath.c_str (), dwDesiredAccess, dwShareMode, lpSecurityAttributes, - dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); + return newPath; } else { // Otherwise if it's an unencrypted fumen. if (!std::filesystem::exists (encPath)) { // We check if we don't already have a cached file. if (fumenKey.length () == 64) { @@ -203,8 +213,7 @@ HOOK (HANDLE, CreateFileAHook, PROC_ADDRESS ("kernel32.dll", "CreateFileA"), LPC encPath = path.string (); } } else std::cout << "Using cached file for " << std::filesystem::relative (newPath) << std::endl; - return originalCreateFileAHook.call (encPath.c_str (), dwDesiredAccess, dwShareMode, lpSecurityAttributes, - dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); + return encPath; } } @@ -236,13 +245,45 @@ HOOK (HANDLE, CreateFileAHook, PROC_ADDRESS ("kernel32.dll", "CreateFileA"), LPC } else std::cout << "Using cached file for " << std::filesystem::relative (json_path) << std::endl; // Otherwise use the already encrypted file. - return originalCreateFileAHook.call (encPath.c_str (), dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, - dwFlagsAndAttributes, hTemplateFile); + return encPath; } } - return originalCreateFileAHook.call (lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, - dwFlagsAndAttributes, hTemplateFile); + return ""; +} + +HOOK ( + HANDLE, CreateFileAHook, PROC_ADDRESS ("kernel32.dll", "CreateFileA"), + LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, + DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile +) { + // std::wcout << "CreateFileA: file " << lpFileName << std::endl; + std::string originalFileName = std::string(lpFileName); + std::string currentFileName = originalFileName; + + if (!beforeHandlers.empty()) { + for (auto handler : beforeHandlers) { + std::string result = handler->handlerMethod(originalFileName, currentFileName); + if (result != "") currentFileName = result; + } + } + + if (useLayeredFs) { + std::string result = LayeredFsHandler(originalFileName, currentFileName); + if (result != "") currentFileName = result; + } + + if (!afterHandlers.empty()) { + for (auto handler : afterHandlers) { + std::string result = handler->handlerMethod(originalFileName, currentFileName); + if (result != "") currentFileName = result; + } + } + + return originalCreateFileAHook.call ( + currentFileName.c_str(), dwDesiredAccess, dwShareMode, lpSecurityAttributes, + dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile + ); } // HOOK (HANDLE, CreateFileWHook, PROC_ADDRESS ("kernel32.dll", "CreateFileW"), LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, @@ -274,10 +315,34 @@ HOOK (HANDLE, CreateFileAHook, PROC_ADDRESS ("kernel32.dll", "CreateFileA"), LPC void Init () { - std::wcout << "Using LayeredFs!" << std::endl; + auto configPath = std::filesystem::current_path () / "config.toml"; + std::unique_ptr config_ptr (openConfig (configPath), toml_free); + if (config_ptr) { + auto layeredFs = openConfigSection (config_ptr.get(), "layeredfs"); + if (layeredFs) { + useLayeredFs = readConfigBool (layeredFs, "enabled", useLayeredFs); + datatableKey = readConfigString (layeredFs, "datatable_key", datatableKey); + fumenKey = readConfigString (layeredFs, "fumen_key", fumenKey); + } + } + + if (useLayeredFs) { + std::wcout << "Using LayeredFs!" << std::endl; + } + register_cipher (&aes_desc); INSTALL_HOOK (CreateFileAHook); // INSTALL_HOOK (CreateFileWHook); } +void +RegisterBefore (const std::function &fileHandler) { + beforeHandlers.push_back (new RegisteredHandler(fileHandler)); +} + +void +RegisterAfter (const std::function &fileHandler) { + afterHandlers.push_back (new RegisteredHandler(fileHandler)); +} + } // namespace patches::LayeredFs diff --git a/src/patches/patches.h b/src/patches/patches.h index 9bf7927..6c9c27c 100644 --- a/src/patches/patches.h +++ b/src/patches/patches.h @@ -1,3 +1,8 @@ +#include +#include +#include +#include + namespace patches { namespace JPN00 { void Init (); @@ -30,5 +35,16 @@ void Init (); } // namespace AmAuth namespace LayeredFs { void Init (); +void RegisterBefore (const std::function &fileHandler); +void RegisterAfter (const std::function &fileHandler); } // namespace LayeredFs +namespace TestMode { +typedef u64 (*RefTestModeMain)(u64); +void Init (); +void SetupAccessor (u64 appAccessor, RefTestModeMain refTestMode); +int ReadTestModeValue (const wchar_t* itemId); +void RegisterItem (const std::wstring item, const std::function &initMethod); +void RegisterModify (const std::wstring query, const std::function &nodeModify, const std::function &initMethod); +void Append (pugi::xml_node &node, const wchar_t *attr, const std::wstring append); +} // namespace TestMode } // namespace patches diff --git a/src/patches/testmode.cpp b/src/patches/testmode.cpp new file mode 100644 index 0000000..493d5da --- /dev/null +++ b/src/patches/testmode.cpp @@ -0,0 +1,333 @@ +#include "constants.h" +#include "helpers.h" +#include "patches.h" +#include + +extern GameVersion gameVersion; + +bool chsPatch = false; + +namespace patches::TestMode { +class RegisteredItem { +public: + std::wstring selectItem; + std::function registerInit; + RegisteredItem (const std::wstring selectItem, const std::function &initMethod) { + this->selectItem = selectItem; + this->registerInit = initMethod; + } +}; +class RegisteredModify { +public: + std::wstring query; + std::function nodeModify; + std::function registerInit; + RegisteredModify (const std::wstring query, const std::function &nodeModify, const std::function &initMethod) { + this->query = query; + this->nodeModify = nodeModify; + this->registerInit = initMethod; + } +}; + +std::vector registeredItems = {}; +std::vector registeredModifies = {}; +std::wstring moddedInitial = L""; +std::wstring modded = L""; + +u64 appAccessor = 0; +RefTestModeMain refTestMode = nullptr; + +pugi::xml_document & +CreateMenu (pugi::xml_document &menuMain, std::wstring menuId, std::wstring menuName, std::vector items, std::wstring backId) { + std::wstring menuBasicLine = L""; + if (menuMain.load_string(menuBasicLine.c_str())) { + pugi::xml_node menu = menuMain.first_child (); + pugi::xml_node menuHeader = menu.append_child(L"layout"); + pugi::xml_node menuCenter = menu.append_child(L"layout"); + pugi::xml_node menuFooter = menu.append_child(L"layout"); + // Mod Manager Menu Header + menuHeader.append_attribute(L"type") = L"Header"; + menuHeader.append_child(L"break-item"); + pugi::xml_node menuTitle = menuHeader.append_child(L"text-item"); + std::wstring menuNameFull = L"   " + menuName; + menuTitle.append_attribute(L"label") = menuNameFull.c_str(); + menuHeader.append_child(L"break-item"); + menuHeader.append_child(L"break-item"); + pugi::xml_node menuTitleData = menuHeader.append_child(L"data-item"); + menuTitleData.append_attribute(L"data") = L"HeaderTextData"; + menuTitleData.append_attribute(L"param-offset-x") = L"-15"; + menuHeader.append_child(L"break-item"); + // Mod Manager Menu Center + menuCenter.append_attribute(L"type") = L"Center"; + menuCenter.append_attribute(L"padding-x") = L"23"; + for (std::wstring item : items) { + pugi::xml_document menuItem; + std::wstring itemLine = L"" + item + L""; + if (menuItem.load_string(itemLine.c_str())) { + menuCenter.append_copy(menuItem.first_child().first_child()); + } else { + std::wcerr << "[TestMode Error] failed to parse option line: " << item << std::endl; + } + menuCenter.append_child(L"break-item"); + } + menuCenter.append_child(L"break-item"); + pugi::xml_node menuCenterExit = menuCenter.append_child(L"menu-item"); + menuCenterExit.append_attribute(L"label") = L"EXIT"; + menuCenterExit.append_attribute(L"menu") = backId.c_str(); + // Mod Manager Menu Footer + menuFooter.append_attribute(L"type") = L"Footer"; + pugi::xml_node menuFooterData = menuFooter.append_child(L"data-item"); + menuFooterData.append_attribute(L"data") = L"FooterTextData"; + menuFooterData.append_attribute(L"param-offset-x") = L"-20"; + } + return menuMain; +} + +std::wstring +ReadXMLFileSwitcher (std::wstring &fileName) { + std::size_t pos = fileName.rfind(L"/"); + std::wstring base = fileName.substr(0, pos + 1); + std::wstring file = fileName.substr(pos + 1, fileName.size()); + + if (gameVersion == GameVersion::JPN39 && chsPatch) { + if (file.starts_with (L"DeviceInitialize")) base.append(L"DeviceInitialize_china.xml"); + if (file.starts_with (L"TestMode")) base.append(L"TestMode_china.xml"); + if (std::filesystem::exists (base)) return base; + } + + return fileName; +} + +HOOK_DYNAMIC (void, TestModeSetMenuHook, u64 testModeLibrary, const wchar_t* lFileName) { + std::wstring originalFileName = std::wstring(lFileName); + std::wstring fileName = originalFileName; + if (fileName.ends_with (L"DeviceInitialize.xml") || fileName.ends_with (L"DeviceInitialize_asia.xml") || fileName.ends_with (L"DeviceInitialize_china.xml")) { + if (moddedInitial == L"") { + fileName = ReadXMLFileSwitcher (fileName); + pugi::xml_document doc; + if (!doc.load_file(fileName.c_str())) { + std::wcerr << "Loading DeviceInitialize structure failed! path: " << fileName << std::endl; + moddedInitial = fileName; + } else { + std::wstring modFileName = replace (replace (replace (fileName, L"lize_asia.xml", L"lize_mod.xml"), L"lize_china.xml", L"lize_mod.xml"), L"lize.xml", L"lize_mod.xml"); + pugi::xpath_query dongleQuery = pugi::xpath_query(L"/root/menu[@id='TopMenu']/layout[@type='Center']/select-item[@id='DongleItem']"); + pugi::xml_node dongleItem = doc.select_node(dongleQuery).node(); + pugi::xml_node talItem = dongleItem.parent().append_copy(dongleItem); + talItem.attribute(L"label").set_value(L"TAIKOARCADELOADER"); + talItem.attribute(L"id").set_value(L"TaikoArcadeLoader"); + talItem.append_attribute(L"default") = L"1"; + dongleItem.parent().append_child(L"break-item"); + + doc.save_file(modFileName.c_str()); + moddedInitial = modFileName; + fileName = modFileName; + } + } else fileName = moddedInitial; + } else if (fileName.ends_with (L"TestMode.xml") || fileName.ends_with (L"TestMode_asia.xml") || fileName.ends_with (L"TestMode_china.xml")) { + if (modded == L"") { + if (!registeredItems.empty() || !registeredModifies.empty()) { + fileName = ReadXMLFileSwitcher (fileName); + pugi::xml_document doc; + if (!doc.load_file(fileName.c_str())) { + std::wcerr << "Loading TestMode structure failed! path: " << fileName << std::endl; + modded = fileName; + } else { + std::wstring modFileName = replace (replace (replace (fileName, L"Mode_asia.xml", L"Mode_mod.xml"), L"Mode_china.xml", L"Mode_mod.xml"), L"Mode.xml", L"Mode_mod.xml"); + if (!registeredItems.empty()) { + pugi::xpath_query menuQuery = pugi::xpath_query(L"/root/menu[@id='TopMenu']/layout[@type='Center']/menu-item[@menu='GameOptionsMenu']"); + pugi::xml_node menuItem = doc.select_node(menuQuery).node(); + menuItem = menuItem.next_sibling(); + pugi::xml_node modMenuEntry = menuItem.parent().insert_child_after(L"menu-item", menuItem); + modMenuEntry.append_attribute(L"label") = L"MOD MANAGER"; + modMenuEntry.append_attribute(L"menu") = L"ModManagerMenu"; + menuItem.parent().insert_child_after(L"break-item", modMenuEntry); + + pugi::xml_document modMenu; + std::vector toInsertItems = {}; + for (RegisteredItem *item : registeredItems) { + toInsertItems.push_back(item->selectItem); + item->registerInit(); + } + CreateMenu(modMenu, L"ModManagerMenu", L"MOD MANAGER", toInsertItems, L"TopMenu"); + pugi::xpath_query topMenuQuery = pugi::xpath_query(L"/root/menu[@id='TopMenu']"); + pugi::xml_node topMenu = doc.select_node(topMenuQuery).node(); + topMenu.parent().insert_copy_after(modMenu.first_child(), topMenu); + } + + if (!registeredModifies.empty()) { + for (RegisteredModify *modify : registeredModifies) { + pugi::xpath_query modifyQuery = pugi::xpath_query(modify->query.c_str()); + try { + pugi::xml_node modifyNode = doc.select_node(modifyQuery).node(); + if (modifyNode) { + modify->nodeModify(modifyNode); + modify->registerInit(); + } + } catch (std::exception &e) { + std::wcerr << "[TestMode Error] failed to find node by xpath: " << modify->query << std::endl; + } + } + } + + doc.save_file(modFileName.c_str()); + modded = modFileName; + fileName = modFileName; + } + } else modded = fileName; + } else fileName = modded; + } + + std::wcout << "TestModeLibrary load: " << fileName << std::endl; + + originalTestModeSetMenuHook.call (testModeLibrary, fileName.c_str()); +} + +void +CommonModify () { + // Default off Close time + TestMode::RegisterModify( + L"/root/menu[@id='CloseTimeSettingMenu']/layout[@type='Center']/select-item[@id='ScheduleTypeItem']", + [&](pugi::xml_node &node) { node.attribute(L"default").set_value(L"0"); }, [&](){} + ); +} + +void +LocalizationCHT () { + TestMode::RegisterModify( + L"/root/menu[@id='TopMenu']/layout[@type='Center']/menu-item[@menu='ModManagerMenu']", + [&](pugi::xml_node &node) { node.attribute(L"label").set_value(L"模組管理"); }, [](){} + ); + TestMode::RegisterModify( + L"/root/menu[@id='ModManagerMenu']/layout[@type='Header']/text-item", + [&](pugi::xml_node &node) { node.attribute(L"label").set_value(L"模組管理"); }, [](){} + ); + TestMode::RegisterModify( + L"/root/menu[@id='ModManagerMenu']/layout[@type='Center']/select-item[@id='ModFreezeTimer']", + [&](pugi::xml_node &node) { node.attribute(L"label").set_value(L"凍結計時"); node.attribute(L"replace-text").set_value(L"0:關閉, 1:開啓"); }, [](){} + ); + TestMode::RegisterModify( + L"/root/menu[@id='ModManagerMenu']/layout[@type='Center']/select-item[@id='ModModeCollabo024']", + [&](pugi::xml_node &node) { node.attribute(L"label").set_value(L"鬼滅之刃模式"); node.attribute(L"replace-text").set_value(L"0:黙認, 1:啓用, 2:僅刷卡"); }, [](){} + ); + TestMode::RegisterModify( + L"/root/menu[@id='ModManagerMenu']/layout[@type='Center']/select-item[@id='ModModeCollabo025']", + [&](pugi::xml_node &node) { node.attribute(L"label").set_value(L"航海王模式"); node.attribute(L"replace-text").set_value(L"0:黙認, 1:啓用, 2:僅刷卡"); }, [](){} + ); + TestMode::RegisterModify( + L"/root/menu[@id='ModManagerMenu']/layout[@type='Center']/select-item[@id='ModModeCollabo026']", + [&](pugi::xml_node &node) { node.attribute(L"label").set_value(L"AI粗品模式"); node.attribute(L"replace-text").set_value(L"0:黙認, 1:啓用, 2:僅刷卡"); }, [](){} + ); + TestMode::RegisterModify( + L"/root/menu[@id='ModManagerMenu']/layout[@type='Center']/select-item[@id='ModModeAprilFool001']", + [&](pugi::xml_node &node) { node.attribute(L"label").set_value(L"青春之達人模式"); node.attribute(L"replace-text").set_value(L"0:黙認, 1:啓用, 2:僅刷卡"); }, [](){} + ); + TestMode::RegisterModify( + L"/root/menu[@id='ModManagerMenu']/layout[@type='Center']/menu-item[@menu='TopMenu']", + [&](pugi::xml_node &node) { node.attribute(L"label").set_value(L"離開"); }, [](){} + ); +} + +void +LocalizationCHS () { + TestMode::RegisterModify( + L"/root/menu[@id='TopMenu']/layout[@type='Center']/menu-item[@menu='ModManagerMenu']", + [&](pugi::xml_node &node) { node.attribute(L"label").set_value(L"模组管理"); }, [](){} + ); + TestMode::RegisterModify( + L"/root/menu[@id='ModManagerMenu']/layout[@type='Header']/text-item", + [&](pugi::xml_node &node) { node.attribute(L"label").set_value(L"模组管理"); }, [](){} + ); + TestMode::RegisterModify( + L"/root/menu[@id='ModManagerMenu']/layout[@type='Center']/select-item[@id='ModFreezeTimer']", + [&](pugi::xml_node &node) { node.attribute(L"label").set_value(L"冻结计时"); node.attribute(L"replace-text").set_value(L"0:禁用, 1:启用"); }, [](){} + ); + TestMode::RegisterModify( + L"/root/menu[@id='ModManagerMenu']/layout[@type='Center']/select-item[@id='ModModeCollabo024']", + [&](pugi::xml_node &node) { node.attribute(L"label").set_value(L"鬼灭之刃模式"); node.attribute(L"replace-text").set_value(L"0:默认, 1:启用, 2:仅刷卡"); }, [](){} + ); + TestMode::RegisterModify( + L"/root/menu[@id='ModManagerMenu']/layout[@type='Center']/select-item[@id='ModModeCollabo025']", + [&](pugi::xml_node &node) { node.attribute(L"label").set_value(L"航海王模式"); node.attribute(L"replace-text").set_value(L"0:默认, 1:启用, 2:仅刷卡"); }, [](){} + ); + TestMode::RegisterModify( + L"/root/menu[@id='ModManagerMenu']/layout[@type='Center']/select-item[@id='ModModeCollabo026']", + [&](pugi::xml_node &node) { node.attribute(L"label").set_value(L"AI粗品模式"); node.attribute(L"replace-text").set_value(L"0:默认, 1:启用, 2:仅刷卡"); }, [](){} + ); + // TestMode::RegisterModify( + // L"/root/menu[@id='ModManagerMenu']/layout[@type='Center']/select-item[@id='ModModeAprilFool001']", + // [&](pugi::xml_node &node) { node.attribute(L"label").set_value(L"青春之达人模式"); node.attribute(L"replace-text").set_value(L"0:默认, 1:启用, 2:仅刷卡"); }, [](){} + // ); + TestMode::RegisterModify( + L"/root/menu[@id='ModManagerMenu']/layout[@type='Center']/menu-item[@menu='TopMenu']", + [&](pugi::xml_node &node) { node.attribute(L"label").set_value(L"离开"); }, [](){} + ); +} + +void +Init () { + auto configPath = std::filesystem::current_path () / "config.toml"; + std::unique_ptr config_ptr (openConfig (configPath), toml_free); + + u64 testModeSetMenuAddress = PROC_ADDRESS_OFFSET ("TestModeLibrary.dll", 0x99D0); + switch (gameVersion) { + case GameVersion::UNKNOWN: break; + case GameVersion::JPN00: break; + case GameVersion::JPN08: break; + case GameVersion::JPN39: { + if (config_ptr) { + auto patches = openConfigSection (config_ptr.get (), "patches"); + if (patches) { + auto jpn39 = openConfigSection (patches, "jpn39"); + if (jpn39) { + chsPatch = readConfigBool (jpn39, "chs_patch", chsPatch); + } + } + } + if (chsPatch) LocalizationCHT(); + } break; + case GameVersion::CHN00: break; + } + + CommonModify (); + + INSTALL_HOOK_DYNAMIC (TestModeSetMenuHook, testModeSetMenuAddress); +} + +void +SetupAccessor (u64 appAccessor, RefTestModeMain refTestMode) { + patches::TestMode::appAccessor = appAccessor; + patches::TestMode::refTestMode = refTestMode; +} + +int +ReadTestModeValue (const wchar_t* itemId) { + if (appAccessor) { + u64 testModeMain = refTestMode (appAccessor); + if (testModeMain) { + int value = 0; + u64* reader = *(u64**)(testModeMain + 16); + (*(void (__fastcall **)(u64*, const wchar_t *, int *))(*reader + 256))(reader, itemId, &value); + return value; + } + } + std::wcerr << "Read TestMode(" << itemId << ") failed!" << std::endl; + return -1; +} + +void +RegisterItem (const std::wstring item, const std::function &initMethod) { + registeredItems.push_back(new RegisteredItem(item, initMethod)); +} + +void +RegisterModify (const std::wstring query, const std::function &nodeModify, const std::function &initMethod) { + registeredModifies.push_back(new RegisteredModify(query, nodeModify, initMethod)); +} + +void +Append (pugi::xml_node &node, const wchar_t *attr, const std::wstring append) { + pugi::xml_attribute attribute = node.attribute (attr); + std::wstring attrValue = std::wstring(attribute.value ()) + append; + attribute.set_value (attrValue.c_str ()); +} +} \ No newline at end of file diff --git a/src/patches/versions/CHN00.cpp b/src/patches/versions/CHN00.cpp index 622fae1..926e286 100644 --- a/src/patches/versions/CHN00.cpp +++ b/src/patches/versions/CHN00.cpp @@ -1,5 +1,5 @@ -#include "../patches.h" #include "helpers.h" +#include "../patches.h" #include extern std::string chassisId; diff --git a/src/patches/versions/JPN00.cpp b/src/patches/versions/JPN00.cpp index e202512..d1c349c 100644 --- a/src/patches/versions/JPN00.cpp +++ b/src/patches/versions/JPN00.cpp @@ -1,5 +1,5 @@ -#include "../patches.h" #include "helpers.h" +#include "../patches.h" #include extern u64 song_data_size; diff --git a/src/patches/versions/JPN08.cpp b/src/patches/versions/JPN08.cpp index 536d9f2..86dbcf3 100644 --- a/src/patches/versions/JPN08.cpp +++ b/src/patches/versions/JPN08.cpp @@ -1,5 +1,5 @@ -#include "../patches.h" #include "helpers.h" +#include "../patches.h" #include extern u64 song_data_size; diff --git a/src/patches/versions/JPN39.cpp b/src/patches/versions/JPN39.cpp index 664cba4..fd3cbba 100644 --- a/src/patches/versions/JPN39.cpp +++ b/src/patches/versions/JPN39.cpp @@ -1,5 +1,5 @@ -#include "../patches.h" #include "helpers.h" +#include "../patches.h" #include #include @@ -30,23 +30,77 @@ FUNCTION_PTR (i32, luaL_loadstring, PROC_ADDRESS ("lua51.dll", "luaL_loadstring" #define LUA_MULTRET (-1) #define luaL_dostring(L, s) (luaL_loadstring (L, s) || lua_pcall (L, 0, LUA_MULTRET, 0)) +FUNCTION_PTR (u64, RefTestModeMain, ASLR (0x1400337C0), u64); +FUNCTION_PTR (u64, RefPlayDataManager, ASLR (0x140024AC0), u64); +FUNCTION_PTR (i64, GetUserCount, ASLR (0x1403F1020), u64); + i64 -lua_pushtrue (i64 a1) { +lua_pushbool (i64 a1, bool val) { lua_settop (a1, 0); - lua_pushboolean (a1, 1); + lua_pushboolean (a1, val); return 1; } -HOOK (i64, AvailableMode_Collabo024, ASLR (0x1402DE710), i64 a1) { return lua_pushtrue (a1); } -HOOK (i64, AvailableMode_Collabo025, ASLR (0x1402DE6B0), i64 a1) { return lua_pushtrue (a1); } -HOOK (i64, AvailableMode_Collabo026, ASLR (0x1402DE670), i64 a1) { return lua_pushtrue (a1); } -HOOK (i64, AvailableMode_AprilFool001, ASLR (0x1402DE5B0), i64 a1) { return lua_pushtrue (a1); } - -i64 -lua_freeze_timer (i64 a1) { - return lua_pushtrue (a1); +u64 appAccessor = 0; +u64 componentAccessor = 0; +HOOK (i64, DeviceCheck, ASLR(0x140464FC0), i64 a1, i64 a2, i64 a3) { + TestMode::SetupAccessor (a3, RefTestModeMain); + componentAccessor = a2; + return originalDeviceCheck.call (a1, a2, a3); } +int +GetUserStatus () { + if (appAccessor) { + u64 playDataManager = RefPlayDataManager (appAccessor); + if (playDataManager) return GetUserCount(playDataManager); + } + return -1; +} + +HOOK (i64, AvailableMode_Collabo024, ASLR (0x1402DE710), i64 a1) { + int tournamentMode = TestMode::ReadTestModeValue (L"TournamentMode"); + if (tournamentMode == 1) return originalAvailableMode_Collabo024.call (a1); + int status = TestMode::ReadTestModeValue (L"ModModeCollabo024"); + if (status == 1 && GetUserStatus () == 1) { + return lua_pushbool (a1, true); + } + return originalAvailableMode_Collabo024.call (a1); +} +HOOK (i64, AvailableMode_Collabo025, ASLR (0x1402DE6B0), i64 a1) { + int tournamentMode = TestMode::ReadTestModeValue (L"TournamentMode"); + if (tournamentMode == 1) return originalAvailableMode_Collabo025.call (a1); + int status = TestMode::ReadTestModeValue (L"ModModeCollabo025"); + if (status == 1 && GetUserStatus () == 1) { + return lua_pushbool (a1, true); + } + return originalAvailableMode_Collabo025.call (a1); +} +HOOK (i64, AvailableMode_Collabo026, ASLR (0x1402DE670), i64 a1) { + int tournamentMode = TestMode::ReadTestModeValue (L"TournamentMode"); + if (tournamentMode == 1) return originalAvailableMode_Collabo026.call (a1); + int status = TestMode::ReadTestModeValue (L"ModModeCollabo026"); + if (status == 1 && GetUserStatus () == 1) { + return lua_pushbool (a1, true); + } + return originalAvailableMode_Collabo026.call (a1); +} +HOOK (i64, AvailableMode_AprilFool001, ASLR (0x1402DE5B0), i64 a1) { + int tournamentMode = TestMode::ReadTestModeValue (L"TournamentMode"); + if (tournamentMode == 1) return originalAvailableMode_AprilFool001.call (a1); + int status = TestMode::ReadTestModeValue (L"ModModeAprilFool001"); + if (status == 1) { + return lua_pushbool (a1, true); + } + return originalAvailableMode_AprilFool001.call (a1); +} +i64 __fastcall lua_freeze_timer (i64 a1) { + int tournamentMode = TestMode::ReadTestModeValue (L"TournamentMode"); + if (tournamentMode == 1) return lua_pushbool (a1, true); + int status = TestMode::ReadTestModeValue (L"ModFreezeTimer"); + if (status == 1) return lua_pushbool (a1, true); + return lua_pushbool (a1, false); +} MID_HOOK (FreezeTimer, ASLR (0x14019FF51), SafetyHookContext &ctx) { auto a1 = ctx.rdi; int v9 = (int)(ctx.rax + 1); @@ -103,11 +157,15 @@ ExecuteSendResultData () { } bool sendFlag = false; -#define SCENE_RESULT_HOOK(functionName, location) \ - HOOK (void, functionName, location, i64 a1, i64 a2, i64 a3) { \ - sendFlag = true; \ - original##functionName.call (a1, a2, a3); \ - ExecuteSendResultData (); \ +#define SCENE_RESULT_HOOK(functionName, location) \ + HOOK (void, functionName, location, i64 a1, i64 a2, i64 a3) { \ + if ( \ + TestMode::ReadTestModeValue (L"ModInstantResult") != 1 && \ + TestMode::ReadTestModeValue (L"NumberOfStageItem") <= 4 \ + ) { original##functionName.call (a1, a2, a3); return; } \ + sendFlag = true; \ + original##functionName.call (a1, a2, a3); \ + ExecuteSendResultData (); \ } SCENE_RESULT_HOOK (SceneResultInitialize_Enso, ASLR (0x140411FD0)); @@ -116,12 +174,16 @@ SCENE_RESULT_HOOK (SceneResultInitialize_Collabo025, ASLR (0x140411FD0)); SCENE_RESULT_HOOK (SceneResultInitialize_Collabo026, ASLR (0x140411FD0)); SCENE_RESULT_HOOK (SceneResultInitialize_AprilFool, ASLR (0x140411FD0)); -#define SEND_RESULT_HOOK(functionName, location) \ - HOOK (void, functionName, location, i64 a1) { \ - if (sendFlag) { \ - sendFlag = false; \ - original##functionName.call (a1); \ - } \ +#define SEND_RESULT_HOOK(functionName, location) \ + HOOK (void, functionName, location, i64 a1) { \ + if ( \ + TestMode::ReadTestModeValue (L"ModInstantResult") != 1 && \ + TestMode::ReadTestModeValue (L"NumberOfStageItem") <= 4 \ + ) { original##functionName.call (a1); return; } \ + if (sendFlag) { \ + sendFlag = false; \ + original##functionName.call (a1); \ + } \ } SEND_RESULT_HOOK (SendResultData_Enso, ASLR (0x1401817B0)); @@ -131,7 +193,11 @@ SEND_RESULT_HOOK (SendResultData_AprilFool, ASLR (0x140177800)); #define CHANGE_RESULT_SIZE_HOOK(functionName, location, target) \ MID_HOOK (functionName, location, SafetyHookContext &ctx) { \ - i64 instance = GetPlayDataManagerRef (*(i64 *)ctx.r12); \ + if ( \ + TestMode::ReadTestModeValue (L"ModInstantResult") != 1 && \ + TestMode::ReadTestModeValue (L"NumberOfStageItem") <= 4 \ + ) { return; } \ + i64 instance = RefPlayDataManager (*(i64 *)ctx.r12); \ u32 currentStageCount = *(u32 *)(instance + 8); \ ctx.target &= 0xFFFFFFFF00000000; \ ctx.target |= currentStageCount; \ @@ -144,7 +210,11 @@ CHANGE_RESULT_SIZE_HOOK (ChangeResultDataSize_AprilFool, ASLR (0x140176044), rax #define CHANGE_RESULT_INDEX_HOOK(functionName, location, target, offset, skip) \ MID_HOOK (functionName, location, SafetyHookContext &ctx) { \ - i64 instance = GetPlayDataManagerRef (*(i64 *)ctx.r12); \ + if ( \ + TestMode::ReadTestModeValue (L"ModInstantResult") != 1 && \ + TestMode::ReadTestModeValue (L"NumberOfStageItem") <= 4 \ + ) { return; } \ + i64 instance = RefPlayDataManager (*(i64 *)ctx.r12); \ u32 currentStageCount = *(u32 *)(instance + 8); \ ctx.target &= 0xFFFFFFFF00000000; \ ctx.target |= currentStageCount - 1; \ @@ -189,6 +259,10 @@ MID_HOOK (ChangeLanguageType, ASLR (0x1400B2016), SafetyHookContext &ctx) { if (*pFontType == 4) *pFontType = 2; } +MID_HOOK (CountLockedCrown, ASLR (0x1403F2A25), SafetyHookContext &ctx) { + ctx.r15 |= 1; +} + std::map nus3bankMap; int nus3bankIdCounter = 0; std::map voiceCnExist; @@ -328,6 +402,32 @@ HOOK (i64, LoadedBankAll, ASLR (0x1404C69F0), i64 a1) { return 1; } +float soundRate = 1.0F; +HOOK (i32, SetMasterVolumeSpeaker, ASLR (0x140160330), i32 a1) { + soundRate = a1 <= 100 ? 1.0F : a1 / 100.0; + return originalSetMasterVolumeSpeaker.call (a1 > 100 ? 100 : a1); +} + +HOOK (u64, NuscBusVolume, ASLR (0x1407B1C30), u64 a1, u64 a2, float a3) { + return originalNuscBusVolume.call (a1, a2, a3 * soundRate); +} + +std::string* fontName = nullptr; +HOOK (u8, SetupFontInfo, ASLR (0x14049D820), u64 a1, u64 a2, size_t a3, u64 a4) { + if (fontName != nullptr) delete fontName; + fontName = new std::string(((char*)a1) + 120); + return originalSetupFontInfo.call (a1, a2, a3, a4); +} + +HOOK (u32, ReadFontInfoInt, ASLR (0x14049EAC0), u64 a1, u64 a2) { + std::string attribute((char*)a2); + u32 result = originalReadFontInfoInt.call (a1, a2); + if (fontName->starts_with("cn_") && attribute == "offsetV") { + result += 1; + } + return result; +} + const i32 datatableBufferSize = 1024 * 1024 * 12; safetyhook::Allocation datatableBuffer1; safetyhook::Allocation datatableBuffer2; @@ -362,12 +462,7 @@ Init () { bool vsync = false; bool unlockSongs = true; bool fixLanguage = false; - bool freezeTimer = false; bool chsPatch = false; - bool instantResult = false; - bool modeCollabo025 = false; - bool modeCollabo026 = false; - bool modeAprilFool001 = false; bool useLayeredfs = false; @@ -380,12 +475,7 @@ Init () { auto jpn39 = openConfigSection (patches, "jpn39"); if (jpn39) { fixLanguage = readConfigBool (jpn39, "fix_language", fixLanguage); - freezeTimer = readConfigBool (jpn39, "freeze_timer", freezeTimer); chsPatch = readConfigBool (jpn39, "chs_patch", chsPatch); - instantResult = readConfigBool (jpn39, "instant_result", instantResult); - modeCollabo025 = readConfigBool (jpn39, "mode_collabo025", modeCollabo025); - modeCollabo026 = readConfigBool (jpn39, "mode_collabo026", modeCollabo026); - modeAprilFool001 = readConfigBool (jpn39, "mode_aprilfool001", modeAprilFool001); } } @@ -403,11 +493,18 @@ Init () { if (layeredfs) useLayeredfs = readConfigBool (layeredfs, "enabled", useLayeredfs); } + // Hook to get AppAccessor and ComponentAccessor + INSTALL_HOOK (DeviceCheck); + INSTALL_HOOK (luaL_newstate); + // Apply common config patch WRITE_MEMORY (ASLR (0x140494533), i32, xRes); WRITE_MEMORY (ASLR (0x14049453A), i32, yRes); if (!vsync) WRITE_MEMORY (ASLR (0x14064C7E9), u8, 0xBA, 0x00, 0x00, 0x00, 0x00, 0x90); - if (unlockSongs) WRITE_MEMORY (ASLR (0x1403F45CF), u8, 0xB0, 0x01); + if (unlockSongs) { + WRITE_MEMORY (ASLR (0x1403F45CF), u8, 0xB0, 0x01); + INSTALL_MID_HOOK (CountLockedCrown); + } // Bypass errors WRITE_MEMORY (ASLR (0x140041A00), u8, 0xC3); @@ -458,29 +555,61 @@ Init () { } // Freeze Timer - if (freezeTimer) INSTALL_MID_HOOK (FreezeTimer); - - // Send result per song - if (instantResult) { - INSTALL_HOOK (luaL_newstate); - INSTALL_HOOK (SceneResultInitialize_Enso); - INSTALL_HOOK (SceneResultInitialize_AI); - INSTALL_HOOK (SceneResultInitialize_Collabo025); - INSTALL_HOOK (SceneResultInitialize_Collabo026); - INSTALL_HOOK (SceneResultInitialize_AprilFool); - INSTALL_HOOK (SendResultData_Enso); - INSTALL_HOOK (SendResultData_AI); - INSTALL_HOOK (SendResultData_Collabo025_026); - INSTALL_HOOK (SendResultData_AprilFool); - INSTALL_MID_HOOK (ChangeResultDataSize_Enso); - INSTALL_MID_HOOK (ChangeResultDataSize_AI); - INSTALL_MID_HOOK (ChangeResultDataSize_Collabo025_026); - INSTALL_MID_HOOK (ChangeResultDataSize_AprilFool); - INSTALL_MID_HOOK (ChangeResultDataIndex_Enso); - INSTALL_MID_HOOK (ChangeResultDataIndex_AI); - INSTALL_MID_HOOK (ChangeResultDataIndex_Collabo025_026); - INSTALL_MID_HOOK (ChangeResultDataIndex_AprilFool); - } + TestMode::RegisterItem( + L"", + [&]() { INSTALL_MID_HOOK (FreezeTimer); } + ); + // Mode Unlock + TestMode::RegisterItem( + L"", + [&]() { INSTALL_HOOK (AvailableMode_Collabo024); } + ); + TestMode::RegisterItem( + L"", + [&]() { INSTALL_HOOK (AvailableMode_Collabo025); } + ); + TestMode::RegisterItem( + L"", + [&]() { INSTALL_HOOK (AvailableMode_Collabo026); } + ); + TestMode::RegisterItem( + L"", + [&]() { INSTALL_HOOK (AvailableMode_AprilFool001); } + ); + TestMode::RegisterItem( + L"", + [&]() { + INSTALL_HOOK (SceneResultInitialize_Enso); + INSTALL_HOOK (SceneResultInitialize_AI); + INSTALL_HOOK (SceneResultInitialize_Collabo025); + INSTALL_HOOK (SceneResultInitialize_Collabo026); + INSTALL_HOOK (SceneResultInitialize_AprilFool); + INSTALL_HOOK (SendResultData_Enso); + INSTALL_HOOK (SendResultData_AI); + INSTALL_HOOK (SendResultData_Collabo025_026); + INSTALL_HOOK (SendResultData_AprilFool); + INSTALL_MID_HOOK (ChangeResultDataSize_Enso); + INSTALL_MID_HOOK (ChangeResultDataSize_AI); + INSTALL_MID_HOOK (ChangeResultDataSize_Collabo025_026); + INSTALL_MID_HOOK (ChangeResultDataSize_AprilFool); + INSTALL_MID_HOOK (ChangeResultDataIndex_Enso); + INSTALL_MID_HOOK (ChangeResultDataIndex_AI); + INSTALL_MID_HOOK (ChangeResultDataIndex_Collabo025_026); + INSTALL_MID_HOOK (ChangeResultDataIndex_AprilFool); + } + ); + // Unlimit Volume + TestMode::RegisterModify( + L"/root/menu[@id='SoundTestMenu']/layout[@type='Center']/select-item[@id='OutputLevelSpeakerItem']", + [&](pugi::xml_node &node) { TestMode::Append(node, L"label", L"*"); node.attribute(L"max").set_value(L"300"); node.attribute(L"delta").set_value(L"1"); }, + [&]() { INSTALL_HOOK (SetMasterVolumeSpeaker); INSTALL_HOOK (NuscBusVolume); } + ); + // Instant Result + // TestMode::RegisterModify( + // L"/root/menu[@id='GameOptionsMenu']/layout[@type='Center']/select-item[@id='NumberOfStageItem']", + // [&](pugi::xml_node &node) { TestMode::Append(node, L"label", L"*"); node.attribute(L"max").set_value(L"99"); }, + // [&](){} + // ); // Use chs font/wordlist instead of cht if (chsPatch) { @@ -498,8 +627,22 @@ Init () { WRITE_MEMORY (ASLR (0x140CD1AF8), char, "cn_30"); WRITE_MEMORY (ASLR (0x140C946A0), char, "chineseSText"); WRITE_MEMORY (ASLR (0x140C946B0), char, "chineseSFontType"); + WRITE_MEMORY (ASLR (0x140CD1E40), wchar_t, L"加載中\0"); + WRITE_MEMORY (ASLR (0x140CD1E28), wchar_t, L"加載中.\0"); + WRITE_MEMORY (ASLR (0x140CD1E68), wchar_t, L"加載中..\0"); + WRITE_MEMORY (ASLR (0x140CD1E50), wchar_t, L"加載中...\0"); INSTALL_MID_HOOK (ChangeLanguageType); + INSTALL_HOOK (SetupFontInfo); + INSTALL_HOOK (ReadFontInfoInt); } + + LayeredFs::RegisterBefore([=](const std::string originalFileName, const std::string currentFileName) -> std::string { + if (currentFileName.find ("\\lumen\\") == std::string::npos) return ""; + std::string fileName = currentFileName; + fileName = replace(fileName, "\\lumen\\", "\\lumen_cn\\"); + if (std::filesystem::exists (fileName)) return fileName; + else return currentFileName; + }); } // Fix language @@ -519,11 +662,6 @@ Init () { INSTALL_HOOK (PlaySoundSpecial); } - // Mode unlock - if (modeCollabo025) INSTALL_HOOK (AvailableMode_Collabo025); - if (modeCollabo026) INSTALL_HOOK (AvailableMode_Collabo026); - if (modeAprilFool001) INSTALL_HOOK (AvailableMode_AprilFool001); - // Fix normal song play after passing through silent song INSTALL_MID_HOOK (GenNus3bankId); INSTALL_HOOK (LoadedBankAll); diff --git a/subprojects/pugixml.wrap b/subprojects/pugixml.wrap new file mode 100644 index 0000000..b14f689 --- /dev/null +++ b/subprojects/pugixml.wrap @@ -0,0 +1,10 @@ +[wrap-file] +directory = pugixml-1.14 + +source_url = https://github.com/zeux/pugixml/archive/v1.14.tar.gz +source_filename = pugixml-1.14.tar.gz +source_hash = 610f98375424b5614754a6f34a491adbddaaec074e9044577d965160ec103d2e +method = cmake + +[provide] +pugixml = pugixml_static_dep