Initial re-upload of spice2x-24-08-24

This commit is contained in:
2024-08-28 11:10:34 -04:00
commit caa9e02285
1181 changed files with 380065 additions and 0 deletions

294
misc/bt5api.cpp Normal file
View File

@@ -0,0 +1,294 @@
#include "bt5api.h"
#include <string>
#include <thread>
#include <windows.h>
#include <external/robin_hood.h>
#include "games/iidx/iidx.h"
#include "hooks/libraryhook.h"
#include "util/logging.h"
#include "util/libutils.h"
#include "avs/game.h"
#include "eamuse.h"
#ifdef __GNUC__
/* Bemanitools is compiled with GCC (MinGW, specifically) as of version 5 */
#define LOG_CHECK_FMT __attribute__(( format(printf, 2, 3) ))
#else
/* Compile it out for MSVC plebs */
#define LOG_CHECK_FMT
#endif
/* An AVS-style logger function. Comes in four flavors: misc, info, warning,
and fatal, with increasing severity. Fatal loggers do not return, they
abort the running process after writing their message to the log.
"module" is an arbitrary short string identifying the source of the log
message. The name of the calling DLL is a good default choice for this
string, although you might want to identify a module within your DLL here
instead.
"fmt" is a printf-style format string. Depending on the context in which
your DLL is running you might end up calling a logger function exported
from libavs, which has its own printf implementation (including a number of
proprietary extensions), so don't use any overly exotic formats. */
typedef void (*log_formatter_t)(const char *module, const char *fmt, ...)
LOG_CHECK_FMT;
/* An API for spawning threads. This API is defined by libavs, although
Bemanitools itself may supply compatible implementations of these functions
to your DLL, depending on the context in which it runs.
NOTE: You may only use the logging functions from a thread where Bemanitools
calls you, or a thread that you create using this API. Failure to observe
this restriction will cause the process to crash. This is a limitation of
libavs itself, not Bemanitools. */
typedef int (*thread_create_t)(int (*proc)(void *), void *ctx,
uint32_t stack_sz, unsigned int priority);
typedef void (*thread_join_t)(int thread_id, int *result);
typedef void (*thread_destroy_t)(int thread_id);
/* The first function that will be called on your DLL. You will be supplied
with four function pointers that may be used to log messages to the game's
log file. See comments in glue.h for further information. */
typedef void (__cdecl *eam_io_set_loggers_t)(log_formatter_t misc, log_formatter_t info,
log_formatter_t warning, log_formatter_t fatal);
static eam_io_set_loggers_t eam_io_set_loggers = nullptr;
/* Initialize your card reader emulation DLL. Thread management functions are
provided to you; you must use these functions to create your own threads if
you want to make use of the logging functions that are provided to
eam_io_set_loggers(). You will also need to pass these thread management
functions on to geninput if you intend to make use of that library.
See glue.h and geninput.h for further details. */
typedef bool (__cdecl *eam_io_init_t)(thread_create_t thread_create, thread_join_t thread_join,
thread_destroy_t thread_destroy);
static eam_io_init_t eam_io_init = nullptr;
/* Shut down your card reader emulation DLL. */
typedef void (__cdecl *eam_io_fini_t)(void);
static eam_io_fini_t eam_io_fini = nullptr;
/* Return the state of the number pad on your reader. This function will be
called frequently. See enum eam_io_keypad_scan_code above for the meaning of
each bit within the return value.
This function will be called even if the running game does not actually have
a number pad on the real cabinet (e.g. Jubeat).
unit_no is either 0 or 1. Games with only a single reader (jubeat, popn,
drummania) will only use unit_no 0. */
typedef uint16_t (__cdecl *eam_io_get_keypad_state_t)(uint8_t unit_no);
static eam_io_get_keypad_state_t eam_io_get_keypad_state = nullptr;
/* Indicate which sensors (front and back) are triggered for a slotted reader
(refer to enum). To emulate non-slotted readers, just set both sensors
to on to indicate the card is in range of the reader. This function
will be called frequently. */
typedef uint8_t (__cdecl *eam_io_get_sensor_state_t)(uint8_t unit_no);
static eam_io_get_sensor_state_t eam_io_get_sensor_state = nullptr;
/* Read a card ID. This function is only called when the return value of
eam_io_get_sensor_state() changes from false to true, so you may take your
time and perform file I/O etc, within reason. You must return exactly eight
bytes into the buffer pointed to by card_id. */
typedef bool (__cdecl *eam_io_read_card_t)(uint8_t unit_no, uint8_t *card_id, uint8_t nbytes);
static eam_io_read_card_t eam_io_read_card = nullptr;
/* Send a command to the card slot. This is called by the game to execute
certain actions on a slotted reader (refer to enum). When emulating
wave pass readers, this is function is never called. */
typedef bool (__cdecl *eam_io_card_slot_cmd_t)(uint8_t unit_no, uint8_t cmd);
static eam_io_card_slot_cmd_t eam_io_card_slot_cmd = nullptr;
/* This function is called frequently. Update your device and states in here */
typedef bool (__cdecl *eam_io_poll_t)(uint8_t unit_no);
static eam_io_poll_t eam_io_poll = nullptr;
/* Return a pointer to an internal configuration API for use by config.exe.
Custom implementations should return NULL. */
typedef const struct eam_io_config_api* (__cdecl *eam_io_get_config_api_t)(void);
static eam_io_get_config_api_t eam_io_get_config_api = nullptr;
bool BT5API_ENABLED = false;
static HMODULE EAMIO_DLL;
static std::string EAMIO_DLL_NAME = "eamio.dll";
static robin_hood::unordered_map<int, std::thread *> BT5API_THREADS;
static robin_hood::unordered_map<int, int> BT5API_THREAD_RESULTS;
static uint8_t BT5API_CARD_STATES[] = {0, 0};
static void bt5api_log(const char *bt5_module, const char *fmt, ...) {
// pre-compute module
std::string module = fmt::format("bt5api:{}", bt5_module != nullptr ? bt5_module : "(null)");
// string format
char buf[1024];
va_list args;
va_start(args, fmt);
const auto result = std::vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
// check if format failed, fallback to logging the format string
if (result < 0) {
log_info(module.c_str(), "{}", fmt);
return;
}
// log it if the buffer was enough
const size_t len = (size_t) result;
if (len < sizeof(buf)) {
log_info(module.c_str(), "{}", buf);
} else {
// allocate a new string and format again
std::string new_buf(len, '\0');
va_start(args, fmt);
std::vsnprintf(new_buf.data(), len + 1, fmt, args);
va_end(args);
// log the result
log_info(module.c_str(), "{}", new_buf);
}
}
static int bt5api_thread_create(int (*proc)(void *), void *ctx, uint32_t stack_sz, unsigned int priority) {
std::thread *thread = new std::thread([proc, ctx]() {
int thread_id = (int) std::hash<std::thread::id>{}(std::this_thread::get_id());
BT5API_THREAD_RESULTS[thread_id] = proc(ctx);
});
int thread_id = static_cast<int>(std::hash<std::thread::id>{}(thread->get_id()));
BT5API_THREADS[thread_id] = thread;
return thread_id;
}
static void bt5api_thread_join(int thread_id, int *result) {
BT5API_THREADS[thread_id]->join();
if (result != nullptr) {
*result = BT5API_THREAD_RESULTS[thread_id];
}
}
static void bt5api_thread_destroy(int thread_id) {
auto thread_handle = BT5API_THREADS.find(thread_id);
if (thread_handle != BT5API_THREADS.end()) {
if (thread_handle->second->joinable()) {
thread_handle->second->detach();
}
delete thread_handle->second;
BT5API_THREADS.erase(thread_handle);
}
BT5API_THREAD_RESULTS.erase(thread_id);
}
void bt5api_init() {
// check if already initialized
if (EAMIO_DLL) {
return;
}
log_info("bt5api", "initializing");
// load DLL instances
EAMIO_DLL = libutils::try_library(EAMIO_DLL_NAME);
if (!EAMIO_DLL) {
EAMIO_DLL = libutils::try_library("..\\" + EAMIO_DLL_NAME);
}
if (!EAMIO_DLL) {
log_warning("bt5api", "unable to load '{}': 0x{:x}", EAMIO_DLL_NAME, GetLastError());
return;
}
// load eamio funcs
eam_io_set_loggers = libutils::try_proc<eam_io_set_loggers_t>(EAMIO_DLL, "eam_io_set_loggers");
eam_io_init = libutils::try_proc<eam_io_init_t>(EAMIO_DLL, "eam_io_init");
eam_io_fini = libutils::try_proc<eam_io_fini_t>(EAMIO_DLL, "eam_io_fini");
eam_io_get_keypad_state = libutils::try_proc<eam_io_get_keypad_state_t>(EAMIO_DLL, "eam_io_get_keypad_state");
eam_io_get_sensor_state = libutils::try_proc<eam_io_get_sensor_state_t>(EAMIO_DLL, "eam_io_get_sensor_state");
eam_io_read_card = libutils::try_proc<eam_io_read_card_t>(EAMIO_DLL, "eam_io_read_card");
eam_io_card_slot_cmd = libutils::try_proc<eam_io_card_slot_cmd_t>(EAMIO_DLL, "eam_io_card_slot_cmd");
eam_io_poll = libutils::try_proc<eam_io_poll_t>(EAMIO_DLL, "eam_io_poll");
eam_io_get_config_api = libutils::try_proc<eam_io_get_config_api_t>(EAMIO_DLL, "eam_io_get_config_api");
// initialize
eam_io_set_loggers(&bt5api_log, &bt5api_log, &bt5api_log, &bt5api_log);
eam_io_init(&bt5api_thread_create, &bt5api_thread_join, &bt5api_thread_destroy);
// bt5api workaround for games with 2 card readers (NFCeAmuse cares about this)
if (eamuse_get_game_keypads() > 1) {
bt5api_poll_reader_card(1);
bt5api_poll_reader_card(0);
}
// done
log_info("bt5api", "done initializing");
}
void bt5api_hook(HINSTANCE module) {
libraryhook_enable(module);
// toastertools
libraryhook_hook_proc("iidx_io_ext_get_16seg", games::iidx::get_16seg);
}
void bt5api_poll_reader_card(uint8_t unit_no) {
// check if initialized
if (!EAMIO_DLL) {
return;
}
// poll
if (!eam_io_poll(unit_no)) {
log_warning("bt5api", "polling bt5api reader {} returned failure",
static_cast<int>(unit_no));
}
// get sensor state
uint8_t sensor_state = eam_io_get_sensor_state(unit_no);
// check for card in
if (sensor_state > BT5API_CARD_STATES[unit_no]) {
uint8_t card_id[8];
eam_io_read_card(unit_no, card_id, 8);
eamuse_card_insert(unit_no, card_id);
}
// save state
BT5API_CARD_STATES[unit_no] = sensor_state;
}
void bt5api_poll_reader_keypad(uint8_t unit_no) {
// check if initialized
if (!EAMIO_DLL) {
return;
}
// poll
if (!eam_io_poll(unit_no)) {
log_warning("bt5api", "polling bt5api reader {} returned failure",
static_cast<int>(unit_no));
}
// get keypad
eamuse_set_keypad_overrides_bt5(unit_no, eam_io_get_keypad_state(unit_no));
}
void bt5api_dispose() {
// check if initialized
if (!EAMIO_DLL) {
return;
}
// finish
eam_io_fini();
}

36
misc/bt5api.h Normal file
View File

@@ -0,0 +1,36 @@
#pragma once
#include <cstdint>
#include <windows.h>
/* Emulating the sensors of a slotted card reader. The reader has one
sensor at the front that detects if a card is getting inserted or
if the card is not fully removed. When the back sensor is triggered
the card is locked in the slot and its data is read. */
enum eam_io_sensor_state {
EAM_IO_SENSOR_FRONT = 0,
EAM_IO_SENSOR_BACK = 1,
};
/* Different commands for the (slotted) reader. The game triggers one
of these actions and the card slot as to execute it. When non-slotted
readers are emulated, these states are not used/set. */
enum eam_io_card_slot_cmd {
EAM_IO_CARD_SLOT_CMD_CLOSE = 0,
EAM_IO_CARD_SLOT_CMD_OPEN = 1,
EAM_IO_CARD_SLOT_CMD_EJECT = 2,
EAM_IO_CARD_SLOT_CMD_READ = 3,
};
/* A private function pointer table returned by the stock EAMIO.DLL
implementation and consumed by config.exe. The contents of this table are
undocumented and subject to change without notice. */
struct eam_io_config_api;
extern bool BT5API_ENABLED;
void bt5api_init();
void bt5api_hook(HINSTANCE module);
void bt5api_poll_reader_card(uint8_t unit_no);
void bt5api_poll_reader_keypad(uint8_t unit_no);
void bt5api_dispose();

185
misc/clipboard.cpp Normal file
View File

@@ -0,0 +1,185 @@
#include "clipboard.h"
// GDI+ Headers
// WARNING: Must stay in this order to compile
#include <windows.h>
#include <objidl.h>
#include <gdiplus.h>
#ifdef __GNUC__
#include <gdiplus/gdiplusflat.h>
#else
#include <gdiplusflat.h>
#endif
#include <thread>
#include "util/libutils.h"
#include "util/logging.h"
#include "util/utils.h"
namespace clipboard {
namespace imports {
static bool ATTEMPTED_LOAD_LIBRARY = false;
static decltype(Gdiplus::GdiplusShutdown) *GdiplusShutdown = nullptr;
static decltype(Gdiplus::GdiplusStartup) *GdiplusStartup = nullptr;
static decltype(Gdiplus::DllExports::GdipCreateBitmapFromFile) *GdipCreateBitmapFromFile = nullptr;
static decltype(Gdiplus::DllExports::GdipCreateHBITMAPFromBitmap) *GdipCreateHBITMAPFromBitmap = nullptr;
static decltype(Gdiplus::DllExports::GdipDisposeImage) *GdipDisposeImage = nullptr;
}
void copy_image_handler(const std::filesystem::path &path) {
if (!imports::ATTEMPTED_LOAD_LIBRARY) {
imports::ATTEMPTED_LOAD_LIBRARY = true;
auto gdiplus = libutils::try_library("gdiplus.dll");
if (gdiplus) {
imports::GdiplusShutdown = (decltype(imports::GdiplusShutdown)) libutils::try_proc(
gdiplus, "GdiplusShutdown");
imports::GdiplusStartup = (decltype(imports::GdiplusStartup)) libutils::try_proc(
gdiplus, "GdiplusStartup");
imports::GdipCreateBitmapFromFile = (decltype(imports::GdipCreateBitmapFromFile)) libutils::try_proc(
gdiplus, "GdipCreateBitmapFromFile");
imports::GdipCreateHBITMAPFromBitmap = (decltype(imports::GdipCreateHBITMAPFromBitmap)) libutils::try_proc(
gdiplus, "GdipCreateHBITMAPFromBitmap");
imports::GdipDisposeImage = (decltype(imports::GdipDisposeImage)) libutils::try_proc(
gdiplus, "GdipDisposeImage");
} else {
log_warning("clipboard", "GDI+ library not found, disabling clipboard functionality");
}
}
if (!imports::GdiplusShutdown ||
!imports::GdiplusStartup ||
!imports::GdipCreateBitmapFromFile ||
!imports::GdipCreateHBITMAPFromBitmap ||
!imports::GdipDisposeImage)
{
return;
}
// If the screenshot button is print screen, the OpenClipboard call seems to fail often if we only
// call it once, probably due to a race condition. So, we can try calling a lot until we can open it.
bool clipboard_open = false;
for (int i = 0; i < 1000000; i++) {
if (OpenClipboard(nullptr)) {
clipboard_open = true;
break;
}
}
if (!clipboard_open) {
log_warning("clipboard", "Failed to open clipboard");
return;
}
// Start gdiplus
Gdiplus::GdiplusStartupInput input {};
ULONG_PTR token;
imports::GdiplusStartup(&token, &input, nullptr);
// Convert the file path to a wstring and open the screenshot file
Gdiplus::GpBitmap *bitmap = nullptr;
auto status = imports::GdipCreateBitmapFromFile(path.c_str(), &bitmap);
if (status != Gdiplus::Ok) {
log_warning("clipboard", "failed to create GDI+ bitmap: {}", status);
imports::GdiplusShutdown(token);
CloseClipboard();
return;
}
// Retrieve the HBITMAP from the Bitmap object
HBITMAP hbitmap {};
status = imports::GdipCreateHBITMAPFromBitmap(bitmap, &hbitmap, 0);
if (status == Gdiplus::Ok) {
// Convert the HBITMAP to a DIB to copy to the clipboard
BITMAP bm;
GetObject(hbitmap, sizeof(bm), &bm);
BITMAPINFOHEADER info;
info.biSize = sizeof(info);
info.biWidth = bm.bmWidth;
info.biHeight = bm.bmHeight;
info.biPlanes = 1;
info.biBitCount = bm.bmBitsPixel;
info.biCompression = BI_RGB;
std::vector<BYTE> dimensions(bm.bmWidthBytes * bm.bmHeight);
auto hdc = GetDC(nullptr);
GetDIBits(hdc, hbitmap, 0, info.biHeight, dimensions.data(), reinterpret_cast<BITMAPINFO *>(&info), 0);
ReleaseDC(nullptr, hdc);
auto hmem = GlobalAlloc(GMEM_MOVEABLE, sizeof(info) + dimensions.size());
auto buffer = reinterpret_cast<BYTE *>(GlobalLock(hmem));
memcpy(buffer, &info, sizeof(info));
memcpy(&buffer[sizeof(info)], dimensions.data(), dimensions.size());
GlobalUnlock(hmem);
if (SetClipboardData(CF_DIB, hmem)) {
log_info("clipboard", "saved image to clipboard");
} else {
log_warning("clipboard", "failed to save image to clipboard");
}
} else {
log_warning("clipboard", "failed to retrieve HBITMAP from image bitmap: {}", status);
}
// Clean up after ourselves. hmem can't be deleted because it is owned by the clipboard now.
imports::GdipDisposeImage(bitmap);
imports::GdiplusShutdown(token);
CloseClipboard();
}
void copy_image(const std::filesystem::path path) {
// Create a new thread since we loop to open the clipboard
std::thread handle(copy_image_handler, std::move(path));
handle.detach();
}
void copy_text(const std::string str) {
if (!OpenClipboard(nullptr)) {
log_warning("clipboard", "Failed to open clipboard");
return;
}
HGLOBAL mem = GlobalAlloc(GMEM_MOVEABLE, str.length());
memcpy(GlobalLock(mem), str.c_str(), str.length());
GlobalUnlock(mem);
EmptyClipboard();
SetClipboardData(CF_TEXT, mem);
CloseClipboard();
}
const std::string paste_text() {
HGLOBAL hglb;
LPSTR str;
std::string text;
// check if clipboard content is text
if (!IsClipboardFormatAvailable(CF_TEXT)) {
return text;
}
if (!OpenClipboard(nullptr)) {
log_warning("clipboard", "Failed to open clipboard");
return text;
}
hglb = GetClipboardData(CF_TEXT);
if (hglb != NULL) {
str = reinterpret_cast<LPSTR>(GlobalLock(hglb));
if (str != NULL) {
text = str;
GlobalUnlock(hglb);
}
}
CloseClipboard();
return text;
}
}

9
misc/clipboard.h Normal file
View File

@@ -0,0 +1,9 @@
#pragma once
#include <filesystem>
namespace clipboard {
void copy_image(const std::filesystem::path path);
void copy_text(const std::string str);
const std::string paste_text();
}

740
misc/device.cpp Normal file
View File

@@ -0,0 +1,740 @@
#include "device.h"
#include <ctime>
#include "avs/game.h"
#include "cfg/api.h"
#include "games/gitadora/io.h"
#include "games/jb/jb.h"
#include "games/jb/io.h"
#include "games/rb/io.h"
#include "util/detour.h"
#include "util/libutils.h"
#include "util/logging.h"
#include "util/utils.h"
#include "rawinput/rawinput.h"
#include "eamuse.h"
using namespace GameAPI;
// settings
const char DEVICE_SYSTEM_VERSION[] = "4.2.0:0";
const char DEVICE_SUBBOARD_VERSION[] = "4.2.0:0";
// state
static HINSTANCE DEVICE_INSTANCE;
static std::string DEVICE_INSTANCE_NAME1 = "device.dll";
static std::string DEVICE_INSTANCE_NAME2 = "libdevice.dll";
static bool DEVICE_INITIALIZED = false;
static int DEVICE_INPUT_STATE;
static int __cdecl device_check_secplug(int a1) {
// check for invalid index
if (a1 > 1) {
return 0;
}
// J44/K44 has it flipped
if (avs::game::is_model({ "J44", "K44" })) {
return 0x101 - a1;
} else {
return 0x100 + a1;
}
}
static int __cdecl device_force_check_secplug(int a1, int a2) {
return 0;
}
static short __cdecl device_dispose_coinstock() {
eamuse_coin_consume_stock();
return 0;
}
static int __cdecl device_finalize(int a1, int a2) {
return 0;
}
static void __cdecl device_get_coinstock(unsigned short *coin1, unsigned short *coin2) {
*coin1 = (unsigned short) eamuse_coin_get_stock();
*coin2 = 0;
// without this, jubeat will spawn never ending credit inserts
eamuse_coin_consume_stock();
}
static void __cdecl device_get_coinstock_all(unsigned short *coin1, unsigned short *coin2) {
*coin1 = (unsigned short) eamuse_coin_get_stock();
*coin2 = 0;
// without this, jubeat will spawn never ending credit inserts
eamuse_coin_consume_stock();
}
static char __cdecl device_get_dispw() {
return 0;
}
static long __cdecl device_get_input(int a1) {
// Gitadora
if (avs::game::is_model({ "J32", "J33", "K32", "K33", "L32", "L33", "M32" })) {
long ret = 0;
// get buttons
auto &buttons = games::gitadora::get_buttons();
if (Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::Service))) {
ret |= 0x8;
}
if (Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::Test))) {
ret |= 0x2;
}
if (Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::Coin))) {
ret |= 0x10;
}
// gf player 2 controls
if (a1 == 1) {
if (Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::GuitarP2Start))) {
ret |= 0x4;
}
if (Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::GuitarP2Up))) {
ret |= 0x20;
}
if (Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::GuitarP2Down))) {
ret |= 0x40;
}
if (Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::GuitarP2Left))) {
ret |= 0x80;
}
if (Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::GuitarP2Right))) {
ret |= 0x100;
}
if (Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::GuitarP2Help))) {
ret |= 0x200;
}
if (Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::GuitarP2Effect1))) {
ret |= 0x400;
}
if (Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::GuitarP2Effect2))) {
ret |= 0x800;
}
if (Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::GuitarP2Effect3))) {
ret |= 0x1000;
}
if (!Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::GuitarP2EffectPedal))) {
ret |= 0x2000;
}
if (Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::GuitarP2ButtonExtra1))) {
ret |= 0x4000;
}
if (Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::GuitarP2ButtonExtra2))) {
ret |= 0x8000;
}
// return flags
return ret;
}
if (Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::GuitarP1Start)) ||
Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::DrumStart)))
{
ret |= 0x4;
}
if (Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::GuitarP1Up)) ||
Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::DrumUp)))
{
ret |= 0x20;
}
if (Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::GuitarP1Down)) ||
Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::DrumDown)))
{
ret |= 0x40;
}
if (Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::GuitarP1Left)) ||
Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::DrumLeft)))
{
ret |= 0x80;
}
if (Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::GuitarP1Right)) ||
Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::DrumRight)))
{
ret |= 0x100;
}
if (Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::GuitarP1Help)) ||
Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::DrumHelp)))
{
ret |= 0x200;
}
if (Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::GuitarP1Effect1))) {
ret |= 0x400;
}
if (Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::GuitarP1Effect2))) {
ret |= 0x800;
}
if (Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::GuitarP1Effect3))) {
ret |= 0x1000;
}
if (!Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::GuitarP1EffectPedal))) {
ret |= 0x2000;
}
if (Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::GuitarP1ButtonExtra1)) ||
Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::DrumButtonExtra1)))
{
ret |= 0x4000;
}
if (Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::GuitarP1ButtonExtra2)) ||
Buttons::getState(RI_MGR, buttons.at(games::gitadora::Buttons::DrumButtonExtra2)))
{
ret |= 0x8000;
}
// return flags
return ret;
}
// all other games get updated in device_update()
if (a1) {
return 0;
}
return DEVICE_INPUT_STATE;
}
static long __cdecl device_get_input_time() {
time_t t = std::time(nullptr);
auto now = static_cast<long int> (t);
return now;
}
static int *__cdecl device_get_jamma() {
return &DEVICE_INPUT_STATE;
}
static int __cdecl device_get_jamma_history(struct T_JAMMA_HISTORY_INFO *a1, int a2) {
return 0;
}
static int __cdecl device_get_panel_trg_off(int a1, int a2, int a3) {
return 0;
}
static int __cdecl device_get_panel_trg_on(int a1, int a2, int a3) {
return 0;
}
static int __cdecl device_get_panel_trg_short_on(int a1, int a2, int a3) {
return 0;
}
static int __cdecl device_get_secplug_error(int a1) {
return 0;
}
static int __cdecl device_get_secplug_hist(int a1, int a2, char *a3) {
*a3 = 0;
return 0;
}
static int __cdecl device_get_racecount() {
return 0;
}
static int __cdecl device_get_secplug(int a1, int a2, int a3) {
return 1;
}
static int __cdecl device_get_sliptrg(int a1) {
return 0;
}
static int __cdecl device_get_status() {
return 0;
}
static int __cdecl device_get_subboard_version(void *data, unsigned int size) {
if (size < sizeof(DEVICE_SUBBOARD_VERSION)) {
memset(data, 0, MIN(1, size));
} else {
memcpy(data, DEVICE_SUBBOARD_VERSION, sizeof(DEVICE_SUBBOARD_VERSION));
}
return 0;
}
static const char *__cdecl device_get_sys_version() {
return DEVICE_SYSTEM_VERSION;
}
static int __cdecl device_initialize(int a1, int a2) {
DEVICE_INITIALIZED = true;
return 0;
}
static bool __cdecl device_is_initialized() {
return DEVICE_INITIALIZED;
}
static void __cdecl device_poweroff() {
}
static int __cdecl device_read_secplug(int a1, int a2, int a3) {
return 1;
}
static int __cdecl device_set_coinblocker_open(char number, char open) {
return 0;
}
static void __cdecl device_set_coincounter_merge() {
}
static int __cdecl device_set_coincounter_work() {
return 0;
}
static void __cdecl device_set_coincounter_controllable() {
}
static int __cdecl device_set_panel_mode(int mode) {
return 0;
}
static void __cdecl device_set_jamma_asyncmode() {
}
static void __cdecl device_set_jamma_normalmode() {
}
static void __cdecl device_set_jamma_unti_inputskip(int a1) {
}
static int __cdecl device_set_portout(size_t index, int value) {
// reflec beat
if (avs::game::is_model("MBR")) {
// get lights
auto &lights = games::rb::get_lights();
// mapping
static const size_t light_mapping[] {
games::rb::Lights::WooferR,
games::rb::Lights::WooferG,
games::rb::Lights::WooferB,
games::rb::Lights::EscutcheonR,
games::rb::Lights::EscutcheonG,
games::rb::Lights::EscutcheonB,
games::rb::Lights::TitleR,
games::rb::Lights::TitleG,
games::rb::Lights::TitleB,
games::rb::Lights::TitleUpR,
games::rb::Lights::TitleUpG,
games::rb::Lights::TitleUpB,
};
// set light
if (index < std::size(light_mapping)) {
Lights::writeLight(RI_MGR, lights.at(light_mapping[index]), value / 127.f);
}
}
return 0;
}
static void __cdecl device_set_portoutbit() {
}
static int __cdecl device_set_watchdog_timer(int a1) {
return 0;
}
static void __cdecl device_set_watchdog() {
return;
}
static void __cdecl device_update() {
// flush device output
RI_MGR->devices_flush_output();
// JB knit and copious
if (avs::game::is_model({ "J44", "K44" })) {
// update touch
games::jb::touch_update();
// get buttons
auto &buttons = games::jb::get_buttons();
// reset
DEVICE_INPUT_STATE = 0;
if (Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Service))) {
DEVICE_INPUT_STATE |= 1 << 30;
}
if (Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Test))) {
DEVICE_INPUT_STATE |= 1 << 28;
}
if (Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::CoinMech))) {
DEVICE_INPUT_STATE |= 1 << 29;
}
if (games::jb::TOUCH_STATE[3] || Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Button1))) {
DEVICE_INPUT_STATE |= 1 << 13;
}
if (games::jb::TOUCH_STATE[7] || Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Button2))) {
DEVICE_INPUT_STATE |= 1 << 9;
}
if (games::jb::TOUCH_STATE[11] || Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Button3))) {
DEVICE_INPUT_STATE |= 1 << 21;
}
if (games::jb::TOUCH_STATE[15] || Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Button4))) {
DEVICE_INPUT_STATE |= 1 << 17;
}
if (games::jb::TOUCH_STATE[2] || Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Button5))) {
DEVICE_INPUT_STATE |= 1 << 14;
}
if (games::jb::TOUCH_STATE[6] || Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Button6))) {
DEVICE_INPUT_STATE |= 1 << 10;
}
if (games::jb::TOUCH_STATE[10] || Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Button7))) {
DEVICE_INPUT_STATE |= 1 << 22;
}
if (games::jb::TOUCH_STATE[14] || Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Button8))) {
DEVICE_INPUT_STATE |= 1 << 18;
}
if (games::jb::TOUCH_STATE[1] || Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Button9))) {
DEVICE_INPUT_STATE |= 1 << 15;
}
if (games::jb::TOUCH_STATE[5] || Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Button10))) {
DEVICE_INPUT_STATE |= 1 << 11;
}
if (games::jb::TOUCH_STATE[9] || Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Button11))) {
DEVICE_INPUT_STATE |= 1 << 23;
}
if (games::jb::TOUCH_STATE[13] || Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Button12))) {
DEVICE_INPUT_STATE |= 1 << 19;
}
if (games::jb::TOUCH_STATE[0] || Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Button13))) {
DEVICE_INPUT_STATE |= 1 << 24;
}
if (games::jb::TOUCH_STATE[4] || Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Button14))) {
DEVICE_INPUT_STATE |= 1 << 12;
}
if (games::jb::TOUCH_STATE[8] || Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Button15))) {
DEVICE_INPUT_STATE |= 1 << 26;
}
if (games::jb::TOUCH_STATE[12] || Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Button16))) {
DEVICE_INPUT_STATE |= 1 << 20;
}
DEVICE_INPUT_STATE = ~DEVICE_INPUT_STATE;
}
// JB
if (avs::game::is_model("L44")) {
// update touch
games::jb::touch_update();
// get buttons
auto &buttons = games::jb::get_buttons();
// reset
DEVICE_INPUT_STATE = 0;
if (Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Service))) {
DEVICE_INPUT_STATE |= 1 << 25;
}
if (Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Test))) {
DEVICE_INPUT_STATE |= 1 << 28;
}
if (Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::CoinMech))) {
DEVICE_INPUT_STATE |= 1 << 24;
}
if (games::jb::TOUCH_STATE[0] || Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Button1))) {
DEVICE_INPUT_STATE |= 1 << 5;
}
if (games::jb::TOUCH_STATE[1] || Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Button2))) {
DEVICE_INPUT_STATE |= 1 << 1;
}
if (games::jb::TOUCH_STATE[2] || Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Button3))) {
DEVICE_INPUT_STATE |= 1 << 13;
}
if (games::jb::TOUCH_STATE[3] || Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Button4))) {
DEVICE_INPUT_STATE |= 1 << 9;
}
if (games::jb::TOUCH_STATE[4] || Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Button5))) {
DEVICE_INPUT_STATE |= 1 << 6;
}
if (games::jb::TOUCH_STATE[5] || Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Button6))) {
DEVICE_INPUT_STATE |= 1 << 2;
}
if (games::jb::TOUCH_STATE[6] || Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Button7))) {
DEVICE_INPUT_STATE |= 1 << 14;
}
if (games::jb::TOUCH_STATE[7] || Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Button8))) {
DEVICE_INPUT_STATE |= 1 << 10;
}
if (games::jb::TOUCH_STATE[8] || Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Button9))) {
DEVICE_INPUT_STATE |= 1 << 7;
}
if (games::jb::TOUCH_STATE[9] || Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Button10))) {
DEVICE_INPUT_STATE |= 1 << 3;
}
if (games::jb::TOUCH_STATE[10] || Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Button11))) {
DEVICE_INPUT_STATE |= 1 << 15;
}
if (games::jb::TOUCH_STATE[11] || Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Button12))) {
DEVICE_INPUT_STATE |= 1 << 11;
}
if (games::jb::TOUCH_STATE[12] || Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Button13))) {
DEVICE_INPUT_STATE |= 1 << 16;
}
if (games::jb::TOUCH_STATE[13] || Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Button14))) {
DEVICE_INPUT_STATE |= 1 << 4;
}
if (games::jb::TOUCH_STATE[14] || Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Button15))) {
DEVICE_INPUT_STATE |= 1 << 20;
}
if (games::jb::TOUCH_STATE[15] || Buttons::getState(RI_MGR, buttons.at(games::jb::Buttons::Button16))) {
DEVICE_INPUT_STATE |= 1 << 12;
}
}
// RB
if (avs::game::is_model({ "KBR", "LBR", "MBR" })) {
// get buttons
auto &buttons = games::rb::get_buttons();
// reset
DEVICE_INPUT_STATE = 0;
if (Buttons::getState(RI_MGR, buttons.at(games::rb::Buttons::Service))) {
DEVICE_INPUT_STATE |= 0x08;
}
if (Buttons::getState(RI_MGR, buttons.at(games::rb::Buttons::Test))) {
DEVICE_INPUT_STATE |= 0x02;
}
}
}
static void __cdecl device_update_secplug() {
}
static int __cdecl devsci_break(char a1, char a2) {
return 0;
}
static int __cdecl devsci_open(int a1, int a2) {
return 0;
}
static int __cdecl devsci_read(int a1, int a2) {
return 0;
}
static int __cdecl devsci_write(int a1, int a2, int a3) {
return a3;
}
static int __cdecl p4io_sci_boot() {
return 1;
}
static int __cdecl p4io_sci_close(int index) {
return 0;
}
static int __cdecl p4io_sci_set_linebreak(int index, char c) {
return 0;
}
static int __cdecl p4io_sci_setparam(int a1, int a2, int a3, uint8_t a4) {
return 1;
}
static int __cdecl p4io_sci_puts(int index, const char *msg, int size) {
return 0;
}
static int __cdecl p4io_sci_gets(int index, uint8_t *buffer, int buffer_size) {
return 0;
}
static int __cdecl p4io_sci_flush() {
return 0;
}
static int __cdecl p4io_sci_flush_complete() {
return 1;
}
static int __cdecl p4io_sci_clear_error(int index) {
// return cleared error
return 0;
}
static int __cdecl p4io_sci_get_error(int index) {
return 0;
}
static int __cdecl p4io_sci_print_error(int index, int (__fastcall *logger)(const char *)) {
return 0;
}
void spicedevice_attach() {
// get instance
DEVICE_INSTANCE = libutils::try_module(DEVICE_INSTANCE_NAME1);
if (!DEVICE_INSTANCE) {
DEVICE_INSTANCE = libutils::try_module(DEVICE_INSTANCE_NAME2);
}
if (!DEVICE_INSTANCE) {
log_info("device", "skipping device module hooks");
return;
}
log_info("device", "SpiceTools DEVICE");
/*
* Patches
* the trick here is to account for normal names and specific ones
*/
detour::inline_hook((void *) device_check_secplug, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_check_secplug", "?device_check_secplug@@YAHH@Z"}));
detour::inline_hook((void *) device_force_check_secplug, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_force_check_secplug", "?device_force_check_secplug@@YAXXZ"}));
detour::inline_hook((void *) device_dispose_coinstock, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_dispose_coinstock"}));
detour::inline_hook((void *) device_finalize, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_finalize", "?device_finalize@@YAXXZ"}));
detour::inline_hook((void *) device_get_coinstock, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_get_coinstock", "?device_get_coinstock@@YAXPEAG0@Z"}));
detour::inline_hook((void *) device_get_coinstock_all, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_get_coinstock_all",
"?device_get_coinstock_all@@YAXPEAG0@Z",
"?device_get_coinstock_all@@YAXPAG0@Z"}));
detour::inline_hook((void *) device_get_dispw, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_get_dispw", "?device_get_dipsw@@YAEH@Z"}));
detour::inline_hook((void *) device_get_input, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_get_input", "?device_get_input@@YAIH@Z"}));
detour::inline_hook((void *) device_get_input_time, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_get_input_time", "?device_get_input_time@@YA_KXZ"}));
detour::inline_hook((void *) device_get_jamma, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_get_jamma"}));
detour::inline_hook((void *) device_get_jamma_history, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_get_jamma_history",
"?device_get_jamma_history@@YAHPEAUT_JAMMA_HISTORY_INFO@@H@Z",
"?device_get_jamma_history@@YAHPAUT_JAMMA_HISTORY_INFO@@H@Z"}));
detour::inline_hook((void *) device_get_panel_trg_off, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_get_panel_trg_off"}));
detour::inline_hook((void *) device_get_panel_trg_on, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_get_panel_trg_on"}));
detour::inline_hook((void *) device_get_panel_trg_short_on, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_get_panel_trg_short_on"}));
detour::inline_hook((void *) device_get_secplug_error, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_get_secplug_error"}));
detour::inline_hook((void *) device_get_secplug_hist, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_get_secplug_hist"}));
detour::inline_hook((void *) device_get_racecount, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_get_racecount", "?device_get_racecount@@YAHXZ"}));
detour::inline_hook((void *) device_get_secplug, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_get_secplug", "?device_get_secplug@@YAHHQEAE0@Z"}));
detour::inline_hook((void *) device_get_sliptrg, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_get_sliptrg", "?device_get_sliptrg@@YAIH@Z"}));
detour::inline_hook((void *) device_get_status, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_get_status", "?device_get_status@@YAHXZ"}));
detour::inline_hook((void *) device_get_subboard_version, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_get_subboard_version",
"?device_get_subboard_version@@YAHPEADH@Z",
"?device_get_subboard_version@@YAHPADH@Z"}));
detour::inline_hook((void *) device_get_sys_version, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_get_sys_version", "?device_get_sys_version@@YAPEBDXZ"}));
detour::inline_hook((void *) device_initialize, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_initialize",
"?device_initialize@@YAHH@Z",
"?device_initialize@@YAHXZ"}));
detour::inline_hook((void *) device_is_initialized, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_is_initialized"}));
detour::inline_hook((void *) device_poweroff, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_poweroff", "?device_poweroff@@YAXXZ"}));
detour::inline_hook((void *) device_read_secplug, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_read_secplug", "?device_read_secplug@@YAHHQEAE0@Z"}));
detour::inline_hook((void *) device_set_coinblocker_open, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_set_coinblocker_open", "?device_set_coinblocker_open@@YAXEE@Z"}));
detour::inline_hook((void *) device_set_coincounter_merge, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_set_coincounter_merge", "?device_set_coincounter_merge@@YAXE@Z"}));
detour::inline_hook((void *) device_set_coincounter_work, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_set_coincounter_work", "?device_set_coincounter_work@@YAXEE@Z"}));
detour::inline_hook((void *) device_set_coincounter_controllable, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_set_coincounter_controllable", "?device_set_coincounter_controllable@@YAXEE@Z"}));
detour::inline_hook((void *) device_set_panel_mode, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_set_panel_mode"}));
detour::inline_hook((void *) device_set_jamma_asyncmode, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_set_jamma_asyncmode", "?device_set_jamma_asyncmode@@YAXXZ"}));
detour::inline_hook((void *) device_set_jamma_normalmode, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_set_jamma_normalmode", "?device_set_jamma_normalmode@@YAXXZ"}));
detour::inline_hook((void *) device_set_jamma_unti_inputskip, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_set_jamma_unti_inputskip", "?device_set_jamma_unti_inputskip@@YAXH@Z"}));
detour::inline_hook((void *) device_set_portout, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_set_portout", "?device_set_portout@@YAXHH@Z"}));
detour::inline_hook((void *) device_set_portoutbit, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_set_portoutbit", "?device_set_portoutbit@@YAXHH@Z"}));
detour::inline_hook((void *) device_set_watchdog_timer, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_set_watchdog_timer"}));
detour::inline_hook((void *) device_set_watchdog, libutils::try_proc_list(
DEVICE_INSTANCE, {"?device_set_watchdog@@YAXH@Z"}));
detour::inline_hook((void *) device_update, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_update", "?device_update@@YAXXZ"}));
detour::inline_hook((void *) device_update_secplug, libutils::try_proc_list(
DEVICE_INSTANCE, {"device_update_secplug", "?device_update_secplug@@YAXXZ"}));
detour::inline_hook((void *) devsci_break, libutils::try_proc_list(
DEVICE_INSTANCE, {"devsci_break", "?devsci_break@@YAXHH@Z"}));
detour::inline_hook((void *) devsci_open, libutils::try_proc_list(
DEVICE_INSTANCE, {"devsci_open", "?devsci_open@@YAXHHH@Z"}));
detour::inline_hook((void *) devsci_read, libutils::try_proc_list(
DEVICE_INSTANCE, {"devsci_read", "?devsci_read@@YAHHPEAEH@Z"}));
detour::inline_hook((void *) devsci_write, libutils::try_proc_list(
DEVICE_INSTANCE, {"devsci_write", "?devsci_write@@YAHHPEBEH@Z"}));
detour::inline_hook((void *) p4io_sci_boot, libutils::try_proc_list(
DEVICE_INSTANCE, {"p4io_sci_boot"}));
detour::inline_hook((void *) p4io_sci_close, libutils::try_proc_list(
DEVICE_INSTANCE, {"p4io_sci_close"}));
detour::inline_hook((void *) p4io_sci_set_linebreak, libutils::try_proc_list(
DEVICE_INSTANCE, {"p4io_sci_set_linebreak"}));
detour::inline_hook((void *) p4io_sci_setparam, libutils::try_proc_list(
DEVICE_INSTANCE, {"p4io_sci_setparam"}));
detour::inline_hook((void *) p4io_sci_puts, libutils::try_proc_list(
DEVICE_INSTANCE, {"p4io_sci_puts"}));
detour::inline_hook((void *) p4io_sci_gets, libutils::try_proc_list(
DEVICE_INSTANCE, {"p4io_sci_gets"}));
detour::inline_hook((void *) p4io_sci_flush, libutils::try_proc_list(
DEVICE_INSTANCE, {"p4io_sci_flush"}));
detour::inline_hook((void *) p4io_sci_flush_complete, libutils::try_proc_list(
DEVICE_INSTANCE, {"p4io_sci_flush_complete"}));
detour::inline_hook((void *) p4io_sci_clear_error, libutils::try_proc_list(
DEVICE_INSTANCE, {"p4io_sci_clear_error"}));
detour::inline_hook((void *) p4io_sci_get_error, libutils::try_proc_list(
DEVICE_INSTANCE, {"p4io_sci_get_error"}));
detour::inline_hook((void *) p4io_sci_print_error, libutils::try_proc_list(
DEVICE_INSTANCE, {"p4io_sci_print_error"}));
// TODO: missing functions
// device_hwinfo_etc_update
// device_hwinfo_get
// device_hwinfo_update
// device_set_jamma_interrupt_status
// sci_mng_get_debug_info
// test_device
}
void spicedevice_detach() {
// TODO
}

5
misc/device.h Normal file
View File

@@ -0,0 +1,5 @@
#pragma once
void spicedevice_attach();
void spicedevice_detach();

570
misc/eamuse.cpp Normal file
View File

@@ -0,0 +1,570 @@
#include "eamuse.h"
#include <fstream>
#include <thread>
#include "avs/game.h"
#include "cfg/config.h"
#include "games/io.h"
#include "rawinput/rawinput.h"
#include "games/sdvx/sdvx.h"
#include "util/logging.h"
#include "util/time.h"
#include "util/utils.h"
#include "bt5api.h"
// state
static bool CARD_INSERT[2] = {false, false};
static double CARD_INSERT_TIME[2] = {0, 0};
static double CARD_INSERT_TIMEOUT = 2.0;
static char CARD_INSERT_UID[2][8];
static char CARD_INSERT_UID_ENABLE[2] = {false, false};
static int COIN_STOCK = 0;
static bool COIN_BLOCK = false;
static std::thread *COIN_INPUT_THREAD;
static bool COIN_INPUT_THREAD_ACTIVE = false;
static uint16_t KEYPAD_STATE[] = {0, 0};
static uint16_t KEYPAD_STATE_OVERRIDES[] = {0, 0};
static uint16_t KEYPAD_STATE_OVERRIDES_BT5[] = {0, 0};
static uint16_t KEYPAD_STATE_OVERRIDES_READER[] = {0, 0};
static uint16_t KEYPAD_STATE_OVERRIDES_OVERLAY[] = {0, 0};
static std::string EAMUSE_GAME_NAME;
static ConfigKeypadBindings KEYPAD_BINDINGS {};
// auto card
bool AUTO_INSERT_CARD[2] = {false, false};
float AUTO_INSERT_CARD_COOLDOWN = 8.f; // seconds
static std::optional<double> AUTO_INSERT_CARD_FIRST_CONSUME_TIME;
static bool AUTO_INSERT_CARD_CACHED[2];
static uint8_t AUTO_INSERT_CARD_CACHED_DATA[2][8];
bool eamuse_get_card(int active_count, int unit_id, uint8_t *card) {
// get unit index
int index = unit_id > 0 && active_count > 1 ? 1 : 0;
// check cards cached for auto-insert
// explicitly not logging anything in this path to avoid log spam
if (AUTO_INSERT_CARD[index] && AUTO_INSERT_CARD_CACHED[index]) {
memcpy(card, AUTO_INSERT_CARD_CACHED_DATA[index], 8);
return true;
}
// reader card input
if (CARD_INSERT_UID_ENABLE[index]) {
CARD_INSERT_UID_ENABLE[index] = false;
memcpy(card, CARD_INSERT_UID[index], 8);
log_info("eamuse", "Inserted card from reader {}: {}", index, bin2hex(card, 8));
return true;
}
// get file path
std::filesystem::path path;
if (!KEYPAD_BINDINGS.card_paths[index].empty()) {
path = KEYPAD_BINDINGS.card_paths[index];
} else {
path = index > 0 ? "card1.txt" : "card0.txt";
}
// call the next function
return eamuse_get_card(path, card, index);
}
bool eamuse_get_card(const std::filesystem::path &path, uint8_t *card, int index) {
// Check if card overrides are present
if (!CARD_OVERRIDES[index].empty()) {
// Override is present
for (int n = 0; n < 16; n++) {
char c = CARD_OVERRIDES[index].c_str()[n];
bool digit = c >= '0' && c <= '9';
bool character_big = c >= 'A' && c <= 'F';
bool character_small = c >= 'a' && c <= 'f';
if (!digit && !character_big && !character_small) {
log_warning("eamuse",
"{} card override contains an invalid character sequence at byte {} (16 characters, 0-9/A-F only)",
CARD_OVERRIDES[index], n);
return false;
}
}
// Log info
log_info("eamuse", "Inserted card override: {}", CARD_OVERRIDES[index]);
// Card is valid, convert and set it.
hex2bin(CARD_OVERRIDES[index].c_str(), card);
// cache it for auto-insert
if (AUTO_INSERT_CARD[index] && !AUTO_INSERT_CARD_CACHED[index]) {
memcpy(AUTO_INSERT_CARD_CACHED_DATA[index], card, 8);
AUTO_INSERT_CARD_CACHED[index] = true;
log_info("eamuse", "Auto card insert - caching this card in memory: {}", CARD_OVERRIDES[index]);
}
// success
return true;
}
// Overrides are not present. Use the standard file reading method.
return eamuse_get_card_from_file(path, card, index);
}
bool eamuse_get_card_from_file(const std::filesystem::path &path, uint8_t *card, int index) {
// open file
std::ifstream f(path);
if (!f) {
log_warning("eamuse", "{} can not be opened!", path.string());
return false;
}
// get size
f.seekg(0, f.end);
auto length = (size_t) f.tellg();
f.seekg(0, f.beg);
// check size
if (length < 16) {
log_warning("eamuse", "{} is too small (must be at least 16 characters)", path.string());
return false;
}
// read file
char buffer[17];
f.read(buffer, 16);
buffer[16] = 0;
// verify card
for (int n = 0; n < 16; n++) {
char c = buffer[n];
bool digit = c >= '0' && c <= '9';
bool character_big = c >= 'A' && c <= 'F';
bool character_small = c >= 'a' && c <= 'f';
if (!digit && !character_big && !character_small) {
log_warning("eamuse",
"{} contains an invalid character sequence at byte {} (16 characters, 0-9/A-F only)",
path.string(), n);
return false;
}
}
// info
log_info("eamuse", "Inserted {}: {}", path.string(), buffer);
// convert hex to bytes
hex2bin(buffer, card);
// cache it for auto-insert
if (AUTO_INSERT_CARD[index] && !AUTO_INSERT_CARD_CACHED[index]) {
memcpy(AUTO_INSERT_CARD_CACHED_DATA[index], card, 8);
AUTO_INSERT_CARD_CACHED[index] = true;
log_info("eamuse", "Auto card insert - caching this card in memory: {}", buffer);
}
// success
return true;
}
void eamuse_card_insert(int unit) {
CARD_INSERT[unit] = true;
CARD_INSERT_TIME[unit] = get_performance_seconds();
}
void eamuse_card_insert(int unit, const uint8_t *card) {
memcpy(CARD_INSERT_UID[unit], card, 8);
CARD_INSERT[unit] = true;
CARD_INSERT_TIME[unit] = get_performance_seconds();
CARD_INSERT_UID_ENABLE[unit] = true;
}
bool eamuse_card_insert_consume(int active_count, int unit_id) {
// get unit index
int index = unit_id > 0 && active_count > 1 ? 1 : 0;
// bt5api
if (BT5API_ENABLED) {
bt5api_poll_reader_card((uint8_t) index);
}
// auto insert card
if (AUTO_INSERT_CARD[index]) {
// reset timer if enough time has passed since last insert
if (CARD_INSERT[index] &&
AUTO_INSERT_CARD_COOLDOWN < (get_performance_seconds() - CARD_INSERT_TIME[index])) {
CARD_INSERT[index] = false;
}
if (!CARD_INSERT[index]) {
eamuse_card_insert(index);
// not logging anything here to prevent spam
// log_info("eamuse", "Automatic card insert on {}/{} (-autocard)", unit_id + 1, active_count);
return true;
} else {
return false;
}
}
// check for card insert
auto keypad_buttons = games::get_buttons_keypads(eamuse_get_game());
auto offset = unit_id * games::KeypadButtons::Size;
if ((CARD_INSERT[index] && fabs(get_performance_seconds() - CARD_INSERT_TIME[index]) < CARD_INSERT_TIMEOUT)
|| GameAPI::Buttons::getState(RI_MGR, keypad_buttons->at(games::KeypadButtons::InsertCard + offset))) {
log_info("eamuse", "Card insert on {}/{}", unit_id + 1, active_count);
CARD_INSERT[index] = false;
return true;
}
return false;
}
bool eamuse_coin_get_block() {
return COIN_BLOCK;
}
void eamuse_coin_set_block(bool block) {
COIN_BLOCK = block;
}
int eamuse_coin_get_stock() {
return COIN_STOCK;
}
void eamuse_coin_set_stock(int amount) {
COIN_STOCK = amount;
}
bool eamuse_coin_consume(int amount) {
if (COIN_STOCK < amount) {
return false;
} else {
COIN_STOCK -= amount;
return true;
}
}
int eamuse_coin_consume_stock() {
int stock = COIN_STOCK;
COIN_STOCK = 0;
return stock;
}
int eamuse_coin_add() {
return ++COIN_STOCK;
}
void eamuse_coin_start_thread() {
// set active
COIN_INPUT_THREAD_ACTIVE = true;
// create thread
COIN_INPUT_THREAD = new std::thread([]() {
auto overlay_buttons = games::get_buttons_overlay(eamuse_get_game());
static bool COIN_INPUT_KEY_STATE = false;
while (COIN_INPUT_THREAD_ACTIVE) {
// check input key
if (overlay_buttons && GameAPI::Buttons::getState(RI_MGR, overlay_buttons->at(
games::OverlayButtons::InsertCoin))) {
if (!COIN_INPUT_KEY_STATE) {
if (COIN_BLOCK)
log_info("eamuse", "coin inserted while blocked");
else {
log_info("eamuse", "coin insert");
COIN_STOCK++;
}
}
COIN_INPUT_KEY_STATE = true;
} else {
COIN_INPUT_KEY_STATE = false;
}
// once every two frames
Sleep(1000 / 30);
}
});
}
void eamuse_coin_stop_thread() {
COIN_INPUT_THREAD_ACTIVE = false;
COIN_INPUT_THREAD->join();
delete COIN_INPUT_THREAD;
COIN_INPUT_THREAD = nullptr;
}
void eamuse_set_keypad_overrides(size_t unit, uint16_t keypad_state) {
// check unit
if (unit >= std::size(KEYPAD_STATE_OVERRIDES)) {
return;
}
// set state
KEYPAD_STATE_OVERRIDES[unit] = keypad_state;
}
void eamuse_set_keypad_overrides_bt5(size_t unit, uint16_t keypad_state) {
// check unit
if (unit >= std::size(KEYPAD_STATE_OVERRIDES_BT5)) {
return;
}
// set state
KEYPAD_STATE_OVERRIDES_BT5[unit] = keypad_state;
}
void eamuse_set_keypad_overrides_reader(size_t unit, uint16_t keypad_state) {
// check unit
if (unit >= std::size(KEYPAD_STATE_OVERRIDES_READER)) {
return;
}
// set state
KEYPAD_STATE_OVERRIDES_READER[unit] = keypad_state;
}
void eamuse_set_keypad_overrides_overlay(size_t unit, uint16_t keypad_state) {
// check unit
if (unit >= std::size(KEYPAD_STATE_OVERRIDES_OVERLAY)) {
return;
}
// set state
KEYPAD_STATE_OVERRIDES_OVERLAY[unit] = keypad_state;
}
uint16_t eamuse_get_keypad_state(size_t unit) {
// check unit
if (unit >= std::size(KEYPAD_STATE)) {
return 0;
}
// reset
KEYPAD_STATE[unit] = KEYPAD_STATE_OVERRIDES[unit];
KEYPAD_STATE[unit] |= KEYPAD_STATE_OVERRIDES_BT5[unit];
KEYPAD_STATE[unit] |= KEYPAD_STATE_OVERRIDES_READER[unit];
KEYPAD_STATE[unit] |= KEYPAD_STATE_OVERRIDES_OVERLAY[unit];
// bt5api
if (BT5API_ENABLED) {
bt5api_poll_reader_keypad((uint8_t) unit);
}
// get keybinds and get offset for unit
auto keypad_buttons = games::get_buttons_keypads(eamuse_get_game());
auto offset = unit * games::KeypadButtons::Size;
// parse
if (GameAPI::Buttons::getState(RI_MGR, keypad_buttons->at(games::KeypadButtons::Keypad0 + offset))) {
KEYPAD_STATE[unit] |= 1 << EAM_IO_KEYPAD_0;
}
if (GameAPI::Buttons::getState(RI_MGR, keypad_buttons->at(games::KeypadButtons::Keypad1 + offset))) {
KEYPAD_STATE[unit] |= 1 << EAM_IO_KEYPAD_1;
}
if (GameAPI::Buttons::getState(RI_MGR, keypad_buttons->at(games::KeypadButtons::Keypad2 + offset))) {
KEYPAD_STATE[unit] |= 1 << EAM_IO_KEYPAD_2;
}
if (GameAPI::Buttons::getState(RI_MGR, keypad_buttons->at(games::KeypadButtons::Keypad3 + offset))) {
KEYPAD_STATE[unit] |= 1 << EAM_IO_KEYPAD_3;
}
if (GameAPI::Buttons::getState(RI_MGR, keypad_buttons->at(games::KeypadButtons::Keypad4 + offset))) {
KEYPAD_STATE[unit] |= 1 << EAM_IO_KEYPAD_4;
}
if (GameAPI::Buttons::getState(RI_MGR, keypad_buttons->at(games::KeypadButtons::Keypad5 + offset))) {
KEYPAD_STATE[unit] |= 1 << EAM_IO_KEYPAD_5;
}
if (GameAPI::Buttons::getState(RI_MGR, keypad_buttons->at(games::KeypadButtons::Keypad6 + offset))) {
KEYPAD_STATE[unit] |= 1 << EAM_IO_KEYPAD_6;
}
if (GameAPI::Buttons::getState(RI_MGR, keypad_buttons->at(games::KeypadButtons::Keypad7 + offset))) {
KEYPAD_STATE[unit] |= 1 << EAM_IO_KEYPAD_7;
}
if (GameAPI::Buttons::getState(RI_MGR, keypad_buttons->at(games::KeypadButtons::Keypad8 + offset))) {
KEYPAD_STATE[unit] |= 1 << EAM_IO_KEYPAD_8;
}
if (GameAPI::Buttons::getState(RI_MGR, keypad_buttons->at(games::KeypadButtons::Keypad9 + offset))) {
KEYPAD_STATE[unit] |= 1 << EAM_IO_KEYPAD_9;
}
if (GameAPI::Buttons::getState(RI_MGR, keypad_buttons->at(games::KeypadButtons::Keypad00 + offset))) {
KEYPAD_STATE[unit] |= 1 << EAM_IO_KEYPAD_00;
}
if (GameAPI::Buttons::getState(RI_MGR, keypad_buttons->at(games::KeypadButtons::KeypadDecimal + offset))) {
KEYPAD_STATE[unit] |= 1 << EAM_IO_KEYPAD_DECIMAL;
}
if (GameAPI::Buttons::getState(RI_MGR, keypad_buttons->at(games::KeypadButtons::InsertCard + offset))) {
KEYPAD_STATE[unit] |= 1 << EAM_IO_INSERT;
}
// return state
return KEYPAD_STATE[unit];
}
std::string eamuse_get_keypad_state_str(size_t unit) {
auto state = eamuse_get_keypad_state(unit);
std::ostringstream ss;
if (state & 1 << EAM_IO_KEYPAD_0) ss << "0";
if (state & 1 << EAM_IO_KEYPAD_1) ss << "1";
if (state & 1 << EAM_IO_KEYPAD_2) ss << "2";
if (state & 1 << EAM_IO_KEYPAD_3) ss << "3";
if (state & 1 << EAM_IO_KEYPAD_4) ss << "4";
if (state & 1 << EAM_IO_KEYPAD_5) ss << "5";
if (state & 1 << EAM_IO_KEYPAD_6) ss << "6";
if (state & 1 << EAM_IO_KEYPAD_7) ss << "7";
if (state & 1 << EAM_IO_KEYPAD_8) ss << "8";
if (state & 1 << EAM_IO_KEYPAD_9) ss << "9";
if (state & 1 << EAM_IO_KEYPAD_00) ss << "00";
if (state & 1 << EAM_IO_KEYPAD_DECIMAL) ss << "00";
if (state & 1 << EAM_IO_INSERT) ss << "00";
return ss.str();
}
bool eamuse_keypad_state_naive() {
return KEYPAD_BINDINGS.keypads[0].empty() && KEYPAD_BINDINGS.keypads[1].empty();
}
void eamuse_set_game(std::string game) {
if (EAMUSE_GAME_NAME != game) {
EAMUSE_GAME_NAME = std::move(game);
eamuse_update_keypad_bindings();
}
}
void eamuse_update_keypad_bindings() {
KEYPAD_BINDINGS = Config::getInstance().getKeypadBindings(EAMUSE_GAME_NAME);
}
std::string eamuse_get_game() {
return EAMUSE_GAME_NAME;
}
int eamuse_get_game_keypads() {
if (avs::game::is_model("JDZ") ||
avs::game::is_model("KDZ") ||
avs::game::is_model("LDJ") ||
avs::game::is_model("JDX") ||
avs::game::is_model("KDX") ||
avs::game::is_model("MDX") ||
avs::game::is_model("J33") ||
avs::game::is_model("K33") ||
avs::game::is_model("L33") ||
avs::game::is_model("M32"))
{
return 2;
}
return 1;
}
int eamuse_get_game_keypads_name() {
auto game_name = eamuse_get_game();
if (game_name == "Beatmania IIDX" ||
game_name == "Dance Dance Revolution" ||
game_name == "GitaDora")
{
return 2;
}
return 1;
}
void eamuse_autodetect_game() {
if (avs::game::is_model("KFC"))
eamuse_set_game("Sound Voltex");
else if (avs::game::is_model("JDZ") ||
avs::game::is_model("KDZ") ||
avs::game::is_model("LDJ"))
eamuse_set_game("Beatmania IIDX");
else if (avs::game::is_model("J44") ||
avs::game::is_model("K44") ||
avs::game::is_model("L44"))
eamuse_set_game("Jubeat");
else if (avs::game::is_model("KDM"))
eamuse_set_game("Dance Evolution");
else if (avs::game::is_model("NBT"))
eamuse_set_game("Beatstream");
else if (avs::game::is_model("I36"))
eamuse_set_game("Metal Gear");
else if (avs::game::is_model("KBR") ||
avs::game::is_model("LBR") ||
avs::game::is_model("MBR"))
eamuse_set_game("Reflec Beat");
else if (avs::game::is_model("KBI"))
eamuse_set_game("Tenkaichi Shogikai");
else if (avs::game::is_model("K39") ||
avs::game::is_model("L39") ||
avs::game::is_model("M39"))
eamuse_set_game("Pop'n Music");
else if (avs::game::is_model("KGG"))
eamuse_set_game("Steel Chronicle");
else if (avs::game::is_model("JGT"))
eamuse_set_game("Road Fighters 3D");
else if (avs::game::is_model("PIX"))
eamuse_set_game("Museca");
else if (avs::game::is_model("R66"))
eamuse_set_game("Bishi Bashi Channel");
else if (avs::game::is_model("J32") ||
avs::game::is_model("J33") ||
avs::game::is_model("K32") ||
avs::game::is_model("K33") ||
avs::game::is_model("L32") ||
avs::game::is_model("L33") ||
avs::game::is_model("M32"))
eamuse_set_game("GitaDora");
else if (avs::game::is_model("JDX") ||
avs::game::is_model("KDX") ||
avs::game::is_model("MDX"))
eamuse_set_game("Dance Dance Revolution");
else if (avs::game::is_model("PAN"))
eamuse_set_game("Nostalgia");
else if (avs::game::is_model("JMA") ||
avs::game::is_model("KMA") ||
avs::game::is_model("LMA"))
eamuse_set_game("Quiz Magic Academy");
else if (avs::game::is_model("MMD"))
eamuse_set_game("FutureTomTom");
else if (avs::game::is_model("KK9"))
eamuse_set_game("Mahjong Fight Club");
else if (avs::game::is_model("JMP"))
eamuse_set_game("HELLO! Pop'n Music");
else if (avs::game::is_model("KLP"))
eamuse_set_game("LovePlus");
else if (avs::game::is_model("NSC"))
eamuse_set_game("Scotto");
else if (avs::game::is_model("REC"))
eamuse_set_game("DANCERUSH");
else if (avs::game::is_model("KCK") ||
avs::game::is_model("NCK"))
eamuse_set_game("Winning Eleven");
else if (avs::game::is_model("NCG"))
eamuse_set_game("Otoca D'or");
else if (avs::game::is_model("LA9"))
eamuse_set_game("Charge Machine");
else if (avs::game::is_model("JC9"))
eamuse_set_game("Ongaku Paradise");
else if (avs::game::is_model("TBS"))
eamuse_set_game("Busou Shinki: Armored Princess Battle Conductor");
else if (avs::game::is_model("UJK"))
eamuse_set_game("Chase Chase Jokers");
else if (avs::game::is_model("UKS"))
eamuse_set_game("QuizKnock STADIUM");
else {
log_warning("eamuse", "unknown game model: {}", avs::game::MODEL);
eamuse_set_game("unknown");
}
}
void eamuse_scard_callback(uint8_t slot_no, card_info_t *cardinfo) {
log_info(
"scard", "eamuse_scard_callback: slot_no={}, card_type={}", slot_no, cardinfo->card_type);
if (cardinfo->card_type > 0) {
eamuse_card_insert(slot_no & 1, cardinfo->uid);
}
}

78
misc/eamuse.h Normal file
View File

@@ -0,0 +1,78 @@
#pragma once
#include <cstdint>
#include <filesystem>
#include <string>
#include <optional>
#include "external/scard/scard.h"
/* From BT5 API for compatibility reasons.
Scan codes for the so-called "10 key" button panel on each card reader. Each
scan code corresponds to a bit position within the 16-bit bitfield that you
return from eam_io_get_keypad_state(). */
enum eam_io_keypad_scan_code {
EAM_IO_KEYPAD_0 = 0,
EAM_IO_KEYPAD_1 = 1,
EAM_IO_KEYPAD_4 = 2,
EAM_IO_KEYPAD_7 = 3,
EAM_IO_KEYPAD_00 = 4,
EAM_IO_KEYPAD_2 = 5,
EAM_IO_KEYPAD_5 = 6,
EAM_IO_KEYPAD_8 = 7,
EAM_IO_KEYPAD_DECIMAL = 8,
EAM_IO_KEYPAD_3 = 9,
EAM_IO_KEYPAD_6 = 10,
EAM_IO_KEYPAD_9 = 11,
EAM_IO_KEYPAD_COUNT = 12, /* Not an actual scan code */
EAM_IO_INSERT = 13, /* SpiceTools Extension */
};
extern std::string CARD_OVERRIDES[2];
extern bool AUTO_INSERT_CARD[2];
extern float AUTO_INSERT_CARD_COOLDOWN;
bool eamuse_get_card(int active_count, int unit_id, uint8_t *card);
bool eamuse_get_card(const std::filesystem::path &path, uint8_t *card, int unit_id);
bool eamuse_get_card_from_file(const std::filesystem::path &path, uint8_t *card, int index);
void eamuse_card_insert(int unit);
void eamuse_card_insert(int unit, const uint8_t *card);
bool eamuse_card_insert_consume(int active_count, int unit_id);
bool eamuse_coin_get_block();
void eamuse_coin_set_block(bool block);
int eamuse_coin_get_stock();
void eamuse_coin_set_stock(int amount);
bool eamuse_coin_consume(int amount);
int eamuse_coin_consume_stock();
int eamuse_coin_add();
void eamuse_coin_start_thread();
void eamuse_coin_stop_thread();
void eamuse_set_keypad_overrides(size_t unit, uint16_t keypad_state);
void eamuse_set_keypad_overrides_bt5(size_t unit, uint16_t keypad_state);
void eamuse_set_keypad_overrides_reader(size_t unit, uint16_t keypad_state);
void eamuse_set_keypad_overrides_overlay(size_t unit, uint16_t keypad_state);
uint16_t eamuse_get_keypad_state(size_t unit);
std::string eamuse_get_keypad_state_str(size_t unit);
void eamuse_update_keypad_bindings();
bool eamuse_keypad_state_naive();
void eamuse_set_game(std::string game);
std::string eamuse_get_game();
int eamuse_get_game_keypads();
int eamuse_get_game_keypads_name();
void eamuse_autodetect_game();
void eamuse_scard_callback(uint8_t slot_no, card_info_t *cardinfo);

1329
misc/extdev.cpp Normal file

File diff suppressed because it is too large Load Diff

4
misc/extdev.h Normal file
View File

@@ -0,0 +1,4 @@
#pragma once
void extdev_attach();
void extdev_detach();

307
misc/sciunit.cpp Normal file
View File

@@ -0,0 +1,307 @@
#include "sciunit.h"
#include "acio/icca/icca.h"
#include "avs/game.h"
#include "eamuse.h"
#include "games/jb/jb.h"
#include "games/jb/io.h"
#include "rawinput/rawinput.h"
#include "util/detour.h"
#include "util/libutils.h"
#include "util/logging.h"
#include "util/utils.h"
// function definitions
typedef int (__cdecl *cardunit_card_cardnumber_t)(const uint8_t *const, int, char *, int);
static cardunit_card_cardnumber_t cardunit_card_cardnumber = nullptr;
// state
static HINSTANCE SCIUNIT_INSTANCE;
static std::string SCIUNIT_INSTANCE_NAME = "sciunit.dll";
static bool SCIUNIT_INITIALIZED = false;
static bool CARD_IN = false;
static bool CARD_PRESSED = false;
static uint8_t CARD_UID[8];
static int CARD_TYPE = 1;
static char CARD_DISPLAY_ID[17];
static bool CARD_WAS_CONVERTED = false;
static inline void update_card() {
if (eamuse_card_insert_consume(1, 0)) {
if (!CARD_PRESSED) {
CARD_PRESSED = true;
// get and check if valid card
if (!eamuse_get_card(1, 0, CARD_UID)) {
return;
}
CARD_IN = true;
CARD_WAS_CONVERTED = false;
CARD_TYPE = is_card_uid_felica(CARD_UID) ? 2 : 1;
// convert the card UID into the Konami ID if the conversion method was found
if (cardunit_card_cardnumber != nullptr) {
auto ret = cardunit_card_cardnumber(CARD_UID, CARD_TYPE, CARD_DISPLAY_ID, sizeof(CARD_DISPLAY_ID));
CARD_DISPLAY_ID[16] = '\0';
CARD_WAS_CONVERTED = ret == 0;
}
}
} else {
CARD_PRESSED = false;
}
}
static int __cdecl mng_card_check_removed(int /* a1 */) {
return 0;
}
static int __cdecl mng_card_eject_init(int /* a1 */) {
CARD_IN = false;
return 0;
}
static int __cdecl mng_card_get_cardnumber(int a1, uint8_t *out) {
if (CARD_WAS_CONVERTED) {
// copy the display id to the output buffer
memcpy(out, CARD_DISPLAY_ID, 16);
} else {
// fallback to displaying the card UID if the conversion method was not
// found or the conversion to the Konami ID failed
std::string konami_id = bin2hex(CARD_UID, sizeof(CARD_UID));
memcpy(out, konami_id.c_str(), 16);
}
// ensure the output is null-terminated
out[16] = '\0';
return 0;
}
static int __cdecl mng_card_get_cardtype(int /* a1 */) {
return CARD_TYPE;
}
static int __cdecl mng_card_get_stat(int /* a1 */) {
return CARD_IN ? 2 : 1;
}
static int __cdecl mng_card_get_uid(int /* a1 */, uint8_t *uid) {
memcpy(uid, CARD_UID, 8);
return CARD_IN ? 0 : 1;
}
static int __cdecl mng_card_is_detected(int /* a1 */) {
return static_cast<int>(CARD_IN);
}
static int __cdecl mng_card_is_ejected(int /* a1 */) {
return 1;
}
static int __cdecl mng_card_is_inserted(int /* a1 */) {
update_card();
return static_cast<int>(CARD_IN);
}
static bool __cdecl mng_card_is_invalid_unit(int /* a1 */) {
return false;
}
static int __cdecl mng_card_is_read_done(int /* a1 */) {
update_card();
return static_cast<int>(CARD_IN);
}
static int __cdecl mng_card_is_ready(int /* a1 */) {
return 1;
}
static int __cdecl mng_card_is_removed(int /* a1 */) {
return 1;
}
static int __cdecl mng_card_is_valid_card(int /* a1 */) {
return static_cast<int>(CARD_IN);
}
static int __cdecl mng_card_read_init(int /* a1 */) {
return 0;
}
static int __cdecl mng_card_sensor_raw(int /* a1 */) {
update_card();
return CARD_IN ? 48 : 0;
}
static void __cdecl mng_card_sleep(int a1) {
}
static int __cdecl sciunit_finalize() {
return 0;
}
static int __cdecl sciunit_get_errorunit() {
return 0;
}
static int __cdecl sciunit_get_stat() {
return 0;
}
static int __cdecl sciunit_get_version(int a1, int a2) {
return 0;
}
static int __cdecl sciunit_initialize() {
SCIUNIT_INITIALIZED = true;
return 0;
}
static int __cdecl sciunit_is_initialized() {
return SCIUNIT_INITIALIZED ? 1 : 0;
}
static int __cdecl sciunit_is_usable() {
return 1;
}
static int __cdecl sciunit_led_set_color(uint8_t *led_field) {
// Jubeat
if (avs::game::is_model({ "J44", "K44", "L44" })) {
// get lights
auto &lights = games::jb::get_lights();
// control mapping
//
// The list in `Lamp Check` is not in the same order as the LED array passed to this
// function.
static size_t mapping[] = {
games::jb::Lights::PANEL_FRONT_R,
games::jb::Lights::PANEL_FRONT_G,
games::jb::Lights::PANEL_FRONT_B,
games::jb::Lights::PANEL_TOP_R,
games::jb::Lights::PANEL_TOP_G,
games::jb::Lights::PANEL_TOP_B,
games::jb::Lights::PANEL_LEFT_R,
games::jb::Lights::PANEL_LEFT_G,
games::jb::Lights::PANEL_LEFT_B,
games::jb::Lights::PANEL_RIGHT_R,
games::jb::Lights::PANEL_RIGHT_G,
games::jb::Lights::PANEL_RIGHT_B,
games::jb::Lights::PANEL_TITLE_R,
games::jb::Lights::PANEL_TITLE_G,
games::jb::Lights::PANEL_TITLE_B,
games::jb::Lights::PANEL_WOOFER_R,
games::jb::Lights::PANEL_WOOFER_G,
games::jb::Lights::PANEL_WOOFER_B,
};
// write light
for (size_t i = 0; i < std::size(mapping); i++) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(mapping[i]), led_field[i] / 255.f);
}
RI_MGR->devices_flush_output();
}
return 0;
}
static int __cdecl sciunit_reset() {
return 0;
}
static int __cdecl sciunit_update() {
if (SCIUNIT_INITIALIZED)
update_card();
return 0;
}
void sciunit_attach() {
// get instance
SCIUNIT_INSTANCE = libutils::try_module(SCIUNIT_INSTANCE_NAME);
if (!SCIUNIT_INSTANCE) {
log_info("sciunit", "skipping sciunit hooks");
return;
}
log_info("sciunit", "SpiceTools SCIUNIT");
// get functions
if (cardunit_card_cardnumber == nullptr) {
cardunit_card_cardnumber = libutils::try_proc_list<cardunit_card_cardnumber_t>(
SCIUNIT_INSTANCE, {"cardunit_card_cardnumber", "?cardunit_card_cardnumber@@YAHQBEHPADH@Z"});
}
// patch
detour::inline_hook((void *) mng_card_check_removed, libutils::try_proc(
SCIUNIT_INSTANCE, "mng_card_check_removed"));
detour::inline_hook((void *) mng_card_eject_init, libutils::try_proc(
SCIUNIT_INSTANCE, "mng_card_eject_init"));
detour::inline_hook((void *) mng_card_get_cardnumber, libutils::try_proc(
SCIUNIT_INSTANCE, "mng_card_get_cardnumber"));
detour::inline_hook((void *) mng_card_get_cardtype, libutils::try_proc(
SCIUNIT_INSTANCE, "mng_card_get_cardtype"));
detour::inline_hook((void *) mng_card_get_stat, libutils::try_proc(
SCIUNIT_INSTANCE, "mng_card_get_stat"));
detour::inline_hook((void *) mng_card_get_uid, libutils::try_proc(
SCIUNIT_INSTANCE, "mng_card_get_uid"));
detour::inline_hook((void *) mng_card_is_detected, libutils::try_proc(
SCIUNIT_INSTANCE, "mng_card_is_detected"));
detour::inline_hook((void *) mng_card_is_ejected, libutils::try_proc(
SCIUNIT_INSTANCE, "mng_card_is_ejected"));
detour::inline_hook((void *) mng_card_is_inserted, libutils::try_proc(
SCIUNIT_INSTANCE, "mng_card_is_inserted"));
detour::inline_hook((void *) mng_card_is_invalid_unit, libutils::try_proc(
SCIUNIT_INSTANCE, "mng_card_is_invalid_unit"));
detour::inline_hook((void *) mng_card_is_read_done, libutils::try_proc(
SCIUNIT_INSTANCE, "mng_card_is_read_done"));
detour::inline_hook((void *) mng_card_is_ready, libutils::try_proc(
SCIUNIT_INSTANCE, "mng_card_is_ready"));
detour::inline_hook((void *) mng_card_is_removed, libutils::try_proc(
SCIUNIT_INSTANCE, "mng_card_is_removed"));
detour::inline_hook((void *) mng_card_is_valid_card, libutils::try_proc(
SCIUNIT_INSTANCE, "mng_card_is_valid_card"));
detour::inline_hook((void *) mng_card_read_init, libutils::try_proc(
SCIUNIT_INSTANCE, "mng_card_read_init"));
detour::inline_hook((void *) mng_card_sensor_raw, libutils::try_proc(
SCIUNIT_INSTANCE, "mng_card_sensor_raw"));
detour::inline_hook((void *) mng_card_sleep, libutils::try_proc(
SCIUNIT_INSTANCE, "mng_card_sleep"));
detour::inline_hook((void *) sciunit_finalize, libutils::try_proc(
SCIUNIT_INSTANCE, "sciunit_finalize"));
detour::inline_hook((void *) sciunit_get_errorunit, libutils::try_proc(
SCIUNIT_INSTANCE, "sciunit_get_errorunit"));
detour::inline_hook((void *) sciunit_get_stat, libutils::try_proc(
SCIUNIT_INSTANCE, "sciunit_get_stat"));
detour::inline_hook((void *) sciunit_get_version, libutils::try_proc(
SCIUNIT_INSTANCE, "sciunit_get_version"));
detour::inline_hook((void *) sciunit_initialize, libutils::try_proc(
SCIUNIT_INSTANCE, "sciunit_initialize"));
detour::inline_hook((void *) sciunit_is_initialized, libutils::try_proc(
SCIUNIT_INSTANCE, "sciunit_is_initialized"));
detour::inline_hook((void *) sciunit_is_usable, libutils::try_proc(
SCIUNIT_INSTANCE, "sciunit_is_usable"));
detour::inline_hook((void *) sciunit_led_set_color, libutils::try_proc(
SCIUNIT_INSTANCE, "sciunit_led_set_color"));
detour::inline_hook((void *) sciunit_reset, libutils::try_proc(
SCIUNIT_INSTANCE, "sciunit_reset"));
detour::inline_hook((void *) sciunit_update, libutils::try_proc(
SCIUNIT_INSTANCE, "sciunit_update"));
}
void sciunit_detach() {
// TODO
}

4
misc/sciunit.h Normal file
View File

@@ -0,0 +1,4 @@
#pragma once
void sciunit_attach();
void sciunit_detach();

28
misc/sde.cpp Normal file
View File

@@ -0,0 +1,28 @@
#include "sde.h"
#include <thread>
#include "hooks/sleephook.h"
#include "util/logging.h"
void sde_init(std::string sde_path) {
// check trailing slash
if (sde_path.length() > 0 && sde_path.back() == '\\') {
sde_path.pop_back();
}
// get PID
auto pid = GetCurrentProcessId();
// try attach
log_info("sde", "Attaching SDE ({}) to PID {}", sde_path, pid);
std::string cmd = "sde.exe -attach-pid " + to_string(pid);
std::thread t([sde_path, cmd]() {
system((sde_path + "\\" + cmd).c_str());
});
t.detach();
// wait to make sure it went through
Sleep(500);
}

5
misc/sde.h Normal file
View File

@@ -0,0 +1,5 @@
#pragma once
#include <string>
void sde_init(std::string sde_path);

120
misc/vrutil.cpp Normal file
View File

@@ -0,0 +1,120 @@
#include "vrutil.h"
#include "util/logging.h"
namespace vrutil {
static bool INITIALIZED = false;
VRStatus STATUS = VRStatus::Disabled;
vr::IVRSystem *SYSTEM = nullptr;
vr::TrackedDeviceIndex_t INDEX_LEFT = ~0u;
vr::TrackedDeviceIndex_t INDEX_RIGHT = ~0u;
static std::string device_string(vr::TrackedDeviceIndex_t dev,
vr::TrackedDeviceProperty prop) {
uint32_t len = vr::VRSystem()->GetStringTrackedDeviceProperty(
dev, prop, nullptr, 0, nullptr);
if (len == 0) return "";
char *buf = new char[len];
vr::VRSystem()->GetStringTrackedDeviceProperty(
dev, prop, buf, len, nullptr);
std::string res = buf;
delete[] buf;
return res;
}
void init() {
if (INITIALIZED) return;
// initialize OpenVR
vr::EVRInitError error = vr::VRInitError_None;
SYSTEM = vr::VR_Init(&error, vr::VRApplication_Other);
if (error != vr::VRInitError_None || !SYSTEM) {
SYSTEM = nullptr;
STATUS = VRStatus::Error;
log_warning("vrutil", "unable to initialize: {}",
vr::VR_GetVRInitErrorAsEnglishDescription(error));
return;
}
// get information
auto driver = device_string(vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_TrackingSystemName_String);
auto serial = device_string(vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_SerialNumber_String);
log_info("vrutil", "initialized: {} - {}", driver, serial);
// success
INITIALIZED = true;
STATUS = VRStatus::Running;
scan(true);
}
void shutdown() {
if (STATUS == VRStatus::Running) {
vr::VR_Shutdown();
}
SYSTEM = nullptr;
STATUS = VRStatus::Disabled;
}
void scan(bool log) {
if (STATUS != VRStatus::Running) return;
for (vr::TrackedDeviceIndex_t index = 0; index < vr::k_unMaxTrackedDeviceCount; ++index) {
auto dclass = SYSTEM->GetTrackedDeviceClass(index);
switch (dclass) {
case vr::TrackedDeviceClass_Controller: {
auto role = SYSTEM->GetControllerRoleForTrackedDeviceIndex(index);
switch (role) {
case vr::TrackedControllerRole_Invalid:
if (log) log_warning("vrutil", "invalid controller on index {}", index);
break;
case vr::TrackedControllerRole_LeftHand:
if (log) log_info("vrutil", "detected left controller on index {}", index);
INDEX_LEFT = index;
break;
case vr::TrackedControllerRole_RightHand:
if (log) log_info("vrutil", "detected right controller on index {}", index);
INDEX_RIGHT = index;
break;
default:
break;
}
break;
}
case vr::TrackedDeviceClass_GenericTracker: {
if (log) log_info("vrutil", "detected tracker on index {}", index);
break;
}
default:
break;
}
}
}
bool get_hmd_pose(vr::TrackedDevicePose_t *pose) {
if (STATUS != VRStatus::Running) {
*pose = vr::TrackedDevicePose_t {};
pose->bPoseIsValid = false;
return false;
}
SYSTEM->GetDeviceToAbsoluteTrackingPose(
vr::TrackingUniverseStanding,
0,
pose, 1);
return true;
}
bool get_con_pose(vr::TrackedDeviceIndex_t index,
vr::TrackedDevicePose_t *pose,
vr::VRControllerState_t *state) {
if (STATUS != VRStatus::Running || index == ~0u) {
*state = vr::VRControllerState_t {};
*pose = vr::TrackedDevicePose_t {};
pose->bPoseIsValid = false;
return false;
}
SYSTEM->GetControllerStateWithPose(
vr::TrackingUniverseStanding,
index, state, sizeof(vr::VRControllerState_t), pose);
return true;
}
}

139
misc/vrutil.h Normal file
View File

@@ -0,0 +1,139 @@
#include <cmath>
#include "external/openvr/headers/openvr.h"
#include "external/linalg.h"
namespace vrutil {
enum class VRStatus {
Disabled, Error, Running,
};
extern VRStatus STATUS;
extern vr::IVRSystem *SYSTEM;
extern vr::TrackedDeviceIndex_t INDEX_LEFT;
extern vr::TrackedDeviceIndex_t INDEX_RIGHT;
void init();
void shutdown();
void scan(bool log = false);
bool get_hmd_pose(vr::TrackedDevicePose_t *pose);
bool get_con_pose(vr::TrackedDeviceIndex_t index,
vr::TrackedDevicePose_t *pose,
vr::VRControllerState_t *state);
/*
* Util
*/
inline linalg::aliases::float3 get_translation(vr::HmdMatrix34_t &m) {
return linalg::aliases::float3 {
m.m[0][3],
m.m[1][3],
m.m[2][3],
};
}
inline linalg::aliases::float3 get_direction_back(vr::HmdMatrix34_t &m) {
return linalg::normalize(linalg::aliases::float3 {
m.m[0][2],
m.m[1][2],
m.m[2][2],
});
}
inline linalg::aliases::float3 get_direction_front(vr::HmdMatrix34_t &m) {
return -get_direction_back(m);
}
inline linalg::aliases::float3 get_direction_up(vr::HmdMatrix34_t &m) {
return linalg::normalize(linalg::aliases::float3 {
m.m[0][1],
m.m[1][1],
m.m[2][1],
});
}
inline linalg::aliases::float3 get_direction_down(vr::HmdMatrix34_t &m) {
return -get_direction_up(m);
}
inline linalg::aliases::float3 get_direction_right(vr::HmdMatrix34_t &m) {
return linalg::normalize(linalg::aliases::float3 {
m.m[0][0],
m.m[1][0],
m.m[2][0],
});
}
inline linalg::aliases::float3 get_direction_left(vr::HmdMatrix34_t &m) {
return -get_direction_right(m);
}
template<class T>
inline linalg::aliases::float4 get_rotation(T &m) {
linalg::aliases::float4 q;
q.w = sqrtf(fmaxf(0, 1 + m[0][0] + m[1][1] + m[2][2])) * 0.5f;
q.x = sqrtf(fmaxf(0, 1 + m[0][0] - m[1][1] - m[2][2])) * 0.5f;
q.y = sqrtf(fmaxf(0, 1 - m[0][0] + m[1][1] - m[2][2])) * 0.5f;
q.z = sqrtf(fmaxf(0, 1 - m[0][0] - m[1][1] + m[2][2])) * 0.5f;
q.x = copysignf(q.x, m[2][1] - m[1][2]);
q.y = copysignf(q.y, m[0][2] - m[2][0]);
q.z = copysignf(q.z, m[1][0] - m[0][1]);
return q;
}
inline linalg::aliases::float4 get_rotation(
float pitch, float yaw, float roll) {
return linalg::aliases::float4 {
sinf(roll/2) * cosf(pitch/2) * cosf(yaw/2) - cosf(roll/2) * sinf(pitch/2) * sinf(yaw/2),
cosf(roll/2) * sinf(pitch/2) * cosf(yaw/2) + sinf(roll/2) * cosf(pitch/2) * sinf(yaw/2),
cosf(roll/2) * cosf(pitch/2) * sinf(yaw/2) - sinf(roll/2) * sinf(pitch/2) * cosf(yaw/2),
cosf(roll/2) * cosf(pitch/2) * cosf(yaw/2) + sinf(roll/2) * sinf(pitch/2) * sinf(yaw/2),
};
}
inline linalg::aliases::float3 orthogonal(
linalg::aliases::float3 v) {
auto x = std::fabs(v.x);
auto y = std::fabs(v.y);
auto z = std::fabs(v.z);
linalg::aliases::float3 o;
if (x < y) {
if (x < z) o = linalg::aliases::float3(1, 0, 0);
else o = linalg::aliases::float3(0, 0, 1);
} else {
if (y < z) o = linalg::aliases::float3(0, 1, 0);
else linalg::aliases::float3(0, 0, 1);
}
return linalg::cross(v, o);
}
inline linalg::aliases::float4 get_rotation(
linalg::aliases::float3 v1,
linalg::aliases::float3 v2) {
auto dot = linalg::dot(v1, v2);
auto scl = sqrtf(linalg::length2(v1) * linalg::length2(v2));
if (dot / scl == -1) {
auto v = linalg::normalize(orthogonal(v1));
return linalg::aliases::float4 {
v.x, v.y, v.z, 0,
};
}
auto cross = linalg::cross(v1, v2);
return linalg::normalize(linalg::aliases::float4 {
cross.x, cross.y, cross.z, dot + scl,
});
}
inline linalg::aliases::float3 intersect_point(
linalg::aliases::float3 ray_dir,
linalg::aliases::float3 ray_point,
linalg::aliases::float3 plane_normal,
linalg::aliases::float3 plane_point) {
auto delta = ray_point - plane_point;
auto delta_dot = linalg::dot(delta, plane_normal);
auto ray_dot = linalg::dot(ray_dir, plane_normal);
return ray_point - ray_dir * (delta_dot / ray_dot);
}
}

447
misc/wintouchemu.cpp Normal file
View File

@@ -0,0 +1,447 @@
// enable touch functions - set version to windows 7
// mingw otherwise doesn't load touch stuff
#define _WIN32_WINNT 0x0601
#include "wintouchemu.h"
#include <algorithm>
#include <optional>
#include "cfg/screen_resize.h"
#include "games/iidx/iidx.h"
#include "hooks/graphics/graphics.h"
#include "overlay/overlay.h"
#include "overlay/windows/generic_sub.h"
#include "touch/touch.h"
#include "util/detour.h"
#include "util/logging.h"
#include "util/time.h"
#include "util/utils.h"
#include "avs/game.h"
namespace wintouchemu {
typedef struct {
POINT pos;
bool last_button_pressed;
DWORD touch_event;
} mouse_state_t;
// settings
bool FORCE = false;
bool INJECT_MOUSE_AS_WM_TOUCH = false;
bool LOG_FPS = false;
static inline bool is_emu_enabled() {
return FORCE || !is_touch_available() || GRAPHICS_SHOW_CURSOR;
}
static decltype(GetSystemMetrics) *GetSystemMetrics_orig = nullptr;
static decltype(RegisterTouchWindow) *RegisterTouchWindow_orig = nullptr;
static int WINAPI GetSystemMetrics_hook(int nIndex) {
/*
* fake touch screen
* the game requires 0x01 and 0x02 flags to be set
* 0x40 and 0x80 are set for completeness
*/
if (nIndex == 94)
return 0x01 | 0x02 | 0x40 | 0x80;
// call original
if (GetSystemMetrics_orig != nullptr) {
return GetSystemMetrics_orig(nIndex);
}
// return error
return 0;
}
static BOOL WINAPI RegisterTouchWindow_hook(HWND hwnd, ULONG ulFlags) {
// don't register it if the emu is enabled
if (is_emu_enabled()) {
return true;
}
// call default
return RegisterTouchWindow_orig(hwnd, ulFlags);
}
// state
BOOL (WINAPI *GetTouchInputInfo_orig)(HANDLE, UINT, PTOUCHINPUT, int);
bool USE_MOUSE = false;
std::vector<TouchEvent> TOUCH_EVENTS;
std::vector<TouchPoint> TOUCH_POINTS;
HMODULE HOOKED_MODULE = nullptr;
std::string WINDOW_TITLE_START = "";
std::optional<std::string> WINDOW_TITLE_END = std::nullopt;
bool INITIALIZED = false;
mouse_state_t mouse_state;
void hook(const char *window_title, HMODULE module) {
// hooks
auto system_metrics_hook = detour::iat_try(
"GetSystemMetrics", GetSystemMetrics_hook, module);
auto register_touch_window_hook = detour::iat_try(
"RegisterTouchWindow", RegisterTouchWindow_hook, module);
// don't hook twice
if (GetSystemMetrics_orig == nullptr) {
GetSystemMetrics_orig = system_metrics_hook;
}
if (RegisterTouchWindow_orig == nullptr) {
RegisterTouchWindow_orig = register_touch_window_hook;
}
// set module and title
HOOKED_MODULE = module;
WINDOW_TITLE_START = window_title;
INITIALIZED = true;
}
void hook_title_ends(const char *window_title_start, const char *window_title_end, HMODULE module) {
hook(window_title_start, module);
WINDOW_TITLE_END = window_title_end;
}
static BOOL WINAPI GetTouchInputInfoHook(HANDLE hTouchInput, UINT cInputs, PTOUCHINPUT pInputs, int cbSize) {
// check if original should be called
if (hTouchInput != GetTouchInputInfoHook) {
return GetTouchInputInfo_orig(hTouchInput, cInputs, pInputs, cbSize);
}
// set touch inputs
bool result = false;
bool mouse_used = false;
for (UINT input = 0; input < cInputs; input++) {
auto *touch_input = &pInputs[input];
// clear touch input
touch_input->x = 0;
touch_input->y = 0;
touch_input->hSource = nullptr;
touch_input->dwID = 0;
touch_input->dwFlags = 0;
touch_input->dwMask = 0;
touch_input->dwTime = 0;
touch_input->dwExtraInfo = 0;
touch_input->cxContact = 0;
touch_input->cyContact = 0;
// get touch event
TouchEvent *touch_event = nullptr;
if (TOUCH_EVENTS.size() > input) {
touch_event = &TOUCH_EVENTS.at(input);
}
// check touch point
if (touch_event) {
// set touch point
result = true;
auto x = touch_event->x;
auto y = touch_event->y;
auto valid = true;
// log_misc("wintouchemu", "touch event ({}, {})", to_string(x), to_string(y));
if (GRAPHICS_IIDX_WSUB) {
// touch was received on subscreen window.
RECT clientRect {};
GetClientRect(TDJ_SUBSCREEN_WINDOW, &clientRect);
x = (float) x / clientRect.right * SPICETOUCH_TOUCH_WIDTH + SPICETOUCH_TOUCH_X;
y = (float) y / clientRect.bottom * SPICETOUCH_TOUCH_HEIGHT + SPICETOUCH_TOUCH_Y;
} else if (overlay::OVERLAY) {
// touch was received on global coords
valid = overlay::OVERLAY->transform_touch_point(&x, &y);
} else {
valid = false;
}
touch_input->x = x * 100;
touch_input->y = y * 100;
touch_input->hSource = hTouchInput;
touch_input->dwID = touch_event->id;
touch_input->dwFlags = 0;
switch (touch_event->type) {
case TOUCH_DOWN:
if (valid) {
touch_input->dwFlags |= TOUCHEVENTF_DOWN;
}
break;
case TOUCH_MOVE:
if (valid) {
touch_input->dwFlags |= TOUCHEVENTF_MOVE;
}
break;
case TOUCH_UP:
// don't check valid so that this touch ID can be released
touch_input->dwFlags |= TOUCHEVENTF_UP;
break;
}
touch_input->dwMask = 0;
touch_input->dwTime = 0;
touch_input->dwExtraInfo = 0;
touch_input->cxContact = 0;
touch_input->cyContact = 0;
} else if (USE_MOUSE && !mouse_used) {
// disable further mouse inputs this call
mouse_used = true;
if (mouse_state.touch_event) {
result = true;
touch_input->x = mouse_state.pos.x;
touch_input->y = mouse_state.pos.y;
if (GRAPHICS_WINDOWED) {
touch_input->x -= SPICETOUCH_TOUCH_X;
touch_input->y -= SPICETOUCH_TOUCH_Y;
}
// log_misc("wintouchemu", "mouse state ({}, {})", to_string(touch_input->x), to_string(touch_input->y));
auto valid = true;
if (overlay::OVERLAY) {
valid = overlay::OVERLAY->transform_touch_point(
&touch_input->x, &touch_input->y);
}
// touch inputs require 100x precision per pixel
touch_input->x *= 100;
touch_input->y *= 100;
touch_input->hSource = hTouchInput;
touch_input->dwID = 0;
touch_input->dwFlags = 0;
switch (mouse_state.touch_event) {
case TOUCHEVENTF_DOWN:
if (valid) {
touch_input->dwFlags |= TOUCHEVENTF_DOWN;
}
break;
case TOUCHEVENTF_MOVE:
if (valid) {
touch_input->dwFlags |= TOUCHEVENTF_MOVE;
}
break;
case TOUCHEVENTF_UP:
// don't check valid so that this touch ID can be released
touch_input->dwFlags |= TOUCHEVENTF_UP;
break;
}
touch_input->dwMask = 0;
touch_input->dwTime = 0;
touch_input->dwExtraInfo = 0;
touch_input->cxContact = 0;
touch_input->cyContact = 0;
// reset it since the event was consumed & propagated as touch
mouse_state.touch_event = 0;
}
} else if (!GRAPHICS_IIDX_WSUB) {
/*
* For some reason, Nostalgia won't show an active touch point unless a move event
* triggers in the same frame. To work around this, we just supply a fake move
* event if we didn't update the same pointer ID in the same call.
*/
// find touch point which has no associated input event
TouchPoint *touch_point = nullptr;
for (auto &tp : TOUCH_POINTS) {
bool found = false;
for (UINT i = 0; i < cInputs; i++) {
if (input > 0 && pInputs[i].dwID == tp.id) {
found = true;
break;
}
}
if (!found) {
touch_point = &tp;
break;
}
}
// check if unused touch point was found
if (touch_point) {
// set touch point
result = true;
touch_input->x = touch_point->x * 100;
touch_input->y = touch_point->y * 100;
touch_input->hSource = hTouchInput;
touch_input->dwID = touch_point->id;
touch_input->dwFlags = 0;
touch_input->dwFlags |= TOUCHEVENTF_MOVE;
touch_input->dwMask = 0;
touch_input->dwTime = 0;
touch_input->dwExtraInfo = 0;
touch_input->cxContact = 0;
touch_input->cyContact = 0;
}
}
}
// return success
return result;
}
void update() {
// check if initialized
if (!INITIALIZED) {
return;
}
// no need for hooks if touch is available
if (!is_emu_enabled()) {
return;
}
// get window handle
static HWND hWnd = nullptr;
if (hWnd == nullptr) {
// start with the active foreground window
hWnd = GetForegroundWindow();
auto title = get_window_title(hWnd);
// if the foreground window does not match the window title start, find a window
// that does
if (!string_begins_with(title, WINDOW_TITLE_START)) {
hWnd = FindWindowBeginsWith(WINDOW_TITLE_START);
title = get_window_title(hWnd);
}
// if a window title end is set, check to see if it matches
if (WINDOW_TITLE_END.has_value() && !string_ends_with(title.c_str(), WINDOW_TITLE_END.value().c_str())) {
hWnd = nullptr;
title = "";
for (auto &window : find_windows_beginning_with(WINDOW_TITLE_START)) {
auto check_title = get_window_title(window);
if (string_ends_with(check_title.c_str(), WINDOW_TITLE_END.value().c_str())) {
hWnd = std::move(window);
title = std::move(check_title);
break;
}
}
}
// check window
if (hWnd == nullptr) {
return;
}
// check if windowed
if (GRAPHICS_WINDOWED) {
if (GRAPHICS_IIDX_WSUB) {
// no handling is needed here
// graphics::MoveWindow_hook will attach hook to windowed subscreen
log_info("wintouchemu", "attach touch hook to windowed subscreen for TDJ");
USE_MOUSE = false;
} else if (avs::game::is_model("LDJ") && !GENERIC_SUB_WINDOW_FULLSIZE) {
// overlay subscreen in IIDX
// use mouse position as ImGui overlay will block the touch window
log_info("wintouchemu", "use mouse cursor API for overlay subscreen");
USE_MOUSE = true;
} else {
// create touch window - create overlay if not yet existing at this point
log_info("wintouchemu", "create touch window relative to main game window");
touch_create_wnd(hWnd, overlay::ENABLED && !overlay::OVERLAY);
USE_MOUSE = false;
}
} else if (INJECT_MOUSE_AS_WM_TOUCH) {
log_info(
"wintouchemu",
"using raw mouse cursor API in full screen and injecting them as WM_TOUCH events");
USE_MOUSE = true;
} else {
log_info("wintouchemu", "activating DirectX hooks");
// mouse position based input only
touch_attach_dx_hook();
USE_MOUSE = false;
}
// hooks
auto GetTouchInputInfo_orig_new = detour::iat_try(
"GetTouchInputInfo", GetTouchInputInfoHook, HOOKED_MODULE);
if (GetTouchInputInfo_orig == nullptr) {
GetTouchInputInfo_orig = GetTouchInputInfo_orig_new;
}
}
// update touch events
if (hWnd != nullptr) {
// get touch events
TOUCH_EVENTS.clear();
touch_get_events(TOUCH_EVENTS);
// get touch points
TOUCH_POINTS.clear();
touch_get_points(TOUCH_POINTS);
// get event count
auto event_count = TOUCH_EVENTS.size();
// for the fake move events
event_count += MAX(0, (int) (TOUCH_POINTS.size() - TOUCH_EVENTS.size()));
// check if new events are available
if (event_count > 0) {
// send fake event to make the game update it's touch inputs
auto wndProc = (WNDPROC) GetWindowLongPtr(hWnd, GWLP_WNDPROC);
wndProc(hWnd, WM_TOUCH, MAKEWORD(event_count, 0), (LPARAM) GetTouchInputInfoHook);
}
// update frame logging
if (LOG_FPS) {
static int log_frames = 0;
static uint64_t log_time = 0;
log_frames++;
if (log_time < get_system_seconds()) {
if (log_time > 0) {
log_info("wintouchemu", "polling at {} touch frames per second", log_frames);
}
log_frames = 0;
log_time = get_system_seconds();
}
}
}
// send separate WM_TOUCH event for mouse
// this must be separate from actual touch events because some games will ignore the return
// value from GetTouchInputInfo or fail to read dwFlags for valid events, so it's not OK to
// send empty events when the mouse button is not clicked/released
if (hWnd != nullptr && USE_MOUSE) {
bool button_pressed = ((GetKeyState(VK_LBUTTON) & 0x100) != 0);
// figure out what kind of touch event to simulate
if (button_pressed && !mouse_state.last_button_pressed) {
mouse_state.touch_event = TOUCHEVENTF_DOWN;
} else if (button_pressed && mouse_state.last_button_pressed) {
mouse_state.touch_event = TOUCHEVENTF_MOVE;
} else if (!button_pressed && mouse_state.last_button_pressed) {
mouse_state.touch_event = TOUCHEVENTF_UP;
}
mouse_state.last_button_pressed = button_pressed;
if (mouse_state.touch_event) {
GetCursorPos(&mouse_state.pos);
// send fake event to make the game update it's touch inputs
auto wndProc = (WNDPROC) GetWindowLongPtr(hWnd, GWLP_WNDPROC);
wndProc(hWnd, WM_TOUCH, MAKEWORD(1, 0), (LPARAM) GetTouchInputInfoHook);
}
}
}
}

18
misc/wintouchemu.h Normal file
View File

@@ -0,0 +1,18 @@
#pragma once
#include <windows.h>
namespace wintouchemu {
// settings
extern bool FORCE;
extern bool INJECT_MOUSE_AS_WM_TOUCH;
extern bool LOG_FPS;
void hook(const char *window_title, HMODULE module = nullptr);
void hook_title_ends(
const char *window_title_start,
const char *window_title_ends,
HMODULE module = nullptr);
void update();
}