diff --git a/CMakeLists.txt b/CMakeLists.txt index e8ddce0..8ad4037 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -133,7 +133,9 @@ set(SOURCES src/patches/dxgi.cpp src/patches/fpslimiter.cpp src/patches/audio.cpp - src/patches/qr.cpp + src/patches/plugins.cpp + src/patches/scanner.cpp + # src/patches/qr.cpp src/patches/layeredfs.cpp src/patches/testmode.cpp src/patches/versions/JPN00.cpp diff --git a/README.md b/README.md index a8ceff2..984507a 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ version = "auto" # Patch version # | - JPN08: For use with Taiko JPN 08.18 # | - JPN39: For use with Taiko JPN 39.06 # | - CHN00: For use with Taiko CHN 00.32 -unlock_songs = true +unlock_songs = true # not active for JPN39 (see TestMode) [patches.chn00] # These patches are only available for version CHN00 fix_language = false # Sync test mode language to attract etc @@ -40,7 +40,6 @@ mode_collabo025 = false # Enable one piece collab mode mode_collabo026 = false # Enable ai soshina mode [patches.jpn39] # These patches are only available for version JPN39 -fix_language = false # Sync test mode language to attract etc chs_patch = false # Use Chinese font and Simplified Chinese values from the wordlist # More options are available in the ModManager, in the TestMode menu (Default key is F1) @@ -53,6 +52,7 @@ qr = true # Disable this if you have an original namco qr code [graphics] res = { x = 1920, y = 1080 } windowed = false +cursor = true vsync = false fpslimit = 120 @@ -100,6 +100,8 @@ TaikoArcadeLoader offers several patches to select in TestMode The follow options are available in "MOD MANAGER" menu: +* FIX LANGUAGE (sync test mode language to attract etc) +* UNLOCK SONGS (show all of the songs) * FREEZE TIMER (stop timer count down) * KIMETSU MODE (enable collabo024, will show a blank title) * ONE PIECE MODE (enable collabo025) @@ -110,6 +112,7 @@ The follow options are available in "MOD MANAGER" menu: Enhanced original option: * Louder volume (Speaker Volume is now up to 300%, **WARNING: May damage your speakers**) +* Attract demo (Only available if FIX LANGUAGE is ON) ## Building Manually diff --git a/dist/config.toml b/dist/config.toml index fb8df90..a584cee 100644 --- a/dist/config.toml +++ b/dist/config.toml @@ -25,7 +25,6 @@ mode_collabo026 = false # Enable ai soshina mode [patches.jpn39] # These patches are only available for version JPN39 -fix_language = false # Sync test mode language to attract etc chs_patch = false # Use Chinese font and Simplified Chinese values from the wordlist # More options are available in the ModManager, in the TestMode menu (Default key is F1) @@ -40,6 +39,7 @@ qr = true # Disable this if you want to use an original Namco [graphics] res = { x = 1920, y = 1080 } windowed = false +cursor = true vsync = false fpslimit = 120 diff --git a/src/bnusio.cpp b/src/bnusio.cpp index f3398a9..2d20508 100644 --- a/src/bnusio.cpp +++ b/src/bnusio.cpp @@ -2,9 +2,10 @@ #include "constants.h" #include "helpers.h" #include "patches/patches.h" +#include "bnusio.h" #include "poll.h" -extern GameVersion version; +extern GameVersion gameVersion; extern std::vector plugins; extern u64 song_data_size; extern void *song_data; @@ -47,10 +48,12 @@ Keybindings P2_LEFT_RED = {.keycodes = {'X'}}; Keybindings P2_RIGHT_RED = {.keycodes = {'C'}}; Keybindings P2_RIGHT_BLUE = {.keycodes = {'V'}}; +int exited = 0; bool testEnabled = false; int coin_count = 0; int service_count = 0; bool inited = false; +bool updateByCoin = false; HWND windowHandle = nullptr; HKL currentLayout; @@ -183,7 +186,10 @@ bnusio_GetAnalogIn (const u8 which) { return 0; } -u16 __fastcall bnusio_GetCoin (i32 a1) { return coin_count; } +u16 __fastcall bnusio_GetCoin (i32 a1) { + if (updateByCoin) bnusio::Update (); + return coin_count; +} u16 __fastcall bnusio_GetService (i32 a1) { return service_count; } } @@ -257,51 +263,12 @@ FUNCTION_PTR (i64, bnusio_DecCoin_Original, PROC_ADDRESS ("bnusio_original.dll", FUNCTION_PTR (u64, bnusio_DecService_Original, PROC_ADDRESS ("bnusio_original.dll", "bnusio_DecService"), i32, u16); FUNCTION_PTR (i64, bnusio_ResetCoin_Original, PROC_ADDRESS ("bnusio_original.dll", "bnusio_ResetCoin")); -HOOK (u64, bngrw_Init, PROC_ADDRESS ("bngrw.dll", "BngRwInit")) { return 0; } -HOOK (void, bngrw_Fin, PROC_ADDRESS ("bngrw.dll", "BngRwFin")) {} -HOOK (u64, bngrw_IsCmdExec, PROC_ADDRESS ("bngrw.dll", "BngRwIsCmdExec")) { return 0xFFFFFFFF; } -HOOK (i32, bngrw_ReqCancel, PROC_ADDRESS ("bngrw.dll", "BngRwReqCancel")) { return 1; } -HOOK (i32, bngrw_ReqSendUrl, PROC_ADDRESS ("bngrw.dll", "BngRwReqSendUrlTo")) { return 1; } -HOOK (u64, bngrw_ReqLed, PROC_ADDRESS ("bngrw.dll", "BngRwReqLed"), u32 a1, u32 ledType, i64 a3, i64 a4) { return 1; } -HOOK (u64, bngrw_ReqBeep, PROC_ADDRESS ("bngrw.dll", "BngRwReqBeep"), u32 a1, u32 beepType, i64 a3, i64 a4) { return 1; } -HOOK (u64, bngrw_ReqAction, PROC_ADDRESS ("bngrw.dll", "BngRwReqAction"), u32 a1, u32 actionType, i64 a3, i64 a4) { return 1; } -HOOK (u64, bngrw_SetLedPower, PROC_ADDRESS ("bngrw.dll", "BngRwSetLedPower")) { return 0; } -HOOK (u64, bngrw_GetRetryCount, PROC_ADDRESS ("bngrw.dll", "BngRwGetTotalRetryCount")) { return 0; } -HOOK (u64, bngrw_GetFwVersion, PROC_ADDRESS ("bngrw.dll", "BngRwGetFwVersion")) { return 0; } -HOOK (u64, bngrw_ReqFwVersionUp, PROC_ADDRESS ("bngrw.dll", "BngRwReqFwVersionUp")) { return 1; } -HOOK (u64, bngrw_ReqFwCleanup, PROC_ADDRESS ("bngrw.dll", "BngRwReqFwCleanup")) { return 1; } -HOOK (u64, bngrw_ReadMifare, PROC_ADDRESS ("bngrw.dll", "BngRwExReadMifareAllBlock")) { return 0xFFFFFF9C; } -HOOK (u64, bngrw_GetStationID, PROC_ADDRESS ("bngrw.dll", "BngRwGetStationID")) { return 0; } -HOOK (i32, bngrw_ReqSendMail, PROC_ADDRESS ("bngrw.dll", "BngRwReqSendMailTo")) { return 1; } -HOOK (i32, bngrw_ReqLatchID, PROC_ADDRESS ("bngrw.dll", "BngRwReqLatchID")) { return 1; } -HOOK (u64, bngrw_ReqAiccAuth, PROC_ADDRESS ("bngrw.dll", "BngRwReqAiccAuth")) { return 1; } -HOOK (u64, bngrw_DevReset, PROC_ADDRESS ("bngrw.dll", "BngRwDevReset")) { return 1; } -HOOK (u64, bngrw_Attach, PROC_ADDRESS ("bngrw.dll", "BngRwAttach"), i32 a1, char *a2, i32 a3, i32 a4, callbackAttach callback, i32 *attachDataH) { - LogMessage (LogLevel::DEBUG, "BngRwAttach"); - // This is way too fucking jank - attachCallback = callback; - attachData = attachDataH; - return 1; -} -HOOK (u64, bngrw_ReqWaitTouch, PROC_ADDRESS ("bngrw.dll", "BngRwReqWaitTouch"), u32 a1, i32 a2, u32 a3, callbackTouch callbackH, u64 touchDataH) { - LogMessage (LogLevel::DEBUG, "BngRwReqWaitTouch"); - touchCallback = callbackH; - if (emulateCardReader) { - waitingForTouch = true; - touchData = touchDataH; - for (const auto plugin : plugins) - if (const FARPROC touchEvent = GetProcAddress (plugin, "WaitTouch")) - reinterpret_cast (touchEvent) (callbackH, touchDataH); - return 1; - } - // This is called when we use an original card reader and acceptInvalidCards is set to true - return originalbngrw_ReqWaitTouch (a1, a2, a3, InspectWaitTouch, touchDataH); -} - void Init () { SetKeyboardButtons (); + int fpsLimit = 120; + const auto configPath = std::filesystem::current_path () / "config.toml"; const std::unique_ptr config_ptr (openConfig (configPath), toml_free); if (config_ptr) { @@ -311,8 +278,16 @@ Init () { analogInput = readConfigBool (controller, "analog_input", analogInput); if (analogInput) LogMessage (LogLevel::WARN, "Using analog input mode. All the keyboard drum inputs have been disabled."); } + auto graphics = openConfigSection (config, "graphics"); + if (graphics) { + fpsLimit = readConfigInt (graphics, "fpslimit", fpsLimit); + } } + updateByCoin = fpsLimit == 0; + if (updateByCoin) { + LogMessage (LogLevel::INFO, "fpsLimit is set to 0, bnusio::Update() will invoke in getCoin callback"); + } const auto keyConfigPath = std::filesystem::current_path () / "keyconfig.toml"; const std::unique_ptr keyConfig_ptr (openConfig (keyConfigPath), toml_free); if (keyConfig_ptr) { @@ -392,113 +367,50 @@ Init () { INSTALL_HOOK_DIRECT (bnusio_DecCoin, bnusio_DecCoin_Original); INSTALL_HOOK_DIRECT (bnusio_DecService, bnusio_DecService_Original); INSTALL_HOOK_DIRECT (bnusio_ResetCoin, bnusio_ResetCoin_Original); - LogMessage (LogLevel::WARN, "USIO emulation disabled"); } - - if (emulateCardReader) { - INSTALL_HOOK (bngrw_Init) - INSTALL_HOOK (bngrw_Fin); - INSTALL_HOOK (bngrw_IsCmdExec); - INSTALL_HOOK (bngrw_ReqCancel); - INSTALL_HOOK (bngrw_ReqWaitTouch); - INSTALL_HOOK (bngrw_ReqSendUrl); - INSTALL_HOOK (bngrw_ReqLed); - INSTALL_HOOK (bngrw_ReqBeep); - INSTALL_HOOK (bngrw_ReqAction); - INSTALL_HOOK (bngrw_SetLedPower); - INSTALL_HOOK (bngrw_GetRetryCount); - INSTALL_HOOK (bngrw_GetFwVersion); - INSTALL_HOOK (bngrw_ReqFwVersionUp); - INSTALL_HOOK (bngrw_ReqFwCleanup); - INSTALL_HOOK (bngrw_ReadMifare); - INSTALL_HOOK (bngrw_GetStationID); - INSTALL_HOOK (bngrw_ReqSendMail); - INSTALL_HOOK (bngrw_ReqLatchID); - INSTALL_HOOK (bngrw_ReqAiccAuth); - INSTALL_HOOK (bngrw_Attach); - INSTALL_HOOK (bngrw_DevReset); - } else { - LogMessage (LogLevel::WARN, "Card reader emulation disabled"); - if (acceptInvalidCards) { - LogMessage (LogLevel::WARN, "Original reader will accept invalid cards!"); - INSTALL_HOOK (bngrw_ReqWaitTouch); - } - } } void Update () { + if (exited && ++exited >= 50) ExitProcess (0); if (!inited) { windowHandle = FindWindowA ("nuFoundation.Window", nullptr); InitializePoll (windowHandle); if (autoIme) { - currentLayout = GetKeyboardLayout (0); - const auto engLayout = LoadKeyboardLayout (TEXT ("00000409"), KLF_ACTIVATE); + currentLayout = GetKeyboardLayout (0); + auto engLayout = LoadKeyboardLayout (TEXT ("00000409"), KLF_ACTIVATE); ActivateKeyboardLayout (engLayout, KLF_SETFORPROCESS); } - for (const auto plugin : plugins) - if (const auto initEvent = GetProcAddress (plugin, "Init")) initEvent (); - + patches::Plugins::Init (); inited = true; } UpdatePoll (windowHandle); + std::vector buffer = {}; if (IsButtonTapped (COIN_ADD) && !testEnabled) coin_count++; - if (IsButtonTapped (SERVICE) && !testEnabled) service_count++; + if (IsButtonTapped (SERVICE) && !testEnabled) service_count++; if (IsButtonTapped (TEST)) testEnabled = !testEnabled; - if (IsButtonTapped (EXIT)) ExitProcess (0); - if (waitingForTouch) { - if (IsButtonTapped (CARD_INSERT_1) || IsButtonTapped (CARD_INSERT_2)) { - bool hasInserted = false; - const bool p1 = IsButtonTapped (CARD_INSERT_1); - static 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}; - - for (const auto plugin : plugins) { - FARPROC insertEvent = GetProcAddress (plugin, p1 ? "BeforeCard1Insert" : "BeforeCard2Insert"); - if (insertEvent) reinterpret_cast (insertEvent) (); - insertEvent = GetProcAddress (plugin, p1 ? "Card1Insert" : "Card2Insert"); - if (insertEvent) { - reinterpret_cast (insertEvent) (); - hasInserted = true; - waitingForTouch = false; - break; - } - } - - if (!hasInserted) { - LogMessage (LogLevel::INFO, "Inserting card for player %d: %s", p1 ? 1 : 2, p1 ? accessCode1 : accessCode2); - memcpy (cardData + 0x2C, p1 ? chipId1 : chipId2, 33); - memcpy (cardData + 0x50, p1 ? accessCode1 : accessCode2, 21); - touchCallback (0, 0, cardData, touchData); - waitingForTouch = false; - } - } + if (IsButtonTapped (EXIT)) { exited += 1; testEnabled = 1; } + if (GameVersion::CHN00 == gameVersion) { + if (IsButtonTapped (CARD_INSERT_1)) patches::Scanner::Qr::CommitLogin (accessCode1); + if (IsButtonTapped (CARD_INSERT_2)) patches::Scanner::Qr::CommitLogin (accessCode2); + } else { + if (IsButtonTapped (CARD_INSERT_1)) patches::Scanner::Card::Commit (accessCode1, chipId1); + if (IsButtonTapped (CARD_INSERT_2)) patches::Scanner::Card::Commit (accessCode2, chipId2); } + if (IsButtonTapped (QR_DATA_READ)) patches::Scanner::Qr::Commit (patches::Scanner::Qr::ReadQRData (buffer)); + if (IsButtonTapped (QR_IMAGE_READ)) patches::Scanner::Qr::Commit (patches::Scanner::Qr::ReadQRImage (buffer)); - for (const auto plugin : plugins) - if (const auto updateEvent = GetProcAddress (plugin, "Update")) updateEvent (); - - patches::Qr::Update (); - - if (attachCallback) attachCallback (0, 0, attachData); + patches::Plugins::Update (); + patches::Scanner::Update (); } void Close () { if (autoIme) ActivateKeyboardLayout (currentLayout, KLF_SETFORPROCESS); - for (const auto plugin : plugins) - if (const FARPROC exitEvent = GetProcAddress (plugin, "Exit")) reinterpret_cast (exitEvent) (); - + patches::Plugins::Exit (); CleanupLogger (); } } // namespace bnusio diff --git a/src/dllmain.cpp b/src/dllmain.cpp index 4c6e0a2..40fa66f 100644 --- a/src/dllmain.cpp +++ b/src/dllmain.cpp @@ -25,6 +25,7 @@ char chipId2[33] = "00000000000000000000000000000002"; bool windowed = false; bool autoIme = false; bool jpLayout = false; +bool cursor = true; bool emulateUsio = true; bool emulateCardReader = true; bool emulateQr = true; @@ -177,7 +178,10 @@ DllMain (HMODULE module, const DWORD reason, LPVOID reserved) { acceptInvalidCards = readConfigBool (emulation, "accept_invalid", acceptInvalidCards); emulateQr = readConfigBool (emulation, "qr", emulateQr); } - if (const auto graphics = openConfigSection (config, "graphics")) windowed = readConfigBool (graphics, "windowed", windowed); + if (const auto graphics = openConfigSection (config, "graphics")) { + windowed = readConfigBool (graphics, "windowed", windowed); + cursor = readConfigBool (graphics, "cursor", cursor); + } if (const auto keyboard = openConfigSection (config, "keyboard")) { autoIme = readConfigBool (keyboard, "auto_ime", autoIme); jpLayout = readConfigBool (keyboard, "jp_layout", jpLayout); @@ -210,20 +214,8 @@ DllMain (HMODULE module, const DWORD reason, LPVOID reserved) { } LogMessage (LogLevel::INFO, "GameVersion is %s", GameVersionToString (gameVersion)); - if (const auto pluginPath = std::filesystem::current_path () / "plugins"; exists (pluginPath)) { - for (const auto &entry : std::filesystem::directory_iterator (pluginPath)) { - if (entry.path ().extension () == ".dll") { - auto name = entry.path ().wstring (); - auto shortName = entry.path ().filename ().wstring (); - if (HMODULE hModule = LoadLibraryW (name.c_str ()); !hModule) { - LogMessage (LogLevel::ERROR, L"Failed to load plugin " + shortName); - } else { - plugins.push_back (hModule); - LogMessage (LogLevel::INFO, L"Loaded plugin " + shortName); - } - } - } - } + patches::Plugins::LoadPlugins (); + patches::Plugins::InitVersion (gameVersion); if (!std::filesystem::exists (".\\card.ini")) CreateCard (); GetPrivateProfileStringA ("card", "accessCode1", accessCode1, accessCode1, 21, ".\\card.ini"); @@ -233,7 +225,7 @@ DllMain (HMODULE module, const DWORD reason, LPVOID reserved) { LogMessage (LogLevel::WARN, "Loading patches, please wait..."); - INSTALL_HOOK (ShowMouse); + if (cursor) INSTALL_HOOK (ShowMouse); INSTALL_HOOK (ExitWindows); INSTALL_HOOK (CreateWindow); INSTALL_HOOK (SetWindowPosition); @@ -262,7 +254,7 @@ DllMain (HMODULE module, const DWORD reason, LPVOID reserved) { case GameVersion::CHN00: patches::CHN00::Init (); break; } - patches::Qr::Init (); + patches::Scanner::Init (); patches::Audio::Init (); patches::Dxgi::Init (); patches::AmAuth::Init (); diff --git a/src/logger.cpp b/src/logger.cpp index e73c8f5..f642522 100644 --- a/src/logger.cpp +++ b/src/logger.cpp @@ -45,6 +45,15 @@ LogMessageHandler (const char *function, const char *codeFile, int codeLine, Log // Determine log type string std::string logType = GetLogLevelString (messageLevel); + int find_idx = 0, begin = -2, end = 0; + while (function[find_idx] != 0) { + if (begin == -2 && function[find_idx] == ' ') begin = -1; + else if (begin == -1 && function[find_idx] == ' ') begin = find_idx + 1; + else if (end == 0 && ((begin > 0 && function[find_idx] == ' ') || function[find_idx] == '(')) end = find_idx; + find_idx ++; + } + std::string short_function = std::string(function + begin, end - begin); + // Remove the absolute path of the build dir constexpr std::string_view build_dir = XSTRING (SOURCE_ROOT); std::string_view filename = codeFile; @@ -60,7 +69,7 @@ LogMessageHandler (const char *function, const char *codeFile, int codeLine, Log // Construct the log message std::ostringstream logStream; - logStream << function << " (" << filename << ":" << codeLine << "): " << formattedMessage; + logStream << short_function << " (" << filename << ":" << codeLine << "): " << formattedMessage; std::string logMessage = logStream.str (); // Print the log message diff --git a/src/logger.h b/src/logger.h index 72c0ebe..d5e2ecf 100644 --- a/src/logger.h +++ b/src/logger.h @@ -3,6 +3,7 @@ #include "helpers.h" #include #include +#include #include #define STRING(x) #x @@ -41,14 +42,16 @@ void LogMessageHandler (const char *function, const char *codeFile, int codeLine */ template struct LogMessage { - LogMessage (const LogLevel level, const std::string_view format, Args &&...ts, + LogMessage (const LogLevel level, const std::string_view format, Args &&...args, const std::source_location &loc = std::source_location::current ()) { - LogMessageHandler (loc.function_name (), loc.file_name (), loc.line (), level, format.data (), std::forward (ts)...); + std::string formatted_message = std::vformat(std::string(format), std::make_format_args(args...)); + LogMessageHandler (loc.function_name (), loc.file_name (), loc.line (), level, formatted_message.c_str ()); } - LogMessage (const LogLevel level, const std::wstring_view format, Args &&...ts, + LogMessage (const LogLevel level, const std::wstring_view format, Args &&...args, const std::source_location &loc = std::source_location::current ()) { - LogMessageHandler (loc.function_name (), loc.file_name (), loc.line (), level, format.data (), std::forward (ts)...); + std::wstring formatted_message = std::vformat(std::wstring(format), std::make_wformat_args(args...)); + LogMessageHandler (loc.function_name (), loc.file_name (), loc.line (), level, formatted_message.c_str ()); } }; diff --git a/src/patches/audio.cpp b/src/patches/audio.cpp index c23cd01..03663e9 100644 --- a/src/patches/audio.cpp +++ b/src/patches/audio.cpp @@ -22,6 +22,8 @@ bool wasapiShared = true; bool asio = false; std::string asioDriver; +float volumeRate = 0.0f; + HOOK_DYNAMIC (i64, NUSCDeviceInit, void *a1, nusc_init_config_t *a2, nusc_init_config_t *a3, void *a4) { LogMessage (LogLevel::INFO, std::string ("Device mode is ") + (asio ? "ASIO" : wasapiShared ? "wasapi shared" : "wasapi exclusive")); if (asio) LogMessage (LogLevel::INFO, (std::string ("ASIO driver is ") + asioDriver).c_str ()); @@ -39,6 +41,19 @@ HOOK_DYNAMIC (bool, LoadASIODriver, void *a1, const char *a2) { } return result; } +HOOK_DYNAMIC (u64, NuscBusVolume, u64 a1, u64 a2, float a3) { + if (volumeRate == 0.0f) { + int value = patches::TestMode::ReadTestModeValue (L"OutputLevelSpeakerItem"); + if (value == -1) return originalNuscBusVolume (a1, a2, a3); + volumeRate = value <= 100 ? 1.0f : value / 100.0f; + } + return originalNuscBusVolume (a1, a2, a3 * volumeRate); +} + +void +SetVolumeRate (float rate) { + volumeRate = rate; +} void Init () { @@ -68,6 +83,7 @@ Init () { case GameVersion::JPN39: { INSTALL_HOOK_DYNAMIC (NUSCDeviceInit, ASLR (0x1407C8620)); INSTALL_HOOK_DYNAMIC (LoadASIODriver, ASLR (0x1407D0F70)); + INSTALL_HOOK_DYNAMIC (NuscBusVolume, ASLR (0x1407B1C30)); break; } case GameVersion::CHN00: { diff --git a/src/patches/dxgi.cpp b/src/patches/dxgi.cpp index c5d4d8a..ffc38a3 100644 --- a/src/patches/dxgi.cpp +++ b/src/patches/dxgi.cpp @@ -212,16 +212,19 @@ Init () { } FpsLimiterEnable = fpsLimit > 0; - FpsLimiter::Init (static_cast (fpsLimit)); - - MH_Initialize (); - MH_CreateHookApi (L"dxgi.dll", "CreateDXGIFactory", reinterpret_cast (CreateDXGIFactoryWrap), - reinterpret_cast (&g_origCreateDXGIFactory)); - MH_CreateHookApi (L"dxgi.dll", "CreateDXGIFactory2", reinterpret_cast (CreateDXGIFactory2Wrap), - reinterpret_cast (&g_origCreateDXGIFactory2)); - MH_CreateHookApi (L"d3d11.dll", "D3D11CreateDeviceAndSwapChain", reinterpret_cast (D3D11CreateDeviceAndSwapChainWrap), - reinterpret_cast (&g_origD3D11CreateDeviceAndSwapChain)); - MH_EnableHook (nullptr); + + if (FpsLimiterEnable) { + FpsLimiter::Init (static_cast (fpsLimit)); + + MH_Initialize (); + MH_CreateHookApi (L"dxgi.dll", "CreateDXGIFactory", reinterpret_cast (CreateDXGIFactoryWrap), + reinterpret_cast (&g_origCreateDXGIFactory)); + MH_CreateHookApi (L"dxgi.dll", "CreateDXGIFactory2", reinterpret_cast (CreateDXGIFactory2Wrap), + reinterpret_cast (&g_origCreateDXGIFactory2)); + MH_CreateHookApi (L"d3d11.dll", "D3D11CreateDeviceAndSwapChain", reinterpret_cast (D3D11CreateDeviceAndSwapChainWrap), + reinterpret_cast (&g_origD3D11CreateDeviceAndSwapChain)); + MH_EnableHook (nullptr); + } } } // namespace patches::Dxgi \ No newline at end of file diff --git a/src/patches/patches.h b/src/patches/patches.h index 81b2122..7f4a53e 100644 --- a/src/patches/patches.h +++ b/src/patches/patches.h @@ -3,6 +3,8 @@ #include #include +#include "constants.h" + namespace patches { namespace JPN00 { void Init (); @@ -20,14 +22,15 @@ namespace Dxgi { void Init (); } // namespace Dxgi namespace FpsLimiter { -void Init (float fpsLimit); +void Init (float fpsLimit); void Update (); } // namespace FpsLimiter namespace Audio { -void Init (); +void Init (); +void SetVolumeRate (float rate); } // namespace Audio namespace Qr { -void Init (); +void Init (); void Update (); } // namespace Qr namespace AmAuth { @@ -36,15 +39,62 @@ void Init (); namespace LayeredFs { void Init (); void RegisterBefore (const std::function &fileHandler); -void RegisterAfter (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 (const pugi::xml_node &node, const wchar_t *attr, const std::wstring& append); +void Init (); +void Append (const pugi::xml_node &node, const wchar_t *attr, const std::wstring &append); +void SetupAccessor (u64 appAccessor, RefTestModeMain refTestMode); +int ReadTestModeValue (const wchar_t *itemId); +void RegisterItem (const std::wstring &item, const std::function &initMethod); +void RegisterItemAfter (const std::wstring &query, const std::wstring &item, const std::function &initMethod); +void RegisterModify (const std::wstring &query, const std::function &nodeModify, const std::function &initMethod); } // namespace TestMode +namespace Plugins { +// typedefs +typedef void (*CallBackTouchCard) (int32_t, int32_t, uint8_t[168], uint64_t); +typedef bool (*CommitCardCallback) (std::string, std::string); +typedef bool (*CommitQrCallback) (std::vector &); +typedef bool (*CommitQrLoginCallback) (std::string); +// Standard API +void Init (); +void Update (); +void Exit (); +// Lowlevel Card API +void WaitTouch (CallBackTouchCard callback, uint64_t touchData); +// Lowlevel QR API +void InitQr (GameVersion gameVersion); +void UsingQr (); +void * CheckQr (); +size_t GetQr (void *plugin, size_t size, uint8_t *buffer); +// New API +void InitVersion (GameVersion gameVersion); +void InitCardReader (CommitCardCallback touch); +void InitQRScanner (CommitQrCallback scan); +void InitQRLogin (CommitQrLoginCallback login); +void UpdateStatus (size_t type, bool status); +// Plugins Loader +void LoadPlugins (); +} // namespace Plugins +namespace Scanner { +enum class State { Disable, Ready, CopyWait }; +void Init (); +void Update (); +namespace Card { +typedef int32_t (*CallbackAttach) (int32_t, int32_t, int32_t *); +typedef void (*CallbackTouch) (int32_t, int32_t, uint8_t[168], uint64_t); +void Init (); +void Update (); +bool Commit (std::string accessCode, std::string chipId); +} // namespace Card +namespace Qr { +void Init (); +void Update (); +bool Commit (std::vector &buffer); +bool CommitLogin (std::string accessCode); +std::vector &ReadQRData (std::vector &buffer); +std::vector &ReadQRImage (std::vector &buffer); +} // namespace Qr +} // namespace Scanner } // namespace patches diff --git a/src/patches/plugins.cpp b/src/patches/plugins.cpp new file mode 100644 index 0000000..a379b86 --- /dev/null +++ b/src/patches/plugins.cpp @@ -0,0 +1,137 @@ +#include "constants.h" +#include "helpers.h" +#include "patches.h" + +namespace patches::Plugins { + std::vector plugins = {}; + + typedef void (*BasicEvent) (); + typedef void (*WaitTouchEvent) (CallBackTouchCard callback, uint64_t touchData); + typedef void (*SendVersionEvent) (GameVersion gameVersion); + typedef bool (*CheckEvent) (); + typedef size_t (*CopyDataEvent) (size_t size, uint8_t *buffer); + typedef void (*SendCardReaderEvent) (CommitCardCallback touch); + typedef void (*SendQRScannerEvent) (CommitQrCallback scan); + typedef void (*SendQRLoginEvent) (CommitQrLoginCallback login); + typedef void (*StatusChangeEvent) (size_t type, bool status); + + void + Init () { + for (auto plugin : plugins) { + auto event = GetProcAddress (plugin, "Init"); + if (event) ((BasicEvent)event) (); + } + } + void + Update () { + for (auto plugin : plugins) { + auto event = GetProcAddress (plugin, "Update"); + if (event) ((BasicEvent)event) (); + } + } + void + Exit () { + for (auto plugin : plugins) { + auto event = GetProcAddress (plugin, "Exit"); + if (event) ((BasicEvent)event) (); + } + } + // Card API + void + WaitTouch (CallBackTouchCard callback, uint64_t touchData) { + for (auto plugin : plugins) { + auto event = GetProcAddress (plugin, "WaitTouch"); + if (event) ((WaitTouchEvent)event) (callback, touchData); + } + } + // QR API (deprecated) + void + InitQr (GameVersion gameVersion) { + for (auto plugin : plugins) { + auto event = GetProcAddress (plugin, "InitQr"); + if (event) ((SendVersionEvent)event) (gameVersion); + } + } + void + UsingQr () { + for (auto plugin : plugins) { + auto event = GetProcAddress (plugin, "UsingQr"); + if (event) ((BasicEvent)event) (); + } + } + void * + CheckQr () { + for (auto plugin : plugins) { + auto event = GetProcAddress (plugin, "UsingQr"); + if (event && ((CheckEvent)event) ()) return plugin; + } + return nullptr; + } + size_t + GetQr (void *plugin, size_t size, uint8_t *buffer) { + auto event = GetProcAddress (*(HMODULE *)plugin, "GetQr"); + if (event) return ((CopyDataEvent)event) (size, buffer); + else return 0; + } + // New API + void + InitVersion (GameVersion gameVersion) { + for (auto plugin : plugins) { + auto event = GetProcAddress (plugin, "InitVersion"); + if (event) ((SendVersionEvent)event) (gameVersion); + } + } + void + InitCardReader (CommitCardCallback touch) { + for (auto plugin : plugins) { + auto event = GetProcAddress (plugin, "InitCardReader"); + if (event) ((SendCardReaderEvent)event) (touch); + } + } + void + InitQRScanner (CommitQrCallback scan) { + for (auto plugin : plugins) { + auto event = GetProcAddress (plugin, "InitQRScanner"); + if (event) ((SendQRScannerEvent)event) (scan); + } + } + void + InitQRLogin (CommitQrLoginCallback login) { + for (auto plugin : plugins) { + auto event = GetProcAddress (plugin, "InitQRLogin"); + if (event) ((SendQRLoginEvent)event) (login); + } + } + void + UpdateStatus (size_t type, bool status) { + // printWarning ("Send UpdateStatus type=%d status=%d", type, status); + for (auto plugin : plugins) { + auto event = GetProcAddress (plugin, "UpdateStatus"); + if (event) ((StatusChangeEvent)event) (type, status); + } + } + + // Plugins Loader + void LoadPlugins () { + auto pluginPath = std::filesystem::current_path () / "plugins"; + + if (std::filesystem::exists (pluginPath)) { + for (const auto &entry : std::filesystem::directory_iterator (pluginPath)) { + if (entry.path ().extension () == ".dll") { + auto name = entry.path ().wstring (); + auto shortName = entry.path ().filename ().wstring (); + if (HMODULE hModule = LoadLibraryW (name.c_str ()); !hModule) { + LogMessage (LogLevel::ERROR, L"Failed to load plugin " + shortName); + } else { + plugins.push_back (hModule); + LogMessage (LogLevel::INFO, L"Loaded plugin " + shortName); + } + } + } + } + } + + + + +} \ No newline at end of file diff --git a/src/patches/qr.cpp b/src/patches/qr.cpp deleted file mode 100644 index 377b26e..0000000 --- a/src/patches/qr.cpp +++ /dev/null @@ -1,298 +0,0 @@ -#include "constants.h" -#include "helpers.h" -#include "poll.h" -#include -#include -#define STB_IMAGE_IMPLEMENTATION -#define STBI_WINDOWS_UTF8 -#include "stb_image.h" - -extern GameVersion gameVersion; -extern Keybindings CARD_INSERT_1; -extern Keybindings CARD_INSERT_2; -extern Keybindings QR_DATA_READ; -extern Keybindings QR_IMAGE_READ; -extern char accessCode1[21]; -extern char accessCode2[21]; -extern std::vector plugins; -extern bool emulateQr; - -typedef void event (); -typedef void initQrEvent (GameVersion gameVersion); -typedef bool checkQrEvent (); -typedef int getQrEvent (int, unsigned char *); - -namespace patches::Qr { - -enum class State { Ready, CopyWait }; -enum class Mode { Card, Data, Image, Plugin }; -auto gState = State::Ready; -auto gMode = Mode::Card; -HMODULE gPlugin; -std::string accessCode; - -std::vector qrPlugins; -bool qrPluginRegistered = false; - -HOOK_DYNAMIC (char, QrInit, i64) { return 1; } -HOOK_DYNAMIC (char, QrClose, i64) { return 1; } -HOOK_DYNAMIC (char, QrRead, i64 a1) { - *reinterpret_cast (a1 + 40) = 1; - *reinterpret_cast (a1 + 16) = 1; - *reinterpret_cast (a1 + 112) = 0; - return 1; -} -HOOK_DYNAMIC (i64, CallQrUnknown, i64) { return 1; } -HOOK_DYNAMIC (bool, Send1, i64 a1) { - *reinterpret_cast (a1 + 88) = 1; - *reinterpret_cast (a1 + 32) = *reinterpret_cast (a1 + 24); - *reinterpret_cast (a1 + 89) = 0; - return true; -} -HOOK_DYNAMIC (bool, Send2, i64 a1) { - *reinterpret_cast (a1 + 88) = 0; - *reinterpret_cast (a1 + 90) = 0; - return true; -} -HOOK_DYNAMIC (bool, Send3, i64, char) { return true; } -HOOK_DYNAMIC (bool, Send4, i64, const void *, i64) { return true; } -HOOK_DYNAMIC (i64, CopyData, i64, void *dest, int length) { - if (gState == State::CopyWait) { - // std::cout << "Copy data, length: " << length << std::endl; - auto configPath = std::filesystem::current_path () / "config.toml"; - std::unique_ptr config_ptr (openConfig (configPath), toml_free); - - if (gMode == Mode::Card) { - memcpy (dest, accessCode.c_str (), accessCode.size () + 1); - gState = State::Ready; - return accessCode.size () + 1; - } - if (gMode == Mode::Data) { - std::string serial = ""; - u16 type = 0; - std::vector songNoes; - - if (config_ptr) { - if (auto qr = openConfigSection (config_ptr.get (), "qr")) { - if (auto data = openConfigSection (qr, "data")) { - serial = readConfigString (data, "serial", ""); - type = static_cast (readConfigInt (data, "type", 0)); - songNoes = readConfigIntArray (data, "song_no", songNoes); - } - } - } - - BYTE serial_length = static_cast (serial.size ()); - std::vector byteBuffer = {0x53, 0x31, 0x32, 0x00, 0x00, 0xFF, 0xFF, serial_length, 0x01, 0x00}; - - for (char c : serial) - byteBuffer.push_back (static_cast (c)); - - if (type == 5) { - std::vector folderData = {0xFF, 0xFF}; - - folderData.push_back (static_cast (songNoes.size ()) * 2); - - folderData.push_back (static_cast (type & 0xFF)); - folderData.push_back (static_cast (type >> 8 & 0xFF)); - - for (i64 songNo : songNoes) { - folderData.push_back (static_cast (songNo & 0xFF)); - folderData.push_back (static_cast (songNo >> 8 & 0xFF)); - } - - for (auto c : folderData) - byteBuffer.push_back (c); - } - - byteBuffer.push_back (0xEE); - byteBuffer.push_back (0xFF); - - std::stringstream hexStream; - for (auto byteData : byteBuffer) - hexStream << std::hex << std::uppercase << std::setfill ('0') << std::setw (2) << static_cast (byteData) << " "; - LogMessage (LogLevel::INFO, ("Data dump: " + hexStream.str ()).c_str ()); - - memcpy (dest, byteBuffer.data (), byteBuffer.size ()); - gState = State::Ready; - return byteBuffer.size (); - } - if (gMode == Mode::Image) { - std::string imagePath = ""; - - if (config_ptr) { - if (auto qr = openConfigSection (config_ptr.get (), "qr")) imagePath = readConfigString (qr, "image_path", ""); - } - - std::u8string u8PathStr (imagePath.begin (), imagePath.end ()); - std::filesystem::path u8Path (u8PathStr); - if (!is_regular_file (u8Path)) { - LogMessage (LogLevel::ERROR, ("Failed to open image: " + u8Path.string () + " (file not found)").c_str ()); - gState = State::Ready; - return 0; - } - - int width, height, channels; - std::unique_ptr buffer (stbi_load (u8Path.string ().c_str (), &width, &height, &channels, 3), - stbi_image_free); - if (!buffer) { - LogMessage (LogLevel::ERROR, ("Failed to read image: " + u8Path.string () + " (" + stbi_failure_reason () + ")").c_str ()); - gState = State::Ready; - return 0; - } - - ZXing::ImageView image{buffer.get (), width, height, ZXing::ImageFormat::RGB}; - auto result = ReadBarcode (image); - if (!result.isValid ()) { - LogMessage (LogLevel::ERROR, ("Failed to read QR: " + imagePath + " (" + ToString (result.error ()) + ")").c_str ()); - gState = State::Ready; - return 0; - } - - // std::cout << "Valid" << std::endl; - auto byteData = result.bytes (); - // std::cout << ZXing::ToHex (byteData) << std::endl; - auto dataSize = byteData.size (); - - memcpy (dest, byteData.data (), dataSize); - gState = State::Ready; - return dataSize; - } - if (gMode == Mode::Plugin) { - if (FARPROC getEvent = GetProcAddress (gPlugin, "GetQr")) { - std::vector plugin_data (length); - int buf_len = reinterpret_cast (getEvent) (length, plugin_data.data ()); - if (0 < buf_len && buf_len <= length) { - std::stringstream hexStream; - for (int i = 0; i < buf_len; i++) - hexStream << std::hex << std::uppercase << std::setfill ('0') << std::setw (2) << static_cast (plugin_data[i]) << " "; - LogMessage (LogLevel::INFO, "QR dump: " + hexStream.str ()); - memcpy (dest, plugin_data.data (), buf_len); - } else { - LogMessage (LogLevel::ERROR, ("QR discard! Length invalid: " + std::to_string (buf_len) + ", valid range: 0~").c_str ()); - } - gState = State::Ready; - return buf_len; - } - gState = State::Ready; - return 0; - } - } else if (qrPluginRegistered) { - for (auto plugin : qrPlugins) - if (FARPROC usingQrEvent = GetProcAddress (plugin, "UsingQr")) reinterpret_cast (usingQrEvent) (); - } - return 0; -} - -void -Update () { - if (!emulateQr) return; - if (gState == State::Ready) { - // std::cout << "Insert" << std::endl; - if (IsButtonTapped (CARD_INSERT_1)) { - if (gameVersion != GameVersion::CHN00) return; - accessCode = "BNTTCNID"; - accessCode += accessCode1; - gState = State::CopyWait; - gMode = Mode::Card; - } else if (IsButtonTapped (CARD_INSERT_2)) { - if (gameVersion != GameVersion::CHN00) return; - accessCode = "BNTTCNID"; - accessCode += accessCode2; - gState = State::CopyWait; - gMode = Mode::Card; - } else if (IsButtonTapped (QR_DATA_READ)) { - gState = State::CopyWait; - gMode = Mode::Data; - } else if (IsButtonTapped (QR_IMAGE_READ)) { - gState = State::CopyWait; - gMode = Mode::Image; - } else if (qrPluginRegistered) { - for (const auto plugin : qrPlugins) { - if (const FARPROC checkEvent = GetProcAddress (plugin, "CheckQr"); checkEvent && reinterpret_cast (checkEvent) ()) { - gState = State::CopyWait; - gMode = Mode::Plugin; - gPlugin = plugin; - break; - } - } - } - } -} - -void -Init () { - LogMessage (LogLevel::INFO, "Init Qr patches"); - - if (!emulateQr) { - LogMessage (LogLevel::WARN, "QR emulation disabled"); - return; - } - - for (auto plugin : plugins) { - if (const FARPROC initEvent = GetProcAddress (plugin, "InitQr")) reinterpret_cast (initEvent) (gameVersion); - if (GetProcAddress (plugin, "UsingQr")) qrPlugins.push_back (plugin); - } - if (qrPlugins.size () > 0) { - - LogMessage (LogLevel::INFO, "QR plugin found!"); - qrPluginRegistered = true; - } - - SetConsoleOutputCP (CP_UTF8); - const auto amHandle = reinterpret_cast (GetModuleHandle ("AMFrameWork.dll")); - switch (gameVersion) { - case GameVersion::JPN00: { - INSTALL_HOOK_DYNAMIC (QrInit, reinterpret_cast (amHandle + 0x1B3E0)); - INSTALL_HOOK_DYNAMIC (QrClose, reinterpret_cast (amHandle + 0x1B5B0)); - INSTALL_HOOK_DYNAMIC (QrRead, reinterpret_cast (amHandle + 0x1B600)); - INSTALL_HOOK_DYNAMIC (CallQrUnknown, reinterpret_cast (amHandle + 0xFD40)); - INSTALL_HOOK_DYNAMIC (Send1, reinterpret_cast (amHandle + 0x1BBB0)); - INSTALL_HOOK_DYNAMIC (Send2, reinterpret_cast (amHandle + 0x1BBF0)); - INSTALL_HOOK_DYNAMIC (Send3, reinterpret_cast (amHandle + 0x1BC60)); - // JPN00 has no Send4 - INSTALL_HOOK_DYNAMIC (CopyData, reinterpret_cast (amHandle + 0x1BC30)); - break; - } - case GameVersion::JPN08: { - INSTALL_HOOK_DYNAMIC (QrInit, reinterpret_cast (amHandle + 0x1BA00)); - INSTALL_HOOK_DYNAMIC (QrClose, reinterpret_cast (amHandle + 0x1BBD0)); - INSTALL_HOOK_DYNAMIC (QrRead, reinterpret_cast (amHandle + 0x1BC20)); - INSTALL_HOOK_DYNAMIC (CallQrUnknown, reinterpret_cast (amHandle + 0xFD40)); - INSTALL_HOOK_DYNAMIC (Send1, reinterpret_cast (amHandle + 0x1C220)); - INSTALL_HOOK_DYNAMIC (Send2, reinterpret_cast (amHandle + 0x1C260)); - INSTALL_HOOK_DYNAMIC (Send3, reinterpret_cast (amHandle + 0x1C2D0)); - // JPN08 has no Send4 - INSTALL_HOOK_DYNAMIC (CopyData, reinterpret_cast (amHandle + 0x1C2A0)); - break; - } - case GameVersion::JPN39: { - INSTALL_HOOK_DYNAMIC (QrInit, reinterpret_cast (amHandle + 0x1EDC0)); - INSTALL_HOOK_DYNAMIC (QrClose, reinterpret_cast (amHandle + 0x1EF60)); - INSTALL_HOOK_DYNAMIC (QrRead, reinterpret_cast (amHandle + 0x1EFB0)); - INSTALL_HOOK_DYNAMIC (CallQrUnknown, reinterpret_cast (amHandle + 0x11A70)); - INSTALL_HOOK_DYNAMIC (Send1, reinterpret_cast (amHandle + 0x1F5B0)); - INSTALL_HOOK_DYNAMIC (Send2, reinterpret_cast (amHandle + 0x1F5F0)); - INSTALL_HOOK_DYNAMIC (Send3, reinterpret_cast (amHandle + 0x1F660)); - INSTALL_HOOK_DYNAMIC (Send4, reinterpret_cast (amHandle + 0x1F690)); - INSTALL_HOOK_DYNAMIC (CopyData, reinterpret_cast (amHandle + 0x1F630)); - break; - } - case GameVersion::CHN00: { - INSTALL_HOOK_DYNAMIC (QrInit, reinterpret_cast (amHandle + 0x161B0)); - INSTALL_HOOK_DYNAMIC (QrClose, reinterpret_cast (amHandle + 0x16350)); - INSTALL_HOOK_DYNAMIC (QrRead, reinterpret_cast (amHandle + 0x163A0)); - INSTALL_HOOK_DYNAMIC (CallQrUnknown, reinterpret_cast (amHandle + 0x8F60)); - INSTALL_HOOK_DYNAMIC (Send1, reinterpret_cast (amHandle + 0x16940)); - INSTALL_HOOK_DYNAMIC (Send2, reinterpret_cast (amHandle + 0x16990)); - INSTALL_HOOK_DYNAMIC (Send3, reinterpret_cast (amHandle + 0x16A00)); - INSTALL_HOOK_DYNAMIC (Send4, reinterpret_cast (amHandle + 0x16A30)); - INSTALL_HOOK_DYNAMIC (CopyData, reinterpret_cast (amHandle + 0x169D0)); - break; - } - default: { - break; - } - } -} -} // namespace patches::Qr diff --git a/src/patches/scanner.cpp b/src/patches/scanner.cpp new file mode 100644 index 0000000..4a9f723 --- /dev/null +++ b/src/patches/scanner.cpp @@ -0,0 +1,478 @@ +#include "constants.h" +#include "helpers.h" +#include "patches.h" +#include +#include +#include +#include +#include +#include +#include +#include +#define STB_IMAGE_IMPLEMENTATION +#define STBI_WINDOWS_UTF8 +#include "stb_image.h" + +extern GameVersion gameVersion; +extern std::vector plugins; +extern bool acceptInvalidCards; +extern bool emulateCardReader; +extern bool emulateQr; +extern char accessCode1[21]; +extern char accessCode2[21]; +extern char chipId1[33]; +extern char chipId2[33]; + +namespace patches::Scanner { +namespace Card { + State state = State::Disable; + + i32 *attachData; + u64 touchData; + CallbackAttach callbackAttach; + CallbackTouch callbackTouch; + + std::string accessCodeTemplate = "00000000000000000000"; + std::string chipIdTemplate = "00000000000000000000000000000000"; + + 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 + }; + + namespace Internal { + void + AgentInsertCard (uint8_t cardData[168], bool internalInvoke) { + if (callbackTouch) { + state = internalInvoke ? State::Disable : State::CopyWait; + patches::Plugins::UpdateStatus (1, false); + callbackTouch (0, 0, cardData, touchData); + } + } + + void + AgentInsertCardPlugin (int32_t a1, int32_t a2, uint8_t *a3, uint64_t a4) { + if (callbackTouch) { + state = State::CopyWait; + patches::Plugins::UpdateStatus (1, false); + callbackTouch (a1, a2, a3, a4); + } + } + + void + AgentInsertCardOfficial (int32_t a1, int32_t a2, uint8_t *a3, uint64_t a4) { + if (callbackTouch) { + state = State::CopyWait; + patches::Plugins::UpdateStatus (1, false); + if (acceptInvalidCards && !a3[0]) { + char AccessId[21] = "00000000000000000001"; + uint8_t UID[8] = {a3[12], a3[14], a3[15], a3[16], 0x90, 0x00, 0x00, 0x00}; + uint64_t ReversedAccessID; + for (int i = 0; i < 8; i++) + ReversedAccessID = (ReversedAccessID << 8) | UID[i]; + sprintf(AccessId, "%020llu", ReversedAccessID); + std::string accessCode = std::string (AccessId), chipId = ""; + if (chipId.length () == 0) chipId = accessCode; + if (chipId.length () < 32) chipId = accessCodeTemplate.substr (0, 32 - chipId.length ()) + chipId; + for (size_t i = 0; i < 32; i++) *(cardData + 0x2C + i) = i < chipId.length() ? chipId[i] : 0; + for (size_t i = 0; i < 20; i++) *(cardData + 0x50 + i) = i < accessCode.length() ? accessCode[i] : 0; + callbackTouch (0, 0, cardData, a4); + } else callbackTouch (a1, a2, a3, a4); + } + } + + bool + Commit0 (std::string accessCode, std::string chipId, bool internalInvoke) { + if (chipId.length () == 0) chipId = accessCode; + if (chipId.length () < 32) chipId = accessCodeTemplate.substr (0, 32 - chipId.length ()) + chipId; + for (size_t i = 0; i < 32; i++) *(cardData + 0x2C + i) = i < chipId.length() ? chipId[i] : 0; + for (size_t i = 0; i < 20; i++) *(cardData + 0x50 + i) = i < accessCode.length() ? accessCode[i] : 0; + if (!internalInvoke) LogMessage (LogLevel::INFO, "[Card] Insert Card accessCode: \"{}\" chipId: \"{}\"\n", accessCode, chipId); + patches::Scanner::Card::Internal::AgentInsertCard (cardData, internalInvoke); + return true; + } + } + + HOOK (u64, bngrw_Init, PROC_ADDRESS ("bngrw.dll", "BngRwInit")) { return 0; } + HOOK (void, bngrw_Fin, PROC_ADDRESS ("bngrw.dll", "BngRwFin")) { return; } + HOOK (u64, bngrw_IsCmdExec, PROC_ADDRESS ("bngrw.dll", "BngRwIsCmdExec")) { return 0xFFFFFFFF; } + HOOK (i32, bngrw_ReqSendUrl, PROC_ADDRESS ("bngrw.dll", "BngRwReqSendUrlTo")) { return 1; } + HOOK (u64, bngrw_ReqLed, PROC_ADDRESS ("bngrw.dll", "BngRwReqLed")) { return 1; } + HOOK (u64, bngrw_ReqBeep, PROC_ADDRESS ("bngrw.dll", "BngRwReqBeep")) { return 1; } + HOOK (u64, bngrw_ReqAction, PROC_ADDRESS ("bngrw.dll", "BngRwReqAction")) { return 1; } + HOOK (u64, bngrw_ReqSetLedPower, PROC_ADDRESS ("bngrw.dll", "BngRwReqSetLedPower")) { return 0; } + HOOK (u64, bngrw_GetRetryCount, PROC_ADDRESS ("bngrw.dll", "BngRwGetTotalRetryCount")) { return 0; } + HOOK (u64, bngrw_GetFwVersion, PROC_ADDRESS ("bngrw.dll", "BngRwGetFwVersion")) { return 0; } + HOOK (u64, bngrw_ReqFwVersionUp, PROC_ADDRESS ("bngrw.dll", "BngRwReqFwVersionUp")) { return 1; } + HOOK (u64, bngrw_ReqFwCleanup, PROC_ADDRESS ("bngrw.dll", "BngRwReqFwCleanup")) { return 1; } + HOOK (u64, bngrw_ReadMifare, PROC_ADDRESS ("bngrw.dll", "BngRwExReadMifareAllBlock")) { return 0xFFFFFF9C; } + HOOK (u64, bngrw_GetStationID, PROC_ADDRESS ("bngrw.dll", "BngRwGetStationID")) { return 0; } + HOOK (i32, bngrw_ReqSendMail, PROC_ADDRESS ("bngrw.dll", "BngRwReqSendMailTo")) { return 1; } + HOOK (i32, bngrw_ReqLatchID, PROC_ADDRESS ("bngrw.dll", "BngRwReqLatchID")) { return 1; } + HOOK (u64, bngrw_ReqAiccAuth, PROC_ADDRESS ("bngrw.dll", "BngRwReqAiccAuth")) { return 1; } + HOOK (u64, bngrw_DevReset, PROC_ADDRESS ("bngrw.dll", "BngRwDevReset")) { return 1; } // Invoke when enter testmode + HOOK (i32, bngrw_ReqCancel, PROC_ADDRESS ("bngrw.dll", "BngRwReqCancel")) { + LogMessage (LogLevel::DEBUG, "[Card] Cancel!\n"); + if (state != State::Disable) { + state = State::Disable; + patches::Scanner::Card::Internal::Commit0 (accessCodeTemplate, chipIdTemplate, true); + } + return 1; + } + HOOK (u64, bngrw_Attach, PROC_ADDRESS ("bngrw.dll", "BngRwAttach"), i32 a1, char *a2, i32 a3, i32 a4, CallbackAttach callback, i32 *a6) { + callbackAttach = callback; + attachData = a6; + return 1; + } + HOOK (u64, bngrw_ReqWaitTouch, PROC_ADDRESS ("bngrw.dll", "BngRwReqWaitTouch"), u32 a1, i32 a2, u32 a3, CallbackTouch callback, u64 a5) { + state = State::Ready; + patches::Plugins::UpdateStatus (1, true); + patches::Plugins::WaitTouch (Internal::AgentInsertCardPlugin, a5); + callbackTouch = callback; + touchData = a5; + return 1; + } + + HOOK (i64, bngrw_ReqCancelOfficial, PROC_ADDRESS ("bngrw.dll", "BngRwReqCancel"), u32 a1) { + if (state != State::Disable) { + state = State::Disable; + patches::Plugins::UpdateStatus (1, false); + } + return originalbngrw_ReqCancelOfficial(a1); + } + HOOK (u64, bngrw_ReqWaitTouchOfficial, PROC_ADDRESS ("bngrw.dll", "BngRwReqWaitTouch"), u32 a1, i32 a2, u32 a3, CallbackTouch callback, u64 a5) { + state = State::Ready; + patches::Plugins::UpdateStatus (1, true); + callbackTouch = callback; + touchData = a5; + return originalbngrw_ReqWaitTouchOfficial (a1, a2, a3, Internal::AgentInsertCardOfficial, a5); + } + + bool + Commit(std::string accessCode, std::string chipId) { + if (!emulateCardReader) { + LogMessage (LogLevel::DEBUG, "[Card] Not emulate CardReader!\n"); + return false; + } + if (state != State::Ready) { + LogMessage (LogLevel::DEBUG, "[Card] Not Waiting for Touch, please wait!\n"); + return false; + } + if (accessCode.length() == 0 || accessCode.length() > 20) { + LogMessage (LogLevel::ERROR, "[Card] Not an effective accessCode: \"{}\"\n", accessCode); + return false; + } + if (chipId.length() > 32) { + LogMessage (LogLevel::ERROR, "[Card] Not an effective chipId: \"{}\"\n", chipId); + return false; + } + return patches::Scanner::Card::Internal::Commit0 (accessCode, chipId, false); + } + + void + Update() { + if (callbackAttach) callbackAttach (0, 0, attachData); + } + + void + Init() { + LogMessage (LogLevel::INFO, "Init Card patches"); + if (!emulateCardReader) { + LogMessage (LogLevel::WARN, "[Card] Card reader emulation disabled!\n"); + INSTALL_HOOK (bngrw_ReqCancelOfficial); + INSTALL_HOOK (bngrw_ReqWaitTouchOfficial); + // patches::Plugins::InitCardReader (patches::Scanner::Card::Commit); + return; + } + + INSTALL_HOOK (bngrw_Init) + INSTALL_HOOK (bngrw_Fin); + INSTALL_HOOK (bngrw_IsCmdExec); + INSTALL_HOOK (bngrw_ReqCancel); + INSTALL_HOOK (bngrw_ReqWaitTouch); + INSTALL_HOOK (bngrw_ReqSendUrl); + INSTALL_HOOK (bngrw_ReqLed); + INSTALL_HOOK (bngrw_ReqBeep); + INSTALL_HOOK (bngrw_ReqAction); + INSTALL_HOOK (bngrw_ReqSetLedPower); + INSTALL_HOOK (bngrw_GetRetryCount); + INSTALL_HOOK (bngrw_GetFwVersion); + INSTALL_HOOK (bngrw_ReqFwVersionUp); + INSTALL_HOOK (bngrw_ReqFwCleanup); + INSTALL_HOOK (bngrw_ReadMifare); + INSTALL_HOOK (bngrw_GetStationID); + INSTALL_HOOK (bngrw_ReqSendMail); + INSTALL_HOOK (bngrw_ReqLatchID); + INSTALL_HOOK (bngrw_ReqAiccAuth); + INSTALL_HOOK (bngrw_Attach); + INSTALL_HOOK (bngrw_DevReset); + patches::Plugins::InitCardReader (patches::Scanner::Card::Commit); + } +} +namespace Qr { + std::queue> scanQueue; + State state = State::Disable; + long long lastScan; + + HOOK_DYNAMIC (char, QrInit, i64) { return 1; } + HOOK_DYNAMIC (char, QrClose, i64) { return 1; } + HOOK_DYNAMIC (char, QrRead, i64 a1) { + *(DWORD *)(a1 + 40) = 1; + *(DWORD *)(a1 + 16) = 1; + *(BYTE *)(a1 + 112) = 0; + return 1; + } + HOOK_DYNAMIC (i64, CallQrUnknown, i64) { return 1; } + HOOK_DYNAMIC (bool, Send1, i64 a1) { + *(BYTE *)(a1 + 88) = 1; + *(i64 *)(a1 + 32) = *(i64 *)(a1 + 24); + *(WORD *)(a1 + 89) = 0; + return true; + } + HOOK_DYNAMIC (bool, Send2, i64 a1) { + *(WORD *)(a1 + 88) = 0; + *(BYTE *)(a1 + 90) = 0; + return true; + } + HOOK_DYNAMIC (bool, Send3, i64, char) { return true; } + HOOK_DYNAMIC (bool, Send4, i64, const void *, i64) { return true; } + HOOK_DYNAMIC (i64, CopyData, i64, void *dest, int length) { + patches::Plugins::UsingQr (); + lastScan = std::chrono::duration_cast(std::chrono::system_clock::now ().time_since_epoch ()).count (); + if (state == State::CopyWait && scanQueue.size () > 0) { + std::vector data = scanQueue.front (); + if ((int)data.size () > length) { + LogMessage (LogLevel::ERROR, "[QR] Not an effective code, length: {} require: {}\n", data.size (), length); + } + std::stringstream hexStream; + hexStream << std::hex << std::uppercase << std::setfill ('0') << std::setw (2); + for (size_t i = 0; i < data.size (); i++) hexStream << static_cast (data[i]) << " "; + LogMessage (LogLevel::INFO, "[QR] Read QRData size: {} data: {}\n", data.size (), hexStream.str ()); + memcpy (dest, data.data (), data.size () + 1); + scanQueue.pop (); + if (scanQueue.size () == 0) state = State::Ready; + return data.size (); + } else if (state == State::Disable) { + while (!scanQueue.empty ()) scanQueue.pop (); + state = State::Ready; + patches::Plugins::UpdateStatus (2, true); + } + return 0; + } + + bool + Commit (std::vector &buffer) { + if (!emulateQr) { + LogMessage (LogLevel::DEBUG, "[QR] Not emulate QR Scanner!\n"); + return false; + } + if (state == State::Disable) { + LogMessage (LogLevel::DEBUG, "[QR] Not Ready to accept QRData!\n"); + return false; + } + if (buffer.size () == 0) { + LogMessage (LogLevel::ERROR, "[QR] Not an effective code, length: 0\n"); + return false; + } + if (scanQueue.empty() || scanQueue.back() != buffer) { + std::vector scanData = {}; + for (uint8_t byte_data : buffer) + scanData.push_back (byte_data); + scanQueue.push (scanData); + } + if (state == State::Ready) state = State::CopyWait; + return true; + } + + bool + CommitLogin (std::string accessCode) { + if (!emulateQr) { + LogMessage (LogLevel::DEBUG, "[QR] Not emulate QR Scanner!\n"); + return false; + } + std::vector buffer = {}; + std::string accessQRDataBase = "BNTTCNID"; + if (!accessCode._Starts_with (accessQRDataBase)) + for (char word : accessQRDataBase) buffer.push_back (word); + for (char word : accessCode) buffer.push_back (word); + return patches::Scanner::Qr::Commit (buffer); + } + + void + Update () { + if (state != State::Disable) { + if ((lastScan + 200) < std::chrono::duration_cast(std::chrono::system_clock::now ().time_since_epoch ()).count ()) { + state = State::Disable; + patches::Plugins::UpdateStatus (2, false); + } else { + void *plugin = patches::Plugins::CheckQr (); + if (plugin) { + uint8_t *space = (uint8_t *)calloc (600, sizeof (uint8_t)); + size_t size = patches::Plugins::GetQr (plugin, 600, space); + if (size > 0) { + std::vector data = {}; + for (size_t i = 0; i < size; i ++) data.push_back (space[i]); + patches::Scanner::Qr::Commit (data); + } + } + } + } + } + + std::vector & + ReadQRData (std::vector &buffer) { + std::string serial = ""; + u16 type = 0; + std::vector songNoes; + + buffer.clear (); + auto configPath = std::filesystem::current_path () / "config.toml"; + std::unique_ptr config_ptr (openConfig (configPath), toml_free); + if (config_ptr) { + auto qr = openConfigSection (config_ptr.get (), "qr"); + if (qr) { + auto data = openConfigSection (qr, "data"); + if (data) { + serial = readConfigString (data, "serial", ""); + type = (u16) readConfigInt (data, "type", 0); + songNoes = readConfigIntArray (data, "song_no", songNoes); + } + } + } + std::vector header = { 0x53, 0x31, 0x32, 0x00, 0x00, 0xFF, 0xFF, (uint8_t)serial.size (), 0x01, 0x00 }; + for (uint8_t byte_data : header) buffer.push_back (byte_data); + for (char word : serial) buffer.push_back ((uint8_t)word); + if (type == 5) { + std::vector folder = { 0xFF, 0xFF, (uint8_t)(songNoes.size () * 2), (uint8_t)(type & 0xFF), (uint8_t)((type >> 8) & 0xFF) }; + for (uint8_t byte_data : folder) buffer.push_back (byte_data); + for (i64 songNo : songNoes) { + buffer.push_back ((uint8_t)(songNo & 0xFF)); + buffer.push_back ((uint8_t)((songNo >> 8) & 0xFF)); + } + } + buffer.push_back (0xEE); + buffer.push_back (0xFF); + return buffer; + } + + std::vector & + ReadQRImage (std::vector &buffer) { + std::string imagePath = ""; + + buffer.clear (); + auto configPath = std::filesystem::current_path () / "config.toml"; + std::unique_ptr config_ptr (openConfig (configPath), toml_free); + if (config_ptr) { + auto qr = openConfigSection (config_ptr.get (), "qr"); + if (qr) imagePath = readConfigString (qr, "image_path", ""); + } + std::u8string u8PathStr (imagePath.begin (), imagePath.end ()); + std::filesystem::path u8Path (u8PathStr); + if (!std::filesystem::is_regular_file (u8Path)) { + LogMessage (LogLevel::ERROR, "Failed to open image: {} (file not found)\n", u8Path.string()); + return buffer; + } + int width, height, channels; + std::unique_ptr img_buffer (stbi_load (u8Path.string ().c_str (), &width, &height, &channels, 3), stbi_image_free); + if (!img_buffer) { + LogMessage (LogLevel::ERROR, "Failed to read image: {} ({})\n", u8Path.string(), stbi_failure_reason ()); + return buffer; + } + ZXing::ImageView image{img_buffer.get (), width, height, ZXing::ImageFormat::RGB}; + auto result = ReadBarcode (image); + if (!result.isValid ()) { + LogMessage (LogLevel::ERROR, "Failed to read QR: {} ({})\n", u8Path.string(), ToString (result.error ())); + return buffer; + } + for (uint8_t byte_data : result.bytes()) buffer.push_back (byte_data); + return buffer; + } + + void + Init () { + LogMessage (LogLevel::INFO, "Init Qr patches"); + + if (!emulateQr) { + LogMessage (LogLevel::WARN, "[QR] QR emulation disabled!\n"); + return; + } + patches::Plugins::InitQr (gameVersion); + SetConsoleOutputCP (CP_UTF8); + auto amHandle = reinterpret_cast (GetModuleHandle ("AMFrameWork.dll")); + switch (gameVersion) { + case GameVersion::JPN00: { + INSTALL_HOOK_DYNAMIC (QrInit, (LPVOID)(amHandle + 0x1B3E0)); + INSTALL_HOOK_DYNAMIC (QrClose, (LPVOID)(amHandle + 0x1B5B0)); + INSTALL_HOOK_DYNAMIC (QrRead, (LPVOID)(amHandle + 0x1B600)); + INSTALL_HOOK_DYNAMIC (CallQrUnknown, (LPVOID)(amHandle + 0xFD40)); + INSTALL_HOOK_DYNAMIC (Send1, (LPVOID)(amHandle + 0x1BBB0)); + INSTALL_HOOK_DYNAMIC (Send2, (LPVOID)(amHandle + 0x1BBF0)); + INSTALL_HOOK_DYNAMIC (Send3, (LPVOID)(amHandle + 0x1BC60)); + // JPN00 has no Send4 + INSTALL_HOOK_DYNAMIC (CopyData, (LPVOID)(amHandle + 0x1BC30)); + break; + } + case GameVersion::JPN08: { + INSTALL_HOOK_DYNAMIC (QrInit, (LPVOID)(amHandle + 0x1BA00)); + INSTALL_HOOK_DYNAMIC (QrClose, (LPVOID)(amHandle + 0x1BBD0)); + INSTALL_HOOK_DYNAMIC (QrRead, (LPVOID)(amHandle + 0x1BC20)); + INSTALL_HOOK_DYNAMIC (CallQrUnknown, (LPVOID)(amHandle + 0xFD40)); + INSTALL_HOOK_DYNAMIC (Send1, (LPVOID)(amHandle + 0x1C220)); + INSTALL_HOOK_DYNAMIC (Send2, (LPVOID)(amHandle + 0x1C260)); + INSTALL_HOOK_DYNAMIC (Send3, (LPVOID)(amHandle + 0x1C2D0)); + // JPN08 has no Send4 + INSTALL_HOOK_DYNAMIC (CopyData, (LPVOID)(amHandle + 0x1C2A0)); + break; + } + case GameVersion::JPN39: { + INSTALL_HOOK_DYNAMIC (QrInit, (LPVOID)(amHandle + 0x1EDC0)); + INSTALL_HOOK_DYNAMIC (QrClose, (LPVOID)(amHandle + 0x1EF60)); + INSTALL_HOOK_DYNAMIC (QrRead, (LPVOID)(amHandle + 0x1EFB0)); + INSTALL_HOOK_DYNAMIC (CallQrUnknown, (LPVOID)(amHandle + 0x11A70)); + INSTALL_HOOK_DYNAMIC (Send1, (LPVOID)(amHandle + 0x1F5B0)); + INSTALL_HOOK_DYNAMIC (Send2, (LPVOID)(amHandle + 0x1F5F0)); + INSTALL_HOOK_DYNAMIC (Send3, (LPVOID)(amHandle + 0x1F660)); + INSTALL_HOOK_DYNAMIC (Send4, (LPVOID)(amHandle + 0x1F690)); + INSTALL_HOOK_DYNAMIC (CopyData, (LPVOID)(amHandle + 0x1F630)); + break; + } + case GameVersion::CHN00: { + INSTALL_HOOK_DYNAMIC (QrInit, (LPVOID)(amHandle + 0x161B0)); + INSTALL_HOOK_DYNAMIC (QrClose, (LPVOID)(amHandle + 0x16350)); + INSTALL_HOOK_DYNAMIC (QrRead, (LPVOID)(amHandle + 0x163A0)); + INSTALL_HOOK_DYNAMIC (CallQrUnknown, (LPVOID)(amHandle + 0x8F60)); + INSTALL_HOOK_DYNAMIC (Send1, (LPVOID)(amHandle + 0x16940)); + INSTALL_HOOK_DYNAMIC (Send2, (LPVOID)(amHandle + 0x16990)); + INSTALL_HOOK_DYNAMIC (Send3, (LPVOID)(amHandle + 0x16A00)); + INSTALL_HOOK_DYNAMIC (Send4, (LPVOID)(amHandle + 0x16A30)); + INSTALL_HOOK_DYNAMIC (CopyData, (LPVOID)(amHandle + 0x169D0)); + break; + } + default: { + break; + } + } + patches::Plugins::InitQRScanner (patches::Scanner::Qr::Commit); + patches::Plugins::InitQRLogin (patches::Scanner::Qr::CommitLogin); + } +} + +void +Update() { + patches::Scanner::Card::Update (); + patches::Scanner::Qr::Update (); +} + +void +Init() { + LogMessage (LogLevel::INFO, "Init Scanner patches"); + patches::Scanner::Card::Init (); + patches::Scanner::Qr::Init (); +} +} \ No newline at end of file diff --git a/src/patches/testmode.cpp b/src/patches/testmode.cpp index 199e044..9ea3055 100644 --- a/src/patches/testmode.cpp +++ b/src/patches/testmode.cpp @@ -17,6 +17,17 @@ class RegisteredItem { this->registerInit = initMethod; } }; +class RegisteredSingleItem { +public: + std::wstring query; + std::wstring selectItem; + std::function registerInit; + RegisteredSingleItem (const std::wstring query, const std::wstring selectItem, const std::function &initMethod) { + this->query = query; + this->selectItem = selectItem; + this->registerInit = initMethod; + } +}; class RegisteredModify { public: std::wstring query; @@ -29,10 +40,11 @@ class RegisteredModify { } }; -std::vector registeredItems = {}; -std::vector registeredModifies = {}; -std::wstring moddedInitial = L""; -std::wstring modded = L""; +std::vector registeredItems = {}; +std::vector registeredSingleItems = {}; +std::vector registeredModifies = {}; +std::wstring moddedInitial = L""; +std::wstring modded = L""; u64 appAccessor = 0; RefTestModeMain refTestMode = nullptr; @@ -51,7 +63,7 @@ CreateMenu (pugi::xml_document &menuMain, const std::wstring &menuId, const std: menuHeader.append_attribute (L"type") = L"Header"; menuHeader.append_child (L"break-item"); pugi::xml_node menuTitle = menuHeader.append_child (L"text-item"); - const std::wstring menuNameFull = L" " + menuName; + const 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"); @@ -84,8 +96,8 @@ CreateMenu (pugi::xml_document &menuMain, const std::wstring &menuId, const std: std::wstring ReadXMLFileSwitcher (std::wstring &fileName) { const std::size_t pos = fileName.rfind (L"/"); - std::wstring base = fileName.substr (0, pos + 1); const std::wstring file = fileName.substr (pos + 1, fileName.size ()); + std::wstring base = fileName.substr (0, pos + 1); if (gameVersion == GameVersion::JPN39 && chsPatch) { if (file.starts_with (L"DeviceInitialize")) base.append (L"DeviceInitialize_china.xml"); @@ -98,7 +110,7 @@ ReadXMLFileSwitcher (std::wstring &fileName) { HOOK_DYNAMIC (void, TestModeSetMenuHook, u64 testModeLibrary, const wchar_t *lFileName) { const auto originalFileName = std::wstring (lFileName); - std::wstring fileName = originalFileName; + 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"") { @@ -156,6 +168,26 @@ HOOK_DYNAMIC (void, TestModeSetMenuHook, u64 testModeLibrary, const wchar_t *lFi topMenu.parent ().insert_copy_after (modMenu.first_child (), topMenu); } + if (!registeredSingleItems.empty()) { + for (RegisteredSingleItem *item : registeredSingleItems) { + pugi::xpath_query singleQuery = pugi::xpath_query(item->query.c_str()); + try { + pugi::xml_node singleNode = doc.select_node(singleQuery).node(); + if (singleNode) { + pugi::xml_document singleItemDoc; + std::wstring itemLine = L"" + item->selectItem + L""; + if (singleItemDoc.load_string(itemLine.c_str())) { + pugi::xml_node breakItem = singleNode.parent().insert_child_after(L"break-item", singleNode); + singleNode.parent().insert_copy_after(singleItemDoc.first_child().first_child(), breakItem); + item->registerInit(); + } else { + LogMessage (LogLevel::ERROR, L"Failed to parse option line: {}\n", item->selectItem); + } + } + } catch ([[maybe_unused]] std::exception &e) { LogMessage (LogLevel::ERROR, L"Failed to append node by xpath: {}\n", item->query); } + } + } + if (!registeredModifies.empty ()) { for (const RegisteredModify *modify : registeredModifies) { auto modifyQuery = pugi::xpath_query (modify->query.c_str ()); try { @@ -163,7 +195,7 @@ HOOK_DYNAMIC (void, TestModeSetMenuHook, u64 testModeLibrary, const wchar_t *lFi modify->nodeModify (modifyNode); modify->registerInit (); } - } catch ([[maybe_unused]] std::exception &e) { LogMessage (LogLevel::ERROR, L"Failed to find node by xpath: " + modify->query); } + } catch ([[maybe_unused]] std::exception &e) { LogMessage (LogLevel::ERROR, L"Failed to find node by xpath: {}\n", modify->query); } } } @@ -189,50 +221,81 @@ CommonModify () { void LocalizationCHT () { - RegisterModify ( + TestMode::RegisterModify( L"/root/menu[@id='TopMenu']/layout[@type='Center']/menu-item[@menu='ModManagerMenu']", - [&] (const pugi::xml_node &node) { node.attribute (L"label").set_value (L"模組管理"); }, [] {}); - RegisterModify ( + [&](pugi::xml_node &node) { node.attribute(L"label").set_value(L"模組管理"); }, [](){} + ); + TestMode::RegisterModify( L"/root/menu[@id='ModManagerMenu']/layout[@type='Header']/text-item", - [&] (const pugi::xml_node &node) { node.attribute (L"label").set_value (L"模組管理"); }, [] {}); - RegisterModify ( + [&](pugi::xml_node &node) { node.attribute(L"label").set_value(L"模組管理"); }, [](){} + ); + TestMode::RegisterModify( + L"/root/menu[@id='ModManagerMenu']/layout[@type='Center']/select-item[@id='ModFixLanguage']", + [&](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='ModUnlockSongs']", + [&](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='ModFreezeTimer']", - [&] (const pugi::xml_node &node) { - node.attribute (L"label").set_value (L"凍結計時"); - node.attribute (L"replace-text").set_value (L"0:關閉, 1:開啓"); - }, - [] {}); - RegisterModify ( + [&](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']", - [&] (const pugi::xml_node &node) { - node.attribute (L"label").set_value (L"鬼滅之刃模式"); - node.attribute (L"replace-text").set_value (L"0:黙認, 1:啓用, 2:僅刷卡"); - }, - [] {}); - RegisterModify ( + [&](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']", - [&] (const pugi::xml_node &node) { - node.attribute (L"label").set_value (L"航海王模式"); - node.attribute (L"replace-text").set_value (L"0:黙認, 1:啓用, 2:僅刷卡"); - }, - [] {}); - RegisterModify ( + [&](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']", - [&] (const pugi::xml_node &node) { - node.attribute (L"label").set_value (L"AI粗品模式"); - node.attribute (L"replace-text").set_value (L"0:黙認, 1:啓用, 2:僅刷卡"); - }, - [] {}); - RegisterModify ( + [&](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']", - [&] (const pugi::xml_node &node) { - node.attribute (L"label").set_value (L"青春之達人模式"); - node.attribute (L"replace-text").set_value (L"0:黙認, 1:啓用, 2:僅刷卡"); - }, - [] {}); - RegisterModify ( + [&](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='ModInstantResult']", + [&](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']/menu-item[@menu='TopMenu']", - [&] (const pugi::xml_node &node) { node.attribute (L"label").set_value (L"離開"); }, [] {}); + [&](pugi::xml_node &node) { node.attribute(L"label").set_value(L"離開"); }, [](){} + ); + TestMode::RegisterModify( + L"/root/menu[@id='OthersMenu']/layout[@type='Center']/select-item[@id='AttractDemoItem']", + [&](pugi::xml_node &node) { + node.attribute(L"label").set_value(L"演示遊玩影片"); + node.attribute(L"replace-text").set_value(L"0:關閉, 1:開啓"); + }, [](){} + ); } void @@ -326,13 +389,19 @@ ReadTestModeValue (const wchar_t *itemId) { void RegisterItem (const std::wstring &item, const std::function &initMethod) { - LogMessage (LogLevel::DEBUG, L"Register Item " + item); + LogMessage (LogLevel::DEBUG, L"Register \n Item: {}", item); registeredItems.push_back (new RegisteredItem (item, initMethod)); } +void +RegisterItemAfter (const std::wstring &query, const std::wstring &item, const std::function &initMethod) { + LogMessage (LogLevel::DEBUG, L"Register \n Query: {} \n Item: {}", query, item); + registeredSingleItems.push_back (new RegisteredSingleItem (query, item, initMethod)); +} + void RegisterModify (const std::wstring &query, const std::function &nodeModify, const std::function &initMethod) { - LogMessage (LogLevel::DEBUG, L"Register Modify " + query); + LogMessage (LogLevel::DEBUG, L"Register \n Modify: {}", query); registeredModifies.push_back (new RegisteredModify (query, nodeModify, initMethod)); } diff --git a/src/patches/versions/JPN39.cpp b/src/patches/versions/JPN39.cpp index c11a89d..ce29670 100644 --- a/src/patches/versions/JPN39.cpp +++ b/src/patches/versions/JPN39.cpp @@ -61,6 +61,18 @@ GetUserStatus () { return -1; } +HOOK (bool, IsSongRelease, ASLR (0x1403F4510), i64 a1, i64 a2, int a3) { + if (TestMode::ReadTestModeValue (L"ModUnlockSongs") == 1) return true; + return originalIsSongRelease (a1, a2, a3); +} +HOOK (bool, IsSongReleasePlayer, ASLR (0x1403F45F0), i64 a1, u64 a2, i32 a3) { + if (TestMode::ReadTestModeValue (L"ModUnlockSongs") == 2) return true; + return originalIsSongReleasePlayer (a1, a2, a3); +} +MID_HOOK (DifficultyPanelCrown, ASLR (0x1403F2A25), SafetyHookContext &ctx) { + if (TestMode::ReadTestModeValue (L"ModUnlockSongs") != 1) return; + ctx.r15 |= 1; +} HOOK (i64, AvailableMode_Collabo024, ASLR (0x1402DE710), i64 a1) { LogMessage (LogLevel::HOOKS, "AvailableMode_Collabo024 was called"); if (const int tournamentMode = TestMode::ReadTestModeValue (L"TournamentMode"); tournamentMode == 1) return originalAvailableMode_Collabo024 (a1); @@ -184,15 +196,20 @@ HOOK (i64, GetLanguage, ASLR (0x140024AC0), i64 a1) { } HOOK (i64, GetRegionLanguage, ASLR (0x1401CE9B0), i64 a1) { LogMessage (LogLevel::HOOKS, "GetRegionLanguage was called"); - lua_settop (a1, 0); - lua_pushstring (a1, languageStr (language)); - return 1; -} -HOOK (i64, GetCabinetLanguage, ASLR (0x1401D1A60), i64, i64 a2) { + if (patches::TestMode::ReadTestModeValue (L"ModFixLanguage") == 1) { + lua_settop (a1, 0); + lua_pushstring (a1, languageStr (language)); + return 1; + } else return originalGetRegionLanguage (a1); +} +FUNCTION_PTR (void **, std_string_assign, ASLR (0x1400209E0), void **, const void *, size_t); +HOOK (i64, GetCabinetLanguage, ASLR (0x14014DB80), u64 *a1, i64 a2) { LogMessage (LogLevel::HOOKS, "GetCabinetLanguage was called"); - lua_settop (a2, 0); - lua_pushstring (a2, languageStr (language)); - return 1; + i64 result = originalGetCabinetLanguage (a1, a2); + if (patches::TestMode::ReadTestModeValue (L"ModFixLanguage") == 1) { + std_string_assign ((void **)result, (const void*)languageStr (language), (language == 0 || language == 3) ? 3 : 5); + } + return result; } MID_HOOK (ChangeLanguageType, ASLR (0x1400B2016), SafetyHookContext &ctx) { @@ -353,15 +370,10 @@ HOOK (i64, LoadedBankAll, ASLR (0x1404C69F0), i64 a1) { float soundRate = 1.0F; HOOK (i32, SetMasterVolumeSpeaker, ASLR (0x140160330), i32 a1) { LogMessage (LogLevel::HOOKS, "SetMasterVolumeSpeaker was called"); - soundRate = static_cast (a1 <= 100 ? 1.0F : a1 / 100.0); + patches::Audio::SetVolumeRate (a1 <= 100 ? 1.0f : a1 / 100.0f); return originalSetMasterVolumeSpeaker (a1 > 100 ? 100 : a1); } -HOOK (u64, NuscBusVolume, ASLR (0x1407B1C30), u64 a1, u64 a2, float a3) { - LogMessage (LogLevel::HOOKS, "NuscBusVolume was called"); - return originalNuscBusVolume (a1, a2, a3 * soundRate); -} - std::string *fontName = nullptr; HOOK (u8, SetupFontInfo, ASLR (0x14049D820), u64 a1, u64 a2, size_t a3, u64 a4) { LogMessage (LogLevel::HOOKS, "SetupFontInfo was called"); @@ -378,6 +390,17 @@ HOOK (u32, ReadFontInfoInt, ASLR (0x14049EAC0), u64 a1, u64 a2) { return result; } +MID_HOOK (AttractDemo, ASLR (0x14045A720), SafetyHookContext &ctx) { + if (TestMode::ReadTestModeValue (L"AttractDemoItem") == 1) ctx.r14 = 0; +} + +HOOK (float, InitPlay, ASLR (0x1401345A0), u64 a1, i64 a2, i32 a3) { + LogMessage (LogLevel::DEBUG, "Enter InitPlay"); + float result = originalInitPlay (a1, a2, a3); + LogMessage (LogLevel::DEBUG, "Exit InitPlay"); + return result; +} + constexpr i32 datatableBufferSize = 1024 * 1024 * 12; safetyhook::Allocation datatableBuffer1; safetyhook::Allocation datatableBuffer2; @@ -441,6 +464,7 @@ Init () { // Hook to get AppAccessor and ComponentAccessor INSTALL_HOOK (DeviceCheck); INSTALL_HOOK (luaL_newstate); + INSTALL_HOOK (InitPlay); // Apply common config patch WRITE_MEMORY (ASLR (0x140494533), i32, xRes); @@ -499,44 +523,66 @@ Init () { ReplaceLeaBufferAddress (datatableBuffer3Addresses, datatableBuffer3.data ()); } + // Fix Language + TestMode::RegisterItem( + L"", + [&]() { INSTALL_HOOK (GetLanguage); INSTALL_HOOK (GetRegionLanguage); INSTALL_HOOK (GetCabinetLanguage); } + ); + // Unlock Songs + TestMode::RegisterItem( + L"", + [&]() { INSTALL_HOOK (IsSongRelease); INSTALL_HOOK (IsSongReleasePlayer); INSTALL_MID_HOOK (DifficultyPanelCrown); } + ); // Freeze Timer - TestMode::RegisterItem (L"", - [&] { INSTALL_MID_HOOK (FreezeTimer); }); + 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); - }); + 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']", @@ -545,10 +591,14 @@ Init () { node.attribute (L"max").set_value (L"300"); node.attribute (L"delta").set_value (L"1"); }, - [&] () { - INSTALL_HOOK (SetMasterVolumeSpeaker); - INSTALL_HOOK (NuscBusVolume); - }); + [&] () { INSTALL_HOOK (SetMasterVolumeSpeaker); } + ); + TestMode::RegisterItemAfter( + L"/root/menu[@id='OthersMenu']/layout[@type='Center']/select-item[@id='AttractMovieItem']", + L"", + [&](){ INSTALL_MID_HOOK (AttractDemo); } + ); + // Instant Result // TestMode::RegisterModify( // L"/root/menu[@id='GameOptionsMenu']/layout[@type='Center']/select-item[@id='NumberOfStageItem']",