Files
esuo1198_TaikoArcadeLoader/src/patches/qr.cpp
T

316 lines
12 KiB
C++

#include "constants.h"
#include "helpers.h"
#include "poll.h"
#include <ReadBarcode.h>
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <iostream>
#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 };
State gState = State::Ready;
Mode gMode = Mode::Card;
HMODULE gPlugin;
std::string accessCode;
std::vector<HMODULE> qrPlugins;
bool qrPluginRegistered = false;
HOOK_DYNAMIC (char, __fastcall, qrInit, i64) { return 1; }
HOOK_DYNAMIC (char, __fastcall, qrClose, 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 (i64, __fastcall, callQrUnknown, i64) { return 1; }
HOOK_DYNAMIC (bool, __fastcall, Send1, i64 a1) {
*(BYTE *)(a1 + 88) = 1;
*(i64 *)(a1 + 32) = *(i64 *)(a1 + 24);
*(WORD *)(a1 + 89) = 0;
return true;
}
HOOK_DYNAMIC (bool, __fastcall, Send2, i64 a1) {
*(WORD *)(a1 + 88) = 0;
*(BYTE *)(a1 + 90) = 0;
return true;
}
HOOK_DYNAMIC (bool, __fastcall, Send3, i64, char) { return true; }
HOOK_DYNAMIC (bool, __fastcall, Send4, i64, const void *, i64) { 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<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;
} else if (gMode == Mode::Data) {
std::string serial = "";
u16 type = 0;
std::vector<i64> 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<BYTE> 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<BYTE> 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);
for (auto byteData : byteBuffer)
std::cout << std::hex << std::uppercase << std::setfill ('0') << std::setw (2) << static_cast<int> (byteData) << " ";
std::cout << std::endl;
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<stbi_uc, void (*) (void *)> 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 << ZXing::ToHex (byteData) << std::endl;
auto dataSize = byteData.size ();
memcpy (dest, byteData.data (), dataSize);
gState = State::Ready;
return dataSize;
} else if (gMode == Mode::Plugin) {
FARPROC getEvent = GetProcAddress (gPlugin, "GetQr");
if (getEvent) {
unsigned char plugin_data[length];
int buf_len = ((getQrEvent *)getEvent) (length, plugin_data);
if (0 < buf_len && buf_len <= length) {
for (int i = 0; i < buf_len; i++)
std::cout << std::hex << std::uppercase << std::setfill ('0') << std::setw (2) << static_cast<int> (plugin_data[i]) << " ";
std::cout << std::endl;
memcpy (dest, plugin_data, buf_len);
} else {
std::cerr << "QR discard! Length invalid: " << buf_len << ", valid range: 0~" << length << std::endl;
}
gState = State::Ready;
return buf_len;
} else {
gState = State::Ready;
return 0;
}
}
} else if (qrPluginRegistered) {
for (auto plugin : qrPlugins) {
FARPROC usingQrEvent = GetProcAddress (plugin, "UsingQr");
if (usingQrEvent) ((event *)usingQrEvent) ();
}
}
return 0;
}
void
Update () {
if (!emulateQR) return;
if (gState == State::Ready) {
if (IsButtonTapped (CARD_INSERT_1)) {
if (gameVersion != GameVersion::CHN00) 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::CHN00) 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 checkEvent = GetProcAddress (plugin, "CheckQr");
if (checkEvent && ((checkQrEvent *)checkEvent) ()) {
std::cout << "Insert" << std::endl;
gState = State::CopyWait;
gMode = Mode::Plugin;
gPlugin = plugin;
break;
}
}
}
}
}
void
Init () {
if (!emulateQR) {
std::cout << "[Init] QR emulation disabled" << std::endl;
return;
}
for (auto plugin : plugins) {
FARPROC initEvent = GetProcAddress (plugin, "InitQr");
if (initEvent) ((initQrEvent *)initEvent) (gameVersion);
FARPROC usingQrEvent = GetProcAddress (plugin, "UsingQr");
if (usingQrEvent) qrPlugins.push_back (plugin);
}
if (qrPlugins.size () > 0) {
std::cout << "QR plugin found!" << std::endl;
qrPluginRegistered = true;
}
SetConsoleOutputCP (CP_UTF8);
auto amHandle = (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 (copy_data, (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 (copy_data, (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 (copy_data, (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 (copy_data, (LPVOID)(amHandle + 0x169D0));
break;
}
default: {
break;
}
}
}
} // namespace patches::Qr