#include "constants.h" #include "helpers.h" #include "poll.h" #include #include #include #include #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; typedef void event (); typedef bool CheckQrEvent(); typedef int GetQrEvent(char *buf_); namespace patches::Qr { enum class State { Ready, CopyWait }; enum class Mode { Card, Data, Image, Plugin }; State gState = State::Ready; Mode gMode = Mode::Card; HMODULE gPlugin; std::string accessCode; bool qrEnabled = true; std::vector qrPlugins; bool qrPluginRegistered = false; HOOK_DYNAMIC (char, __fastcall, qrInit, i64) { return 1; } HOOK_DYNAMIC (char, __fastcall, qrRead, i64 a1) { *(DWORD *)(a1 + 40) = 1; *(DWORD *)(a1 + 16) = 1; *(BYTE *)(a1 + 112) = 0; return 1; } HOOK_DYNAMIC (char, __fastcall, qrClose, i64) { return 1; } HOOK_DYNAMIC (i64, __fastcall, callQrUnknown, i64) { return 1; } HOOK_DYNAMIC (bool, __fastcall, Send1, i64, const void *, i64) { return true; } HOOK_DYNAMIC (bool, __fastcall, Send2, i64, char) { return true; } HOOK_DYNAMIC (bool, __fastcall, Send3, i64 a1) { *(WORD *)(a1 + 88) = 0; *(BYTE *)(a1 + 90) = 0; return true; } HOOK_DYNAMIC (bool, __fastcall, Send4, i64 a1) { *(BYTE *)(a1 + 88) = 1; *(i64 *)(a1 + 32) = *(i64 *)(a1 + 24); *(WORD *)(a1 + 89) = 0; return true; } HOOK_DYNAMIC (i64, __fastcall, copy_data, 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; } else if (gMode == Mode::Data) { std::string serial = ""; u16 type = 0; std::vector songNoes; if (config_ptr) { auto qr = openConfigSection (config_ptr.get (), "qr"); if (qr) { auto data = openConfigSection (qr, "data"); if (data) { serial = readConfigString (data, "serial", ""); type = readConfigInt (data, "type", 0); songNoes = readConfigIntArray (data, "song_no", songNoes); } } } BYTE serial_length = (BYTE)serial.size (); std::vector byteBuffer = {0x53, 0x31, 0x32, 0x00, 0x00, 0xFF, 0xFF, serial_length, 0x01, 0x00}; for (char c : serial) byteBuffer.push_back ((BYTE)c); if (type == 5) { std::vector folderData = {0xFF, 0xFF}; folderData.push_back (songNoes.size () * 2); folderData.push_back ((u8)(type & 0xFF)); folderData.push_back ((u8)((type >> 8) & 0xFF)); for (u16 songNo : songNoes) { folderData.push_back ((u8)(songNo & 0xFF)); folderData.push_back ((u8)((songNo >> 8) & 0xFF)); } for (auto c : folderData) byteBuffer.push_back (c); } byteBuffer.push_back (0xEE); byteBuffer.push_back (0xFF); memcpy (dest, byteBuffer.data (), byteBuffer.size ()); gState = State::Ready; return byteBuffer.size (); } else if (gMode == Mode::Image) { std::string imagePath = ""; 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)) { std::cerr << "Failed to open image: " << u8Path.string () << " (file not found)" << "\n"; 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) { std::cerr << "Failed to read image: " << u8Path << " (" << stbi_failure_reason () << ")" << "\n"; gState = State::Ready; return 0; } ZXing::ImageView image{buffer.get (), width, height, ZXing::ImageFormat::RGB}; auto result = ReadBarcode (image); if (!result.isValid ()) { std::cerr << "Failed to read qr: " << imagePath << " (" << ToString (result.error ()) << ")" << "\n"; gState = State::Ready; return 0; } std::cout << "Valid" << std::endl; auto byteData = result.bytes (); std::cout << "Image QR: " << ZXing::ToHex (byteData) << std::endl; auto dataSize = byteData.size (); memcpy (dest, byteData.data (), dataSize); std::cout << "Data consumed! len = " << dataSize << std::endl; gState = State::Ready; return dataSize; } else if (gMode == Mode::Plugin) { FARPROC getQrEvent = GetProcAddress (gPlugin, "getQr"); if (getQrEvent) { unsigned char plugin_data[10086] ; int buf_len = ((GetQrEvent*) getQrEvent) (plugin_data); memcpy (dest, plugin_data, buf_len); gState = State::Ready; gMode = Mode::Card; return buf_len; } else { gState = State::Ready; gMode = Mode::Card; return 0; } } } else if (qrPluginRegistered) { for (auto plugin : qrPlugins) { FARPROC usingQrEvent = GetProcAddress (plugin, "usingQr"); if (usingQrEvent) ((event*) usingQrEvent) (); } } return 0; } void Update () { if (!qrEnabled) return; if (gState == State::Ready) { if (IsButtonTapped (CARD_INSERT_1)) { if (gameVersion != GameVersion::CN_JUN_2023) return; std::cout << "Insert" << std::endl; accessCode = "BNTTCNID"; accessCode += accessCode1; gState = State::CopyWait; gMode = Mode::Card; } else if (IsButtonTapped (CARD_INSERT_2)) { if (gameVersion != GameVersion::CN_JUN_2023) return; std::cout << "Insert" << std::endl; accessCode = "BNTTCNID"; accessCode += accessCode2; gState = State::CopyWait; gMode = Mode::Card; } else if (IsButtonTapped (QR_DATA_READ)) { std::cout << "Insert" << std::endl; gState = State::CopyWait; gMode = Mode::Data; } else if (IsButtonTapped (QR_IMAGE_READ)) { std::cout << "Insert" << std::endl; gState = State::CopyWait; gMode = Mode::Image; } else if (qrPluginRegistered) { for (auto plugin : qrPlugins) { FARPROC checkQrEvent = GetProcAddress (plugin, "checkQr"); if (checkQrEvent && ((CheckQrEvent*) checkQrEvent) ()) { std::cout << "Insert" << std::endl; gState = State::CopyWait; gMode = Mode::Plugin; gPlugin = plugin; break; } } } } } void Init () { auto configPath = std::filesystem::current_path () / "config.toml"; std::unique_ptr config_ptr (openConfig (configPath), toml_free); if (!config_ptr) { std::cerr << "[Init] Config file not found" << std::endl; return; } auto qr = openConfigSection (config_ptr.get (), "qr"); if (!qr) { std::cerr << "[Init] QR config section not found! QR emulation disabled" << std::endl; qrEnabled = false; return; } qrEnabled = readConfigBool (qr, "enabled", true); if (!qrEnabled) { std::cout << "[Init] QR emulation disabled" << std::endl; return; } for (auto plugin : plugins) { FARPROC usingQrEvent = GetProcAddress (plugin, "usingQr"); if (usingQrEvent) qrPlugins.push_back(plugin); } if (qrPlugins.size() > 0) qrPluginRegistered = true; SetConsoleOutputCP (CP_UTF8); auto amHandle = (u64)GetModuleHandle ("AMFrameWork.dll"); switch (gameVersion) { case GameVersion::JP_NOV_2020: { INSTALL_HOOK_DYNAMIC (qrInit, (LPVOID)(amHandle + 0x1BA00)); INSTALL_HOOK_DYNAMIC (qrRead, (LPVOID)(amHandle + 0x1BC20)); INSTALL_HOOK_DYNAMIC (qrClose, (LPVOID)(amHandle + 0x1BBD0)); INSTALL_HOOK_DYNAMIC (callQrUnknown, (LPVOID)(amHandle + 0xFD40)); // 08.18 has no Send1 INSTALL_HOOK_DYNAMIC (Send2, (LPVOID)(amHandle + 0x1C2D0)); INSTALL_HOOK_DYNAMIC (Send3, (LPVOID)(amHandle + 0x1C260)); INSTALL_HOOK_DYNAMIC (Send4, (LPVOID)(amHandle + 0x1C220)); INSTALL_HOOK_DYNAMIC (copy_data, (LPVOID)(amHandle + 0x1C2A0)); break; } case GameVersion::JP_APR_2023: { INSTALL_HOOK_DYNAMIC (qrInit, (LPVOID)(amHandle + 0x1EDC0)); INSTALL_HOOK_DYNAMIC (qrRead, (LPVOID)(amHandle + 0x1EFB0)); INSTALL_HOOK_DYNAMIC (qrClose, (LPVOID)(amHandle + 0x1EF60)); INSTALL_HOOK_DYNAMIC (callQrUnknown, (LPVOID)(amHandle + 0x11A70)); INSTALL_HOOK_DYNAMIC (Send1, (LPVOID)(amHandle + 0x1F690)); INSTALL_HOOK_DYNAMIC (Send2, (LPVOID)(amHandle + 0x1F660)); INSTALL_HOOK_DYNAMIC (Send3, (LPVOID)(amHandle + 0x1F5F0)); INSTALL_HOOK_DYNAMIC (Send4, (LPVOID)(amHandle + 0x1F5B0)); INSTALL_HOOK_DYNAMIC (copy_data, (LPVOID)(amHandle + 0x1F630)); break; } case GameVersion::CN_JUN_2023: { INSTALL_HOOK_DYNAMIC (qrInit, (LPVOID)(amHandle + 0x161B0)); INSTALL_HOOK_DYNAMIC (qrRead, (LPVOID)(amHandle + 0x163A0)); INSTALL_HOOK_DYNAMIC (qrClose, (LPVOID)(amHandle + 0x16350)); INSTALL_HOOK_DYNAMIC (callQrUnknown, (LPVOID)(amHandle + 0x8F60)); INSTALL_HOOK_DYNAMIC (Send1, (LPVOID)(amHandle + 0x16A30)); INSTALL_HOOK_DYNAMIC (Send2, (LPVOID)(amHandle + 0x16A00)); INSTALL_HOOK_DYNAMIC (Send3, (LPVOID)(amHandle + 0x16990)); INSTALL_HOOK_DYNAMIC (Send4, (LPVOID)(amHandle + 0x16940)); INSTALL_HOOK_DYNAMIC (copy_data, (LPVOID)(amHandle + 0x169D0)); break; } default: { break; } } } } // namespace patches::Qr