Initial re-upload of spice2x-24-08-24
This commit is contained in:
294
misc/bt5api.cpp
Normal file
294
misc/bt5api.cpp
Normal 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
36
misc/bt5api.h
Normal 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
185
misc/clipboard.cpp
Normal 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
9
misc/clipboard.h
Normal 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
740
misc/device.cpp
Normal 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
5
misc/device.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
void spicedevice_attach();
|
||||
|
||||
void spicedevice_detach();
|
||||
570
misc/eamuse.cpp
Normal file
570
misc/eamuse.cpp
Normal 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
78
misc/eamuse.h
Normal 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
1329
misc/extdev.cpp
Normal file
File diff suppressed because it is too large
Load Diff
4
misc/extdev.h
Normal file
4
misc/extdev.h
Normal file
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
void extdev_attach();
|
||||
void extdev_detach();
|
||||
307
misc/sciunit.cpp
Normal file
307
misc/sciunit.cpp
Normal 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
4
misc/sciunit.h
Normal file
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
void sciunit_attach();
|
||||
void sciunit_detach();
|
||||
28
misc/sde.cpp
Normal file
28
misc/sde.cpp
Normal 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
5
misc/sde.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
void sde_init(std::string sde_path);
|
||||
120
misc/vrutil.cpp
Normal file
120
misc/vrutil.cpp
Normal 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
139
misc/vrutil.h
Normal 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
447
misc/wintouchemu.cpp
Normal 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
18
misc/wintouchemu.h
Normal 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();
|
||||
}
|
||||
Reference in New Issue
Block a user