Refactor Plugins API / Card API / Qr API
Kinda fix exit voice issue by enter testmode before quit fix bnusio::Update not invoked issue when fpsLimit=0 add option in config.toml for cursor (mouse) move FixLanguage to testmode, upgrade it to support release attractdemo add attractdemo control switch after attractmovie (only enable when FixLanguage on) move unlock songs to testmode, add force option to release shop songs move NusBusVolumeHook to audio patches
This commit is contained in:
parent
0988da9268
commit
7dc76ed958
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
2
dist/config.toml
vendored
2
dist/config.toml
vendored
@ -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
|
||||
|
||||
|
160
src/bnusio.cpp
160
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<HMODULE> 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<waitTouchEvent *> (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<toml_table_t, void (*) (toml_table_t *)> 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<toml_table_t, void (*) (toml_table_t *)> 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<uint8_t> 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<event *> (insertEvent) ();
|
||||
insertEvent = GetProcAddress (plugin, p1 ? "Card1Insert" : "Card2Insert");
|
||||
if (insertEvent) {
|
||||
reinterpret_cast<event *> (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<event *> (exitEvent) ();
|
||||
|
||||
patches::Plugins::Exit ();
|
||||
CleanupLogger ();
|
||||
}
|
||||
} // namespace bnusio
|
||||
|
@ -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 ();
|
||||
|
@ -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
|
||||
|
11
src/logger.h
11
src/logger.h
@ -3,6 +3,7 @@
|
||||
#include "helpers.h"
|
||||
#include <source_location>
|
||||
#include <string_view>
|
||||
#include <format>
|
||||
#include <wincon.h>
|
||||
|
||||
#define STRING(x) #x
|
||||
@ -41,14 +42,16 @@ void LogMessageHandler (const char *function, const char *codeFile, int codeLine
|
||||
*/
|
||||
template <typename... Args>
|
||||
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<Args> (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<Args> (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 ());
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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: {
|
||||
|
@ -212,16 +212,19 @@ Init () {
|
||||
}
|
||||
|
||||
FpsLimiterEnable = fpsLimit > 0;
|
||||
FpsLimiter::Init (static_cast<float> (fpsLimit));
|
||||
|
||||
MH_Initialize ();
|
||||
MH_CreateHookApi (L"dxgi.dll", "CreateDXGIFactory", reinterpret_cast<LPVOID> (CreateDXGIFactoryWrap),
|
||||
reinterpret_cast<void **> (&g_origCreateDXGIFactory));
|
||||
MH_CreateHookApi (L"dxgi.dll", "CreateDXGIFactory2", reinterpret_cast<LPVOID> (CreateDXGIFactory2Wrap),
|
||||
reinterpret_cast<void **> (&g_origCreateDXGIFactory2));
|
||||
MH_CreateHookApi (L"d3d11.dll", "D3D11CreateDeviceAndSwapChain", reinterpret_cast<LPVOID> (D3D11CreateDeviceAndSwapChainWrap),
|
||||
reinterpret_cast<void **> (&g_origD3D11CreateDeviceAndSwapChain));
|
||||
MH_EnableHook (nullptr);
|
||||
if (FpsLimiterEnable) {
|
||||
FpsLimiter::Init (static_cast<float> (fpsLimit));
|
||||
|
||||
MH_Initialize ();
|
||||
MH_CreateHookApi (L"dxgi.dll", "CreateDXGIFactory", reinterpret_cast<LPVOID> (CreateDXGIFactoryWrap),
|
||||
reinterpret_cast<void **> (&g_origCreateDXGIFactory));
|
||||
MH_CreateHookApi (L"dxgi.dll", "CreateDXGIFactory2", reinterpret_cast<LPVOID> (CreateDXGIFactory2Wrap),
|
||||
reinterpret_cast<void **> (&g_origCreateDXGIFactory2));
|
||||
MH_CreateHookApi (L"d3d11.dll", "D3D11CreateDeviceAndSwapChain", reinterpret_cast<LPVOID> (D3D11CreateDeviceAndSwapChainWrap),
|
||||
reinterpret_cast<void **> (&g_origD3D11CreateDeviceAndSwapChain));
|
||||
MH_EnableHook (nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace patches::Dxgi
|
@ -3,6 +3,8 @@
|
||||
#include <functional>
|
||||
#include <pugixml.hpp>
|
||||
|
||||
#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<std::string (const std::string, const std::string)> &fileHandler);
|
||||
void RegisterAfter (const std::function<std::string (const std::string, const std::string)> &fileHandler);
|
||||
void RegisterAfter (const std::function<std::string (const std::string, const std::string)> &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<void ()> &initMethod);
|
||||
void RegisterModify (const std::wstring& query, const std::function<void (pugi::xml_node &)> &nodeModify, const std::function<void ()> &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<void ()> &initMethod);
|
||||
void RegisterItemAfter (const std::wstring &query, const std::wstring &item, const std::function<void()> &initMethod);
|
||||
void RegisterModify (const std::wstring &query, const std::function<void (pugi::xml_node &)> &nodeModify, const std::function<void ()> &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<uint8_t> &);
|
||||
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<uint8_t> &buffer);
|
||||
bool CommitLogin (std::string accessCode);
|
||||
std::vector<uint8_t> &ReadQRData (std::vector<uint8_t> &buffer);
|
||||
std::vector<uint8_t> &ReadQRImage (std::vector<uint8_t> &buffer);
|
||||
} // namespace Qr
|
||||
} // namespace Scanner
|
||||
} // namespace patches
|
||||
|
137
src/patches/plugins.cpp
Normal file
137
src/patches/plugins.cpp
Normal file
@ -0,0 +1,137 @@
|
||||
#include "constants.h"
|
||||
#include "helpers.h"
|
||||
#include "patches.h"
|
||||
|
||||
namespace patches::Plugins {
|
||||
std::vector<HMODULE> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -1,298 +0,0 @@
|
||||
#include "constants.h"
|
||||
#include "helpers.h"
|
||||
#include "poll.h"
|
||||
#include <ReadBarcode.h>
|
||||
#include <vector>
|
||||
#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<HMODULE> 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<HMODULE> 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<DWORD *> (a1 + 40) = 1;
|
||||
*reinterpret_cast<DWORD *> (a1 + 16) = 1;
|
||||
*reinterpret_cast<BYTE *> (a1 + 112) = 0;
|
||||
return 1;
|
||||
}
|
||||
HOOK_DYNAMIC (i64, CallQrUnknown, i64) { return 1; }
|
||||
HOOK_DYNAMIC (bool, Send1, i64 a1) {
|
||||
*reinterpret_cast<BYTE *> (a1 + 88) = 1;
|
||||
*reinterpret_cast<i64 *> (a1 + 32) = *reinterpret_cast<i64 *> (a1 + 24);
|
||||
*reinterpret_cast<WORD *> (a1 + 89) = 0;
|
||||
return true;
|
||||
}
|
||||
HOOK_DYNAMIC (bool, Send2, i64 a1) {
|
||||
*reinterpret_cast<WORD *> (a1 + 88) = 0;
|
||||
*reinterpret_cast<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) {
|
||||
if (gState == State::CopyWait) {
|
||||
// std::cout << "Copy data, length: " << length << std::endl;
|
||||
auto configPath = std::filesystem::current_path () / "config.toml";
|
||||
std::unique_ptr<toml_table_t, void (*) (toml_table_t *)> 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<i64> 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<u16> (readConfigInt (data, "type", 0));
|
||||
songNoes = readConfigIntArray (data, "song_no", songNoes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BYTE serial_length = static_cast<BYTE> (serial.size ());
|
||||
std::vector<BYTE> byteBuffer = {0x53, 0x31, 0x32, 0x00, 0x00, 0xFF, 0xFF, serial_length, 0x01, 0x00};
|
||||
|
||||
for (char c : serial)
|
||||
byteBuffer.push_back (static_cast<BYTE> (c));
|
||||
|
||||
if (type == 5) {
|
||||
std::vector<BYTE> folderData = {0xFF, 0xFF};
|
||||
|
||||
folderData.push_back (static_cast<u8> (songNoes.size ()) * 2);
|
||||
|
||||
folderData.push_back (static_cast<u8> (type & 0xFF));
|
||||
folderData.push_back (static_cast<u8> (type >> 8 & 0xFF));
|
||||
|
||||
for (i64 songNo : songNoes) {
|
||||
folderData.push_back (static_cast<u8> (songNo & 0xFF));
|
||||
folderData.push_back (static_cast<u8> (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<int> (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<stbi_uc, void (*) (void *)> 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<unsigned char> plugin_data (length);
|
||||
int buf_len = reinterpret_cast<getQrEvent *> (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<int> (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<event *> (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<checkQrEvent *> (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<initQrEvent *> (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<u64> (GetModuleHandle ("AMFrameWork.dll"));
|
||||
switch (gameVersion) {
|
||||
case GameVersion::JPN00: {
|
||||
INSTALL_HOOK_DYNAMIC (QrInit, reinterpret_cast<LPVOID> (amHandle + 0x1B3E0));
|
||||
INSTALL_HOOK_DYNAMIC (QrClose, reinterpret_cast<LPVOID> (amHandle + 0x1B5B0));
|
||||
INSTALL_HOOK_DYNAMIC (QrRead, reinterpret_cast<LPVOID> (amHandle + 0x1B600));
|
||||
INSTALL_HOOK_DYNAMIC (CallQrUnknown, reinterpret_cast<LPVOID> (amHandle + 0xFD40));
|
||||
INSTALL_HOOK_DYNAMIC (Send1, reinterpret_cast<LPVOID> (amHandle + 0x1BBB0));
|
||||
INSTALL_HOOK_DYNAMIC (Send2, reinterpret_cast<LPVOID> (amHandle + 0x1BBF0));
|
||||
INSTALL_HOOK_DYNAMIC (Send3, reinterpret_cast<LPVOID> (amHandle + 0x1BC60));
|
||||
// JPN00 has no Send4
|
||||
INSTALL_HOOK_DYNAMIC (CopyData, reinterpret_cast<LPVOID> (amHandle + 0x1BC30));
|
||||
break;
|
||||
}
|
||||
case GameVersion::JPN08: {
|
||||
INSTALL_HOOK_DYNAMIC (QrInit, reinterpret_cast<LPVOID> (amHandle + 0x1BA00));
|
||||
INSTALL_HOOK_DYNAMIC (QrClose, reinterpret_cast<LPVOID> (amHandle + 0x1BBD0));
|
||||
INSTALL_HOOK_DYNAMIC (QrRead, reinterpret_cast<LPVOID> (amHandle + 0x1BC20));
|
||||
INSTALL_HOOK_DYNAMIC (CallQrUnknown, reinterpret_cast<LPVOID> (amHandle + 0xFD40));
|
||||
INSTALL_HOOK_DYNAMIC (Send1, reinterpret_cast<LPVOID> (amHandle + 0x1C220));
|
||||
INSTALL_HOOK_DYNAMIC (Send2, reinterpret_cast<LPVOID> (amHandle + 0x1C260));
|
||||
INSTALL_HOOK_DYNAMIC (Send3, reinterpret_cast<LPVOID> (amHandle + 0x1C2D0));
|
||||
// JPN08 has no Send4
|
||||
INSTALL_HOOK_DYNAMIC (CopyData, reinterpret_cast<LPVOID> (amHandle + 0x1C2A0));
|
||||
break;
|
||||
}
|
||||
case GameVersion::JPN39: {
|
||||
INSTALL_HOOK_DYNAMIC (QrInit, reinterpret_cast<LPVOID> (amHandle + 0x1EDC0));
|
||||
INSTALL_HOOK_DYNAMIC (QrClose, reinterpret_cast<LPVOID> (amHandle + 0x1EF60));
|
||||
INSTALL_HOOK_DYNAMIC (QrRead, reinterpret_cast<LPVOID> (amHandle + 0x1EFB0));
|
||||
INSTALL_HOOK_DYNAMIC (CallQrUnknown, reinterpret_cast<LPVOID> (amHandle + 0x11A70));
|
||||
INSTALL_HOOK_DYNAMIC (Send1, reinterpret_cast<LPVOID> (amHandle + 0x1F5B0));
|
||||
INSTALL_HOOK_DYNAMIC (Send2, reinterpret_cast<LPVOID> (amHandle + 0x1F5F0));
|
||||
INSTALL_HOOK_DYNAMIC (Send3, reinterpret_cast<LPVOID> (amHandle + 0x1F660));
|
||||
INSTALL_HOOK_DYNAMIC (Send4, reinterpret_cast<LPVOID> (amHandle + 0x1F690));
|
||||
INSTALL_HOOK_DYNAMIC (CopyData, reinterpret_cast<LPVOID> (amHandle + 0x1F630));
|
||||
break;
|
||||
}
|
||||
case GameVersion::CHN00: {
|
||||
INSTALL_HOOK_DYNAMIC (QrInit, reinterpret_cast<LPVOID> (amHandle + 0x161B0));
|
||||
INSTALL_HOOK_DYNAMIC (QrClose, reinterpret_cast<LPVOID> (amHandle + 0x16350));
|
||||
INSTALL_HOOK_DYNAMIC (QrRead, reinterpret_cast<LPVOID> (amHandle + 0x163A0));
|
||||
INSTALL_HOOK_DYNAMIC (CallQrUnknown, reinterpret_cast<LPVOID> (amHandle + 0x8F60));
|
||||
INSTALL_HOOK_DYNAMIC (Send1, reinterpret_cast<LPVOID> (amHandle + 0x16940));
|
||||
INSTALL_HOOK_DYNAMIC (Send2, reinterpret_cast<LPVOID> (amHandle + 0x16990));
|
||||
INSTALL_HOOK_DYNAMIC (Send3, reinterpret_cast<LPVOID> (amHandle + 0x16A00));
|
||||
INSTALL_HOOK_DYNAMIC (Send4, reinterpret_cast<LPVOID> (amHandle + 0x16A30));
|
||||
INSTALL_HOOK_DYNAMIC (CopyData, reinterpret_cast<LPVOID> (amHandle + 0x169D0));
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace patches::Qr
|
478
src/patches/scanner.cpp
Normal file
478
src/patches/scanner.cpp
Normal file
@ -0,0 +1,478 @@
|
||||
#include "constants.h"
|
||||
#include "helpers.h"
|
||||
#include "patches.h"
|
||||
#include <ReadBarcode.h>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#define STBI_WINDOWS_UTF8
|
||||
#include "stb_image.h"
|
||||
|
||||
extern GameVersion gameVersion;
|
||||
extern std::vector<HMODULE> 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<std::vector<uint8_t>> 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::milliseconds>(std::chrono::system_clock::now ().time_since_epoch ()).count ();
|
||||
if (state == State::CopyWait && scanQueue.size () > 0) {
|
||||
std::vector<uint8_t> 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<int> (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<uint8_t> &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<uint8_t> 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<uint8_t> 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::milliseconds>(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<uint8_t> data = {};
|
||||
for (size_t i = 0; i < size; i ++) data.push_back (space[i]);
|
||||
patches::Scanner::Qr::Commit (data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> &
|
||||
ReadQRData (std::vector<uint8_t> &buffer) {
|
||||
std::string serial = "";
|
||||
u16 type = 0;
|
||||
std::vector<i64> songNoes;
|
||||
|
||||
buffer.clear ();
|
||||
auto configPath = std::filesystem::current_path () / "config.toml";
|
||||
std::unique_ptr<toml_table_t, void (*) (toml_table_t *)> 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<uint8_t> 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<uint8_t> 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<uint8_t> &
|
||||
ReadQRImage (std::vector<uint8_t> &buffer) {
|
||||
std::string imagePath = "";
|
||||
|
||||
buffer.clear ();
|
||||
auto configPath = std::filesystem::current_path () / "config.toml";
|
||||
std::unique_ptr<toml_table_t, void (*) (toml_table_t *)> 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<stbi_uc, void (*) (void *)> 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<u64> (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 ();
|
||||
}
|
||||
}
|
@ -17,6 +17,17 @@ public:
|
||||
this->registerInit = initMethod;
|
||||
}
|
||||
};
|
||||
class RegisteredSingleItem {
|
||||
public:
|
||||
std::wstring query;
|
||||
std::wstring selectItem;
|
||||
std::function<void()> registerInit;
|
||||
RegisteredSingleItem (const std::wstring query, const std::wstring selectItem, const std::function<void()> &initMethod) {
|
||||
this->query = query;
|
||||
this->selectItem = selectItem;
|
||||
this->registerInit = initMethod;
|
||||
}
|
||||
};
|
||||
class RegisteredModify {
|
||||
public:
|
||||
std::wstring query;
|
||||
@ -29,10 +40,11 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<RegisteredItem *> registeredItems = {};
|
||||
std::vector<RegisteredModify *> registeredModifies = {};
|
||||
std::wstring moddedInitial = L"";
|
||||
std::wstring modded = L"";
|
||||
std::vector<RegisteredItem *> registeredItems = {};
|
||||
std::vector<RegisteredSingleItem *> registeredSingleItems = {};
|
||||
std::vector<RegisteredModify *> 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"<root>" + item->selectItem + L"</root>";
|
||||
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<void ()> &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<void()> &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<void (pugi::xml_node &)> &nodeModify, const std::function<void ()> &initMethod) {
|
||||
LogMessage (LogLevel::DEBUG, L"Register Modify " + query);
|
||||
LogMessage (LogLevel::DEBUG, L"Register \n Modify: {}", query);
|
||||
registeredModifies.push_back (new RegisteredModify (query, nodeModify, initMethod));
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
if (patches::TestMode::ReadTestModeValue (L"ModFixLanguage") == 1) {
|
||||
lua_settop (a1, 0);
|
||||
lua_pushstring (a1, languageStr (language));
|
||||
return 1;
|
||||
} else return originalGetRegionLanguage (a1);
|
||||
}
|
||||
HOOK (i64, GetCabinetLanguage, ASLR (0x1401D1A60), i64, i64 a2) {
|
||||
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<float> (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"<select-item label=\"FIX LANGUAGE\" param-offset-x=\"35\" replace-text=\"0:OFF, 1:ON\" group=\"Setting\" id=\"ModFixLanguage\" max=\"1\" min=\"0\" default=\"1\"/>",
|
||||
[&]() { INSTALL_HOOK (GetLanguage); INSTALL_HOOK (GetRegionLanguage); INSTALL_HOOK (GetCabinetLanguage); }
|
||||
);
|
||||
// Unlock Songs
|
||||
TestMode::RegisterItem(
|
||||
L"<select-item label=\"UNLOCK SONGS\" param-offset-x=\"35\" replace-text=\"0:OFF, 1:ON, 2:FORCE\" group=\"Setting\" id=\"ModUnlockSongs\" max=\"2\" min=\"0\" default=\"1\"/>",
|
||||
[&]() { INSTALL_HOOK (IsSongRelease); INSTALL_HOOK (IsSongReleasePlayer); INSTALL_MID_HOOK (DifficultyPanelCrown); }
|
||||
);
|
||||
// Freeze Timer
|
||||
TestMode::RegisterItem (L"<select-item label=\"FREEZE TIMER\" param-offset-x=\"35\" replace-text=\"0:OFF, 1:ON\" "
|
||||
L"group=\"Setting\" id=\"ModFreezeTimer\" max=\"1\" min=\"0\" default=\"0\"/>",
|
||||
[&] { INSTALL_MID_HOOK (FreezeTimer); });
|
||||
TestMode::RegisterItem (
|
||||
L"<select-item label=\"FREEZE TIMER\" param-offset-x=\"35\" replace-text=\"0:OFF, 1:ON\" "
|
||||
L"group=\"Setting\" id=\"ModFreezeTimer\" max=\"1\" min=\"0\" default=\"0\"/>",
|
||||
[&] { INSTALL_MID_HOOK (FreezeTimer); }
|
||||
);
|
||||
// Mode Unlock
|
||||
TestMode::RegisterItem (L"<select-item label=\"KIMETSU MODE\" param-offset-x=\"35\" replace-text=\"0:DEFAULT, 1:ENABLE, "
|
||||
L"2:CARD ONLY\" group=\"Setting\" id=\"ModModeCollabo024\" max=\"1\" min=\"0\" default=\"0\"/>",
|
||||
[&] { INSTALL_HOOK (AvailableMode_Collabo024); });
|
||||
TestMode::RegisterItem (L"<select-item label=\"ONE PIECE MODE\" param-offset-x=\"35\" replace-text=\"0:DEFAULT, "
|
||||
L"1:ENABLE, 2:CARD ONLY\" group=\"Setting\" id=\"ModModeCollabo025\" max=\"1\" min=\"0\" default=\"0\"/>",
|
||||
[&] { INSTALL_HOOK (AvailableMode_Collabo025); });
|
||||
TestMode::RegisterItem (L"<select-item label=\"AI SOSHINA MODE\" param-offset-x=\"35\" replace-text=\"0:DEFAULT, "
|
||||
L"1:ENABLE, 2:CARD ONLY\" group=\"Setting\" id=\"ModModeCollabo026\" max=\"1\" min=\"0\" default=\"0\"/>",
|
||||
[&] { INSTALL_HOOK (AvailableMode_Collabo026); });
|
||||
TestMode::RegisterItem (L"<select-item label=\"AOHARU MODE\" param-offset-x=\"35\" replace-text=\"0:DEFAULT, 1:ENABLE, "
|
||||
L"2:CARD ONLY\" group=\"Setting\" id=\"ModModeAprilFool001\" max=\"1\" min=\"0\" default=\"0\"/>",
|
||||
[&] { INSTALL_HOOK (AvailableMode_AprilFool001); });
|
||||
TestMode::RegisterItem (L"<select-item label=\"INSTANT RESULT\" param-offset-x=\"35\" replace-text=\"0:OFF, 1:ON\" "
|
||||
L"group=\"Setting\" id=\"ModInstantResult\" max=\"1\" min=\"0\" default=\"0\"/>",
|
||||
[&] {
|
||||
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"<select-item label=\"KIMETSU MODE\" param-offset-x=\"35\" replace-text=\"0:DEFAULT, 1:ENABLE, "
|
||||
L"2:CARD ONLY\" group=\"Setting\" id=\"ModModeCollabo024\" max=\"1\" min=\"0\" default=\"0\"/>",
|
||||
[&] { INSTALL_HOOK (AvailableMode_Collabo024); }
|
||||
);
|
||||
TestMode::RegisterItem (
|
||||
L"<select-item label=\"ONE PIECE MODE\" param-offset-x=\"35\" replace-text=\"0:DEFAULT, "
|
||||
L"1:ENABLE, 2:CARD ONLY\" group=\"Setting\" id=\"ModModeCollabo025\" max=\"1\" min=\"0\" default=\"0\"/>",
|
||||
[&] { INSTALL_HOOK (AvailableMode_Collabo025); }
|
||||
);
|
||||
TestMode::RegisterItem (
|
||||
L"<select-item label=\"AI SOSHINA MODE\" param-offset-x=\"35\" replace-text=\"0:DEFAULT, "
|
||||
L"1:ENABLE, 2:CARD ONLY\" group=\"Setting\" id=\"ModModeCollabo026\" max=\"1\" min=\"0\" default=\"0\"/>",
|
||||
[&] { INSTALL_HOOK (AvailableMode_Collabo026); }
|
||||
);
|
||||
TestMode::RegisterItem (
|
||||
L"<select-item label=\"AOHARU MODE\" param-offset-x=\"35\" replace-text=\"0:DEFAULT, 1:ENABLE, "
|
||||
L"2:CARD ONLY\" group=\"Setting\" id=\"ModModeAprilFool001\" max=\"1\" min=\"0\" default=\"0\"/>",
|
||||
[&] { INSTALL_HOOK (AvailableMode_AprilFool001); }
|
||||
);
|
||||
TestMode::RegisterItem (
|
||||
L"<select-item label=\"INSTANT RESULT\" param-offset-x=\"35\" replace-text=\"0:OFF, 1:ON\" "
|
||||
L"group=\"Setting\" id=\"ModInstantResult\" max=\"1\" min=\"0\" default=\"0\"/>",
|
||||
[&] {
|
||||
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"<select-item label=\"ATTRACT DEMO\" disable=\"True/ModFixLanguage:0\" param-offset-x=\"35\" replace-text=\"0:ON, 1:OFF\" group=\"Setting\" id=\"AttractDemoItem\" max=\"1\" min=\"0\" default=\"0\"/>",
|
||||
[&](){ INSTALL_MID_HOOK (AttractDemo); }
|
||||
);
|
||||
|
||||
// Instant Result
|
||||
// TestMode::RegisterModify(
|
||||
// L"/root/menu[@id='GameOptionsMenu']/layout[@type='Center']/select-item[@id='NumberOfStageItem']",
|
||||
|
Loading…
x
Reference in New Issue
Block a user