mai2: wip hook

This commit is contained in:
Hay1tsme 2023-11-30 02:29:27 -05:00
parent 4dcf01f643
commit 9d8a38bbf7
30 changed files with 1646 additions and 5 deletions

15
.vscode/settings.json vendored
View File

@ -1,4 +1,19 @@
{
"editor.formatOnSave": false,
"mesonbuild.configureOnOpen": false,
"files.associations": {
"string.h": "c",
"stdbool.h": "c",
"windows.h": "c",
"dprintf.h": "c",
"touch.h": "c",
"mai2-dll.h": "c",
"led.h": "c",
"path.h": "c",
"reg.h": "c",
"platform.h": "c",
"procaddr.h": "c",
"table.h": "c",
"serial.h": "c"
},
}

View File

@ -104,6 +104,21 @@ $(BUILD_DIR_ZIP)/mu3.zip:
$(V)strip $(BUILD_DIR_ZIP)/mu3/*.{exe,dll}
$(V)cd $(BUILD_DIR_ZIP)/mu3 ; zip -r ../mu3.zip *
$(BUILD_DIR_ZIP)/mai2.zip:
$(V)echo ... $@
$(V)mkdir -p $(BUILD_DIR_ZIP)/mai2
$(V)mkdir -p $(BUILD_DIR_ZIP)/mai2/DEVICE
$(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \
$(BUILD_DIR_64)/mai2hook/mai2hook.dll \
$(DIST_DIR)/mai2/segatools.ini \
$(DIST_DIR)/mai2/start.bat \
$(BUILD_DIR_ZIP)/mai2
$(V)cp pki/billing.pub \
pki/ca.crt \
$(BUILD_DIR_ZIP)/mai2/DEVICE
$(V)strip $(BUILD_DIR_ZIP)/mai2/*.{exe,dll}
$(V)cd $(BUILD_DIR_ZIP)/mai2 ; zip -r ../mai2.zip *
$(BUILD_DIR_ZIP)/doc.zip: \
$(DOC_DIR)/config \
$(DOC_DIR)/chunihook.md \
@ -121,6 +136,7 @@ $(BUILD_DIR_ZIP)/segatools.zip: \
$(BUILD_DIR_ZIP)/idz.zip \
$(BUILD_DIR_ZIP)/mercury.zip \
$(BUILD_DIR_ZIP)/mu3.zip \
$(BUILD_DIR_ZIP)/mai2.zip \
CHANGELOG.md \
README.md \

44
dist/mai2/segatools.ini vendored Normal file
View File

@ -0,0 +1,44 @@
[vfs]
; Insert the path to the game AMFS directory here (contains ICF1 and ICF2)
amfs=amfs
; Create an empty directory somewhere and insert the path here.
; This directory may be shared between multiple SEGA games.
; NOTE: This has nothing to do with Windows %APPDATA%.
appdata=appdata
option=option
[dns]
; Insert the hostname or IP address of the server you wish to use here.
; Note that 127.0.0.1, localhost etc are specifically rejected.
default=127.0.0.1
[ds]
; Region code on the emulated AMEX board DS EEPROM.
; 1: Japan
; 4: Export (some UI elements in English)
;
; NOTE: Changing this setting causes a factory reset.
region=1
[netenv]
; Simulate an ideal LAN environment. This may interfere with head-to-head play.
; SEGA games are somewhat picky about their LAN environment, so leaving this
; setting enabled is recommended.
enable=1
[keychip]
; The /24 LAN subnet that the emulated keychip will tell the game to expect.
; If you disable netenv then you must set this to your LAN's IP subnet, and
; that subnet must start with 192.168.
subnet=192.168.172.0
[gfx]
enable=1
[io4]
; Delete
test=0x2E
; End
service=0x23
; Insert
coin=0x2D

11
dist/mai2/start.bat vendored Normal file
View File

@ -0,0 +1,11 @@
@echo off
pushd %~dp0
taskkill /f /im amdaemon.exe > nul 2>&1
start inject -d -k mai2hook.dll amdaemon.exe -f -c config_client.json config_common.json config_server.json
inject.exe -d -k mai2hook.dll Sinmai.exe -screen-fullscreen 0 -screen-width 2160 -screen-height 1920
taskkill /f /im amdaemon.exe > nul 2>&1
echo Game processes have terminated

View File

@ -76,8 +76,13 @@ static void proc_addr_hook_init(void)
InitializeCriticalSection(&proc_addr_hook_lock);
proc_addr_insert_hooks(NULL);
}
void proc_addr_insert_hooks(HMODULE target)
{
hook_table_apply(
NULL,
target,
"kernel32.dll",
win32_hooks,
_countof(win32_hooks));

View File

@ -16,3 +16,5 @@ HRESULT proc_addr_table_push(
struct hook_symbol *syms,
size_t nsyms
);
void proc_addr_insert_hooks(HMODULE target);

View File

@ -7,6 +7,7 @@
#include "hook/table.h"
#include "hooklib/reg.h"
#include "hooklib/procaddr.h"
#include "util/dprintf.h"
#include "util/str.h"
@ -99,6 +100,29 @@ static LSTATUS WINAPI hook_RegGetValueW(
uint32_t *numData
);
static LSTATUS WINAPI hook_RegQueryInfoKeyW(
HKEY hKey,
LPWSTR lpClass,
LPDWORD lpcchClass,
LPDWORD lpReserved,
LPDWORD lpcSubKeys,
LPDWORD lpcbMaxSubKeyLen,
LPDWORD lpcbMaxClassLen,
LPDWORD lpcValues,
LPDWORD lpcbMaxValueNameLen,
LPDWORD lpcbMaxValueLen,
LPDWORD lpcbSecurityDescriptor,
PFILETIME lpftLastWriteTime);
static LSTATUS WINAPI hook_RegEnumValueW(
HKEY hkey,
DWORD dwIndex,
LPWSTR lpValueName,
LPDWORD lpcchValueName,
LPDWORD lpReserved,
LPDWORD lpType,
LPBYTE lpData,
LPDWORD lpcbData);
/* Link pointers */
static LSTATUS (WINAPI *next_RegOpenKeyExW)(
@ -155,6 +179,30 @@ static LSTATUS (WINAPI *next_RegGetValueW)(
uint32_t *numData
);
static LSTATUS (WINAPI *next_RegQueryInfoKeyW)(
HKEY hKey,
LPWSTR lpClass,
LPDWORD lpcchClass,
LPDWORD lpReserved,
LPDWORD lpcSubKeys,
LPDWORD lpcbMaxSubKeyLen,
LPDWORD lpcbMaxClassLen,
LPDWORD lpcValues,
LPDWORD lpcbMaxValueNameLen,
LPDWORD lpcbMaxValueLen,
LPDWORD lpcbSecurityDescriptor,
PFILETIME lpftLastWriteTime);
static LSTATUS (WINAPI *next_RegEnumValueW)(
HKEY hkey,
DWORD dwIndex,
LPWSTR lpValueName,
LPDWORD lpcchValueName,
LPDWORD lpReserved,
LPDWORD lpType,
LPBYTE lpData,
LPDWORD lpcbData);
static const struct hook_symbol reg_hook_syms[] = {
{
.name = "RegOpenKeyExW",
@ -184,6 +232,14 @@ static const struct hook_symbol reg_hook_syms[] = {
.name = "RegGetValueW",
.patch = hook_RegGetValueW,
.link = (void **) &next_RegGetValueW,
}, {
.name = "RegQueryInfoKeyW",
.patch = hook_RegQueryInfoKeyW,
.link = (void **) &next_RegQueryInfoKeyW,
}, {
.name = "RegEnumValueW",
.patch = hook_RegEnumValueW,
.link = (void **) &next_RegEnumValueW,
}
};
@ -254,11 +310,23 @@ static void reg_hook_init(void)
InitializeCriticalSection(&reg_hook_lock);
dprintf("Reg hook init\n");
reg_hook_insert_hooks(NULL);
proc_addr_table_push(
"ADVAPI32.dll",
(struct hook_symbol *) reg_hook_syms,
_countof(reg_hook_syms));
}
void reg_hook_insert_hooks(HMODULE target)
{
hook_table_apply(
NULL,
target,
"advapi32.dll",
reg_hook_syms,
_countof(reg_hook_syms));
}
static LRESULT reg_hook_propagate_hr(HRESULT hr)
@ -331,6 +399,7 @@ static LSTATUS reg_hook_open_locked(
/* Assume reg keys are referenced from a root key and not from some
intermediary key */
key = &reg_hook_keys[i];
//dprintf("Reg: %ls vs %ls\n", name, key->name);
if (key->root == parent && wstr_ieq(key->name, name)) {
break;
@ -821,6 +890,99 @@ static LSTATUS WINAPI hook_RegGetValueW(
return err;
}
static LSTATUS WINAPI hook_RegQueryInfoKeyW(
HKEY hKey,
LPWSTR lpClass,
LPDWORD lpcchClass,
LPDWORD lpReserved,
LPDWORD lpcSubKeys,
LPDWORD lpcbMaxSubKeyLen,
LPDWORD lpcbMaxClassLen,
LPDWORD lpcValues,
LPDWORD lpcbMaxValueNameLen,
LPDWORD lpcbMaxValueLen,
LPDWORD lpcbSecurityDescriptor,
PFILETIME lpftLastWriteTime)
{
struct reg_hook_key *key;
LSTATUS err;
EnterCriticalSection(&reg_hook_lock);
key = reg_hook_match_key_locked(hKey);
/* Check if this is a virtualized registry key */
if (key == NULL) {
LeaveCriticalSection(&reg_hook_lock);
return next_RegQueryInfoKeyW(
hKey,
lpClass,
lpcchClass,
lpReserved,
lpcSubKeys,
lpcbMaxSubKeyLen,
lpcbMaxClassLen,
lpcValues,
lpcbMaxValueNameLen,
lpcbMaxValueLen,
lpcbSecurityDescriptor,
lpftLastWriteTime);
}
// This is the only one I've seen even be changed, so it's all I'm doing
// until I see otherwise.
*lpcValues = key->nvals;
LeaveCriticalSection(&reg_hook_lock);
return ERROR_SUCCESS;
}
static LSTATUS WINAPI hook_RegEnumValueW(
HKEY hkey,
DWORD dwIndex,
LPWSTR lpValueName,
LPDWORD lpcchValueName,
LPDWORD lpReserved,
LPDWORD lpType,
LPBYTE lpData,
LPDWORD lpcbData)
{
struct reg_hook_key *key;
HRESULT hr;
LSTATUS err;
EnterCriticalSection(&reg_hook_lock);
key = reg_hook_match_key_locked(hkey);
/* Check if this is a virtualized registry key */
if (key == NULL) {
LeaveCriticalSection(&reg_hook_lock);
return next_RegEnumValueW(
hkey,
dwIndex,
lpValueName,
lpcchValueName,
lpReserved,
lpType,
lpData,
lpcbData);
}
if (dwIndex >= key->nvals) {
LeaveCriticalSection(&reg_hook_lock);
return ERROR_NO_MORE_ITEMS; // Pretty sure this is what it actually returns here?
}
wcscpy_s(lpValueName, *lpcchValueName, key->vals[dwIndex].name);
*lpcchValueName = wcslen(key->vals[dwIndex].name);
LeaveCriticalSection(&reg_hook_lock);
return ERROR_SUCCESS;
}
HRESULT reg_hook_read_bin(
void *bytes,
uint32_t *nbytes,

View File

@ -12,6 +12,8 @@ struct reg_hook_val {
uint32_t type;
};
void reg_hook_insert_hooks(HMODULE target);
HRESULT reg_hook_push_key(
HKEY root,
const wchar_t *name,

66
mai2hook/config.c Normal file
View File

@ -0,0 +1,66 @@
#include <assert.h>
#include <stddef.h>
#include "board/config.h"
#include "gfxhook/config.h"
#include "hooklib/config.h"
#include "hooklib/dvd.h"
#include "mai2hook/config.h"
#include "platform/config.h"
void mai2_dll_config_load(
struct mai2_dll_config *cfg,
const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
GetPrivateProfileStringW(
L"mai2io",
L"path",
L"",
cfg->path,
_countof(cfg->path),
filename);
}
void touch_config_load(
struct touch_config *cfg,
const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
cfg->enable = GetPrivateProfileIntW(L"touch", L"enable", 1, filename);
}
void led_config_load(
struct led_config *cfg,
const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
cfg->enable = GetPrivateProfileIntW(L"led", L"enable", 1, filename);
}
void mai2_hook_config_load(
struct mai2_hook_config *cfg,
const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
platform_config_load(&cfg->platform, filename);
aime_config_load(&cfg->aime, filename);
dvd_config_load(&cfg->dvd, filename);
io4_config_load(&cfg->io4, filename);
gfx_config_load(&cfg->gfx, filename);
mai2_dll_config_load(&cfg->dll, filename);
touch_config_load(&cfg->touch, filename);
led_config_load(&cfg->led, filename);
}

34
mai2hook/config.h Normal file
View File

@ -0,0 +1,34 @@
#pragma once
#include <stddef.h>
#include "board/config.h"
#include "gfxhook/gfx.h"
#include "hooklib/dvd.h"
#include "mai2hook/mai2-dll.h"
#include "mai2hook/touch.h"
#include "mai2hook/led.h"
#include "platform/config.h"
struct mai2_hook_config {
struct platform_config platform;
struct aime_config aime;
struct dvd_config dvd;
struct io4_config io4;
struct gfx_config gfx;
struct mai2_dll_config dll;
struct touch_config touch;
struct led_config led;
};
void mai2_dll_config_load(
struct mai2_dll_config *cfg,
const wchar_t *filename);
void mai2_hook_config_load(
struct mai2_hook_config *cfg,
const wchar_t *filename);

157
mai2hook/dllmain.c Normal file
View File

@ -0,0 +1,157 @@
#include <windows.h>
#include <stdlib.h>
#include "board/io4.h"
#include "board/sg-reader.h"
#include "board/vfd.h"
#include "gfxhook/d3d9.h"
#include "gfxhook/d3d11.h"
#include "gfxhook/dxgi.h"
#include "gfxhook/gfx.h"
#include "hook/process.h"
#include "hook/table.h"
#include "hook/iohook.h"
#include "hooklib/dvd.h"
#include "hooklib/serial.h"
#include "hooklib/spike.h"
#include "hooklib/path.h"
#include "hooklib/reg.h"
#include "hooklib/procaddr.h"
#include "mai2hook/config.h"
#include "mai2hook/io4.h"
#include "mai2hook/mai2-dll.h"
#include "mai2hook/unity.h"
#include "mai2hook/touch.h"
#include "mai2hook/led.h"
#include "platform/platform.h"
#include "util/dprintf.h"
static HMODULE mai2_hook_mod;
static process_entry_t mai2_startup;
static struct mai2_hook_config mai2_hook_cfg;
static DWORD CALLBACK mai2_pre_startup(void)
{
HRESULT hr;
dprintf("--- Begin mai2_pre_startup ---\n");
/* Load config */
mai2_hook_config_load(&mai2_hook_cfg, L".\\segatools.ini");
HMODULE mono = LoadLibraryW(L".\\MonoBleedingEdge\\EmbedRuntime\\mono-2.0-bdwgc.dll"); // load this so we can hook it maybe
if (mono == NULL) {
dprintf("Failed to load mono-2.0-bdwgc.dll\n");
} else {
serial_hook_apply_hooks(mono);
iohook_apply_hooks(mono);
}
/* Hook Win32 APIs */
dvd_hook_init(&mai2_hook_cfg.dvd, mai2_hook_mod);
gfx_hook_init(&mai2_hook_cfg.gfx);
gfx_d3d9_hook_init(&mai2_hook_cfg.gfx, mai2_hook_mod);
gfx_d3d11_hook_init(&mai2_hook_cfg.gfx, mai2_hook_mod);
gfx_dxgi_hook_init(&mai2_hook_cfg.gfx, mai2_hook_mod);
serial_hook_init();
/* Initialize emulation hooks */
hr = platform_hook_init(
&mai2_hook_cfg.platform,
"SDEZ",
"ACA1",
mai2_hook_mod);
if (FAILED(hr)) {
goto fail;
}
hr = sg_reader_hook_init(&mai2_hook_cfg.aime, 1, mai2_hook_mod);
if (FAILED(hr)) {
goto fail;
}
hr = vfd_hook_init(2);
if (FAILED(hr)) {
goto fail;
}
hr = mai2_dll_init(&mai2_hook_cfg.dll, mai2_hook_mod);
if (FAILED(hr)) {
goto fail;
}
hr = mai2_io4_hook_init(&mai2_hook_cfg.io4);
if (FAILED(hr)) {
goto fail;
}
// TODO: The handling of the fake registry COM values is extraordinarly lazy
// and I need to make a better method for tacking fake values onto common keys
// that multiple modules may try to use.
hr = led_hook_init(&mai2_hook_cfg.led);
if (FAILED(hr)) {
goto fail;
}
hr = touch_hook_init(&mai2_hook_cfg.touch);
if (FAILED(hr)) {
goto fail;
}
/* Initialize Unity native plugin DLL hooks
There seems to be an issue with other DLL hooks if `LoadLibraryW` is
hooked earlier in the `mai2hook` initialization. */
unity_hook_init();
/* Initialize debug helpers */
spike_hook_init(L".\\segatools.ini");
dprintf("--- End mai2_pre_startup ---\n");
/* Jump to EXE start address */
return mai2_startup();
fail:
ExitProcess(EXIT_FAILURE);
}
BOOL WINAPI DllMain(HMODULE mod, DWORD cause, void *ctx)
{
HRESULT hr;
if (cause != DLL_PROCESS_ATTACH) {
return TRUE;
}
mai2_hook_mod = mod;
hr = process_hijack_startup(mai2_pre_startup, &mai2_startup);
if (!SUCCEEDED(hr)) {
dprintf("Failed to hijack process startup: %x\n", (int) hr);
}
return SUCCEEDED(hr);
}

147
mai2hook/io4.c Normal file
View File

@ -0,0 +1,147 @@
#include <windows.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "board/io4.h"
#include "mai2hook/mai2-dll.h"
#include "util/dprintf.h"
bool mai2_io_coin = false;
uint16_t mai2_io_coins = 0;
static HRESULT mai2_io4_poll(void *ctx, struct io4_state *state);
static const struct io4_ops mai2_io4_ops = {
.poll = mai2_io4_poll,
};
HRESULT mai2_io4_hook_init(const struct io4_config *cfg)
{
HRESULT hr;
assert(mai2_dll.init != NULL);
hr = io4_hook_init(cfg, &mai2_io4_ops, NULL);
if (FAILED(hr)) {
return hr;
}
return mai2_dll.init();
}
static HRESULT mai2_io4_poll(void *ctx, struct io4_state *state)
{
uint8_t opbtn = 0;
uint8_t player1 = 0;
uint8_t player2 = 0;
HRESULT hr;
assert(mai2_dll.poll != NULL);
hr = mai2_dll.poll(&opbtn, &player1, &player2);
if (FAILED(hr)) {
return hr;
}
if (opbtn & MAI2_IO_OPBTN_TEST) {
state->buttons[0] |= IO4_BUTTON_TEST;
}
if (opbtn & MAI2_IO_OPBTN_SERVICE) {
state->buttons[0] |= IO4_BUTTON_SERVICE;
}
if (opbtn & MAI2_IO_P1_START) {
state->buttons[0] |= 1 << 1;
}
if (opbtn & MAI2_IO_P2_START) {
state->buttons[1] |= 1 << 4;
}
if (!(player1 & MAI2_IO_GAMEBTN_1)) {
state->buttons[0] |= 1 << 2;
}
if (!(player1 & MAI2_IO_GAMEBTN_2)) {
state->buttons[0] |= 1 << 3;
}
if (!(player1 & MAI2_IO_GAMEBTN_3)) {
state->buttons[0] |= 1 << 0;
}
if (!(player1 & MAI2_IO_GAMEBTN_4)) {
state->buttons[0] |= 1 << 15;
}
if (!(player1 & MAI2_IO_GAMEBTN_5)) {
state->buttons[0] |= 1 << 14;
}
if (!(player1 & MAI2_IO_GAMEBTN_6)) {
state->buttons[0] |= 1 << 13;
}
if (!(player1 & MAI2_IO_GAMEBTN_7)) {
state->buttons[0] |= 1 << 12;
}
if (!(player1 & MAI2_IO_GAMEBTN_8)) {
state->buttons[0] |= 1 << 11;
}
if (!(player2 & MAI2_IO_GAMEBTN_1)) {
state->buttons[1] |= 1 << 2;
}
if (!(player2 & MAI2_IO_GAMEBTN_2)) {
state->buttons[1] |= 1 << 3;
}
if (!(player2 & MAI2_IO_GAMEBTN_3)) {
state->buttons[1] |= 1 << 0;
}
if (!(player2 & MAI2_IO_GAMEBTN_4)) {
state->buttons[1] |= 1 << 15;
}
if (!(player2 & MAI2_IO_GAMEBTN_5)) {
state->buttons[1] |= 1 << 14;
}
if (!(player2 & MAI2_IO_GAMEBTN_6)) {
state->buttons[1] |= 1 << 13;
}
if (!(player2 & MAI2_IO_GAMEBTN_7)) {
state->buttons[1] |= 1 << 12;
}
if (!(player2 & MAI2_IO_GAMEBTN_8)) {
state->buttons[1] |= 1 << 11;
}
if (opbtn & MAI2_IO_OPBTN_COIN) {
if (!mai2_io_coin) {
mai2_io_coin = true;
mai2_io_coins++;
}
}
else {
mai2_io_coin = false;
}
state->chutes[0] = 128 + 256 * mai2_io_coins;
return S_OK;
}

7
mai2hook/io4.h Normal file
View File

@ -0,0 +1,7 @@
#pragma once
#include <windows.h>
#include "board/io4.h"
HRESULT mai2_io4_hook_init(const struct io4_config *cfg);

210
mai2hook/led.c Normal file
View File

@ -0,0 +1,210 @@
#include <windows.h>
#include <stdbool.h>
#include "hooklib/reg.h"
#include "hooklib/uart.h"
#include "hooklib/fdshark.h"
#include "mai2hook/led.h"
#include "util/dprintf.h"
static HRESULT read_fake_com0(void *bytes, uint32_t *nbytes);
static HRESULT read_fake_com1(void *bytes, uint32_t *nbytes);
static HRESULT read_fake_com2(void *bytes, uint32_t *nbytes);
static HRESULT led_handle_irp(struct irp *irp);
static HRESULT led0_handle_irp_locked(struct irp *irp);
static HRESULT led1_handle_irp_locked(struct irp *irp);
static CRITICAL_SECTION led0_lock;
static struct uart led0_uart;
static uint8_t led0_written_bytes[520];
static uint8_t led0_readable_bytes[520];
static CRITICAL_SECTION led1_lock;
static struct uart led1_uart;
static uint8_t led1_written_bytes[520];
static uint8_t led1_readable_bytes[520];
static const struct reg_hook_val fake_com_keys[] = {
{
.name = L"\\Device\\RealTouchBoard0",
.read = read_fake_com0,
.type = REG_SZ,
},{
.name = L"\\Device\\RealTouchBoard1",
.read = read_fake_com1,
.type = REG_SZ,
},{
.name = L"\\Device\\RealLedBoard0",
.read = read_fake_com2,
.type = REG_SZ,
},
};
HRESULT led_hook_init(const struct led_config *cfg)
{
HRESULT hr;
if (!cfg->enable) {
return S_FALSE;
}
dprintf("Mai2 LED: Init\n");
InitializeCriticalSection(&led0_lock);
InitializeCriticalSection(&led1_lock);
hr = reg_hook_push_key(
HKEY_LOCAL_MACHINE,
L"HARDWARE\\DEVICEMAP\\SERIALCOMM",
fake_com_keys,
_countof(fake_com_keys));
if (FAILED(hr)) {
return hr;
}
uart_init(&led0_uart, 21);
led0_uart.written.bytes = led0_written_bytes;
led0_uart.written.nbytes = sizeof(led0_written_bytes);
led0_uart.readable.bytes = led0_readable_bytes;
led0_uart.readable.nbytes = sizeof(led0_readable_bytes);
uart_init(&led1_uart, 23);
led1_uart.written.bytes = led1_written_bytes;
led1_uart.written.nbytes = sizeof(led1_written_bytes);
led1_uart.readable.bytes = led1_readable_bytes;
led1_uart.readable.nbytes = sizeof(led1_readable_bytes);
return iohook_push_handler(led_handle_irp);
}
static HRESULT read_fake_com0(void *bytes, uint32_t *nbytes)
{
//dprintf("Mai2 Touch: Read COM3 reg val\n");
return reg_hook_read_wstr(bytes, nbytes, L"COM3");
}
static HRESULT read_fake_com1(void *bytes, uint32_t *nbytes)
{
//dprintf("Mai2 Touch: Read COM4 reg val\n");
return reg_hook_read_wstr(bytes, nbytes, L"COM4");
}
static HRESULT read_fake_com2(void *bytes, uint32_t *nbytes)
{
//dprintf("Mai2 LED: Read COM20 reg val\n");
return reg_hook_read_wstr(bytes, nbytes, L"COM20");
}
static HRESULT led_handle_irp(struct irp *irp)
{
HRESULT hr;
assert(irp != NULL);
if (uart_match_irp(&led0_uart, irp)) {
EnterCriticalSection(&led0_lock);
hr = led0_handle_irp_locked(irp);
LeaveCriticalSection(&led0_lock);
}
else if (uart_match_irp(&led1_uart, irp)) {
EnterCriticalSection(&led1_lock);
hr = led1_handle_irp_locked(irp);
LeaveCriticalSection(&led1_lock);
}
else {
return iohook_invoke_next(irp);
}
return hr;
}
static HRESULT led0_handle_irp_locked(struct irp *irp)
{
HRESULT hr = S_OK;
if (irp->op == IRP_OP_OPEN) {
dprintf("Mai2 led0: Starting backend\n");
//hr = mai2_dll.led_init();
if (FAILED(hr)) {
dprintf("Mai2 led: Backend error: %x\n", (int) hr);
return hr;
}
}
hr = uart_handle_irp(&led0_uart, irp);
if (FAILED(hr) || irp->op != IRP_OP_WRITE) {
return hr;
}
for (;;) {
#if 0
dprintf("TX0 Buffer:\n");
dump_iobuf(&led0_uart.written);
#endif
//hr = led_frame_decode(&req, &led0_uart.written, 0);
if (hr != S_OK) {
if (FAILED(hr)) {
dprintf("Mai2 led: Deframe error: %x\n", (int) hr);
}
return hr;
}
//hr = led_req_dispatch(&req);
if (FAILED(hr)) {
dprintf("Mai2 led: Processing error: %x\n", (int) hr);
}
return hr;
}
}
static HRESULT led1_handle_irp_locked(struct irp *irp)
{
HRESULT hr = S_OK;
if (irp->op == IRP_OP_OPEN) {
dprintf("Mai2 led1: Starting backend\n");
//hr = mai2_dll.led_init();
if (FAILED(hr)) {
dprintf("Mai2 led: Backend error: %x\n", (int) hr);
return hr;
}
}
hr = uart_handle_irp(&led0_uart, irp);
if (FAILED(hr) || irp->op != IRP_OP_WRITE) {
return hr;
}
for (;;) {
#if 0
dprintf("TX0 Buffer:\n");
dump_iobuf(&led0_uart.written);
#endif
//hr = led_frame_decode(&req, &led0_uart.written, 0);
if (hr != S_OK) {
if (FAILED(hr)) {
dprintf("Mai2 led: Deframe error: %x\n", (int) hr);
}
return hr;
}
//hr = led_req_dispatch(&req);
if (FAILED(hr)) {
dprintf("Mai2 led: Processing error: %x\n", (int) hr);
}
return hr;
}
}

9
mai2hook/led.h Normal file
View File

@ -0,0 +1,9 @@
#pragma once
#include <windows.h>
#include <stdbool.h>
struct led_config {
bool enable;
};
HRESULT led_hook_init(const struct led_config *cfg);

103
mai2hook/mai2-dll.c Normal file
View File

@ -0,0 +1,103 @@
#include <windows.h>
#include <assert.h>
#include <stdlib.h>
#include "mai2hook/mai2-dll.h"
#include "util/dll-bind.h"
#include "util/dprintf.h"
const struct dll_bind_sym mai2_dll_syms[] = {
{
.sym = "mai2_io_init",
.off = offsetof(struct mai2_dll, init),
}, {
.sym = "mai2_io_poll",
.off = offsetof(struct mai2_dll, poll),
},
};
struct mai2_dll mai2_dll;
// Copypasta DLL binding and diagnostic message boilerplate.
// Not much of this lends itself to being easily factored out. Also there
// will be a lot of API-specific branching code here eventually as new API
// versions get defined, so even though these functions all look the same
// now this won't remain the case forever.
HRESULT mai2_dll_init(const struct mai2_dll_config *cfg, HINSTANCE self)
{
uint16_t (*get_api_version)(void);
const struct dll_bind_sym *sym;
HINSTANCE owned;
HINSTANCE src;
HRESULT hr;
assert(cfg != NULL);
assert(self != NULL);
if (cfg->path[0] != L'\0') {
owned = LoadLibraryW(cfg->path);
if (owned == NULL) {
hr = HRESULT_FROM_WIN32(GetLastError());
dprintf("Mai2 IO: Failed to load IO DLL: %lx: %S\n",
hr,
cfg->path);
goto end;
}
dprintf("Mai2 IO: Using custom IO DLL: %S\n", cfg->path);
src = owned;
} else {
owned = NULL;
src = self;
}
get_api_version = (void *) GetProcAddress(src, "mai2_io_get_api_version");
if (get_api_version != NULL) {
mai2_dll.api_version = get_api_version();
} else {
mai2_dll.api_version = 0x0100;
dprintf("Custom IO DLL does not expose mai2_io_get_api_version, "
"assuming API version 1.0.\n"
"Please ask the developer to update their DLL.\n");
}
if (mai2_dll.api_version >= 0x0200) {
hr = E_NOTIMPL;
dprintf("Mai2 IO: Custom IO DLL implements an unsupported "
"API version (%#04x). Please update Segatools.\n",
mai2_dll.api_version);
goto end;
}
sym = mai2_dll_syms;
hr = dll_bind(&mai2_dll, src, &sym, _countof(mai2_dll_syms));
if (FAILED(hr)) {
if (src != self) {
dprintf("Mai2 IO: Custom IO DLL does not provide function "
"\"%s\". Please contact your IO DLL's developer for "
"further assistance.\n",
sym->sym);
goto end;
} else {
dprintf("Internal error: could not reflect \"%s\"\n", sym->sym);
}
}
owned = NULL;
end:
if (owned != NULL) {
FreeLibrary(owned);
}
return hr;
}

19
mai2hook/mai2-dll.h Normal file
View File

@ -0,0 +1,19 @@
#pragma once
#include <windows.h>
#include "mai2io/mai2io.h"
struct mai2_dll {
uint16_t api_version;
HRESULT (*init)(void);
HRESULT (*poll)(uint8_t *opbtn, uint8_t *p1, uint8_t *p2);
};
struct mai2_dll_config {
wchar_t path[MAX_PATH];
};
extern struct mai2_dll mai2_dll;
HRESULT mai2_dll_init(const struct mai2_dll_config *cfg, HINSTANCE self);

22
mai2hook/mai2hook.def Normal file
View File

@ -0,0 +1,22 @@
LIBRARY mai2hook
EXPORTS
CreateDXGIFactory
CreateDXGIFactory1
CreateDXGIFactory2
D3D11CreateDevice
D3D11CreateDeviceAndSwapChain
Direct3DCreate9
aime_io_get_api_version
aime_io_init
aime_io_led_set_color
aime_io_nfc_get_aime_id
aime_io_nfc_get_felica_id
aime_io_nfc_poll
amDllVideoClose @2
amDllVideoGetVBiosVersion @4
amDllVideoOpen @1
amDllVideoSetResolution @3
mai2_io_get_api_version
mai2_io_init
mai2_io_poll

37
mai2hook/meson.build Normal file
View File

@ -0,0 +1,37 @@
shared_library(
'mai2hook',
name_prefix : '',
include_directories : inc,
implicit_include_directories : false,
vs_module_defs : 'mai2hook.def',
c_pch : '../precompiled.h',
dependencies : [
capnhook.get_variable('hook_dep'),
capnhook.get_variable('hooklib_dep'),
xinput_lib,
],
link_with : [
aimeio_lib,
board_lib,
gfxhook_lib,
hooklib_lib,
mai2io_lib,
platform_lib,
util_lib,
],
sources : [
'config.c',
'config.h',
'dllmain.c',
'led.c',
'led.h',
'io4.c',
'io4.h',
'mai2-dll.c',
'mai2-dll.h',
'touch.c',
'touch.h',
'unity.h',
'unity.c',
],
)

233
mai2hook/touch.c Normal file
View File

@ -0,0 +1,233 @@
#include <windows.h>
#include <stdbool.h>
#include <stdint.h>
#include "hooklib/reg.h"
#include "hooklib/uart.h"
#include "hooklib/fdshark.h"
#include "mai2hook/touch.h"
#include "mai2hook/mai2-dll.h"
#include "util/dprintf.h"
const char CMD_START = '{';
const char CMD_END = '}';
const char RESP_START = '(';
const char RESP_END = ')';
const char BLANK = '@';
// Commands with parameters
const char CMD_RATIO[1] = "r";
const char CMD_SENS[1] = "k";
const char CMD_SENS_CHECK[2] = "th";
// Commands that never change
const char CMD_RESET[7] = "{RSET}"; // Reset board to default state
const char CMD_STAT[7] = "{STAT}"; // Start sending touch state
const char CMD_HALT[7] = "{HALT}"; // Stop sending touch state
static HRESULT read_fake_com0(void *bytes, uint32_t *nbytes);
static HRESULT read_fake_com1(void *bytes, uint32_t *nbytes);
static HRESULT read_fake_com2(void *bytes, uint32_t *nbytes);
static HRESULT touch_handle_irp(struct irp *irp);
static HRESULT touch0_handle_irp_locked(struct irp *irp);
static HRESULT touch1_handle_irp_locked(struct irp *irp);
bool touch0_auto = false;
bool touch1_auto = false;
static CRITICAL_SECTION touch0_lock;
static struct uart touch0_uart;
static uint8_t touch0_written_bytes[520];
static uint8_t touch0_readable_bytes[520];
static CRITICAL_SECTION touch1_lock;
static struct uart touch1_uart;
static uint8_t touch1_written_bytes[520];
static uint8_t touch1_readable_bytes[520];
static const struct reg_hook_val fake_com_keys[] = {
{
.name = L"\\Device\\RealTouchBoard0",
.read = read_fake_com0,
.type = REG_SZ,
},{
.name = L"\\Device\\RealTouchBoard1",
.read = read_fake_com1,
.type = REG_SZ,
},{
.name = L"\\Device\\RealLedBoard0",
.read = read_fake_com2,
.type = REG_SZ,
},
};
HRESULT touch_hook_init(const struct touch_config *cfg)
{
HRESULT hr;
if (!cfg->enable) {
return S_FALSE;
}
dprintf("Mai2 touch: Init\n");
InitializeCriticalSection(&touch0_lock);
InitializeCriticalSection(&touch1_lock);
hr = reg_hook_push_key(
HKEY_LOCAL_MACHINE,
L"HARDWARE\\DEVICEMAP\\SERIALCOMM",
fake_com_keys,
_countof(fake_com_keys));
if (FAILED(hr)) {
return hr;
}
uart_init(&touch0_uart, 3);
touch0_uart.written.bytes = touch0_written_bytes;
touch0_uart.written.nbytes = sizeof(touch0_written_bytes);
touch0_uart.readable.bytes = touch0_readable_bytes;
touch0_uart.readable.nbytes = sizeof(touch0_readable_bytes);
uart_init(&touch1_uart, 4);
touch1_uart.written.bytes = touch1_written_bytes;
touch1_uart.written.nbytes = sizeof(touch1_written_bytes);
touch1_uart.readable.bytes = touch1_readable_bytes;
touch1_uart.readable.nbytes = sizeof(touch1_readable_bytes);
return iohook_push_handler(touch_handle_irp);
}
static HRESULT read_fake_com0(void *bytes, uint32_t *nbytes)
{
// dprintf("Mai2 Touch: Read COM3 reg val\n");
return reg_hook_read_wstr(bytes, nbytes, L"COM3");
}
static HRESULT read_fake_com1(void *bytes, uint32_t *nbytes)
{
// dprintf("Mai2 Touch: Read COM4 reg val\n");
return reg_hook_read_wstr(bytes, nbytes, L"COM4");
}
static HRESULT read_fake_com2(void *bytes, uint32_t *nbytes)
{
// dprintf("Mai2 LED: Read COM20 reg val\n");
return reg_hook_read_wstr(bytes, nbytes, L"COM20");
}
static HRESULT touch_handle_irp(struct irp *irp)
{
HRESULT hr;
assert(irp != NULL);
if (uart_match_irp(&touch0_uart, irp)) {
EnterCriticalSection(&touch0_lock);
hr = touch0_handle_irp_locked(irp);
LeaveCriticalSection(&touch0_lock);
}
else if (uart_match_irp(&touch1_uart, irp)) {
EnterCriticalSection(&touch1_lock);
hr = touch1_handle_irp_locked(irp);
LeaveCriticalSection(&touch1_lock);
}
else {
return iohook_invoke_next(irp);
}
return hr;
}
static HRESULT touch0_handle_irp_locked(struct irp *irp)
{
HRESULT hr = S_OK;
if (irp->op == IRP_OP_OPEN) {
dprintf("Mai2 touch0: Starting backend\n");
//hr = mai2_dll.touch_init();
if (FAILED(hr)) {
dprintf("Mai2 touch: Backend error: %x\n", (int) hr);
return hr;
}
}
hr = uart_handle_irp(&touch0_uart, irp);
if (FAILED(hr) || irp->op != IRP_OP_WRITE) {
return hr;
}
for (;;) {
#if 0
dprintf("TX0 Buffer:\n");
dump_iobuf(&touch0_uart.written);
#endif
//hr = touch_frame_decode(&req, &touch0_uart.written, 0);
if (hr != S_OK) {
if (FAILED(hr)) {
dprintf("Mai2 touch: Deframe error: %x\n", (int) hr);
}
return hr;
}
//hr = touch_req_dispatch(&req);
if (FAILED(hr)) {
dprintf("Mai2 touch: Processing error: %x\n", (int) hr);
}
return hr;
}
}
static HRESULT touch1_handle_irp_locked(struct irp *irp)
{
HRESULT hr = S_OK;
if (irp->op == IRP_OP_OPEN) {
dprintf("Mai2 touch1: Starting backend\n");
//hr = mai2_dll.touch_init();
if (FAILED(hr)) {
dprintf("Mai2 touch: Backend error: %x\n", (int) hr);
return hr;
}
}
hr = uart_handle_irp(&touch0_uart, irp);
if (FAILED(hr) || irp->op != IRP_OP_WRITE) {
return hr;
}
for (;;) {
#if 0
dprintf("TX0 Buffer:\n");
dump_iobuf(&touch0_uart.written);
#endif
//hr = touch_frame_decode(&req, &touch0_uart.written, 0);
if (hr != S_OK) {
if (FAILED(hr)) {
dprintf("Mai2 touch: Deframe error: %x\n", (int) hr);
}
return hr;
}
//hr = touch_req_dispatch(&req);
if (FAILED(hr)) {
dprintf("Mai2 touch: Processing error: %x\n", (int) hr);
}
return hr;
}
}

9
mai2hook/touch.h Normal file
View File

@ -0,0 +1,9 @@
#pragma once
#include <windows.h>
#include <stdbool.h>
struct touch_config {
bool enable;
};
HRESULT touch_hook_init(const struct touch_config *cfg);

112
mai2hook/unity.c Normal file
View File

@ -0,0 +1,112 @@
#include <stdbool.h>
#include <windows.h>
#include "hook/table.h"
#include "hooklib/dll.h"
#include "hooklib/path.h"
#include "hooklib/reg.h"
#include "hooklib/procaddr.h"
#include "util/dprintf.h"
static void dll_hook_insert_hooks(HMODULE target);
static HMODULE WINAPI my_LoadLibraryW(const wchar_t *name);
static HMODULE (WINAPI *next_LoadLibraryW)(const wchar_t *name);
static HMODULE WINAPI my_LoadLibraryExW(const wchar_t *name, HANDLE hFile, DWORD dwFlags);
static HMODULE (WINAPI *next_LoadLibraryExW)(const wchar_t *name, HANDLE hFile, DWORD dwFlags);
static const struct hook_symbol unity_kernel32_syms[] = {
{
.name = "LoadLibraryW",
.patch = my_LoadLibraryW,
.link = (void **) &next_LoadLibraryW,
},{
.name = "LoadLibraryExW",
.patch = my_LoadLibraryExW,
.link = (void **) &next_LoadLibraryExW,
}
};
static const wchar_t *target_modules[] = {
L"mono-2.0-bdwgc.dll",
L"cri_ware_unity.dll",
};
static const size_t target_modules_len = _countof(target_modules);
void unity_hook_init(void)
{
dll_hook_insert_hooks(NULL);
}
static void dll_hook_insert_hooks(HMODULE target)
{
hook_table_apply(
target,
"kernel32.dll",
unity_kernel32_syms,
_countof(unity_kernel32_syms));
}
static HMODULE WINAPI my_LoadLibraryExW(const wchar_t *name, HANDLE hFile, DWORD dwFlags)
{
//dprintf("Unity: LoadLibraryExW %ls\n", name);
return my_LoadLibraryW(name);
}
static HMODULE WINAPI my_LoadLibraryW(const wchar_t *name)
{
const wchar_t *name_end;
const wchar_t *target_module;
bool already_loaded;
HMODULE result;
size_t name_len;
size_t target_module_len;
if (name == NULL) {
SetLastError(ERROR_INVALID_PARAMETER);
return NULL;
}
// Check if the module is already loaded
already_loaded = GetModuleHandleW(name) != NULL;
// Must call the next handler so the DLL reference count is incremented
result = next_LoadLibraryW(name);
if (!already_loaded && result != NULL) {
name_len = wcslen(name);
for (size_t i = 0; i < target_modules_len; i++) {
target_module = target_modules[i];
target_module_len = wcslen(target_module);
// Check if the newly loaded library is at least the length of
// the name of the target module
if (name_len < target_module_len) {
continue;
}
name_end = &name[name_len - target_module_len];
// Check if the name of the newly loaded library is one of the
// modules the path hooks should be injected into
if (_wcsicmp(name_end, target_module) != 0) {
continue;
}
dprintf("Unity: Loaded %S\n", target_module);
dll_hook_insert_hooks(result);
path_hook_insert_hooks(result);
reg_hook_insert_hooks(result);
proc_addr_insert_hooks(result);
}
}
return result;
}

3
mai2hook/unity.h Normal file
View File

@ -0,0 +1,3 @@
#pragma once
void unity_hook_init(void);

46
mai2io/config.c Normal file
View File

@ -0,0 +1,46 @@
#include <windows.h>
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include "mai2io/config.h"
static const int mai2_io_1p_default[] = {'W', 'E', 'D', 'C', 'X', 'Z', 'A', 'Q'};
static const int mai2_io_2p_default[] = {0x68, 0x69, 0x66, 0x63, 0x62, 0x61, 0x64, 0x67};
void mai2_io_config_load(
struct mai2_io_config *cfg,
const wchar_t *filename)
{
wchar_t key[240];
int i;
assert(cfg != NULL);
assert(filename != NULL);
cfg->vk_test = GetPrivateProfileIntW(L"io4", L"test", VK_DELETE, filename);
cfg->vk_service = GetPrivateProfileIntW(L"io4", L"service", VK_END, filename);
cfg->vk_coin = GetPrivateProfileIntW(L"io4", L"coin", VK_INSERT, filename);
cfg->vk_p1_start = GetPrivateProfileIntW(L"io4", L"p1_start", '1', filename);
cfg->vk_p2_start = GetPrivateProfileIntW(L"io4", L"p2_start", '2', filename);
for (i = 0 ; i < 8 ; i++) {
swprintf_s(key, _countof(key), L"1p_btn%i", i + 1);
cfg->vk_p1_btn[i] = GetPrivateProfileIntW(
L"button",
key,
mai2_io_1p_default[i],
filename);
}
for (i = 0 ; i < 8 ; i++) {
swprintf_s(key, _countof(key), L"2p_btn%i", i + 1);
cfg->vk_p2_btn[i] = GetPrivateProfileIntW(
L"button",
key,
mai2_io_2p_default[i],
filename);
}
}

20
mai2io/config.h Normal file
View File

@ -0,0 +1,20 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
struct mai2_io_config {
uint8_t vk_test;
uint8_t vk_service;
uint8_t vk_coin;
uint8_t vk_p1_start;
uint8_t vk_p2_start;
uint8_t vk_p1_btn[8];
uint8_t vk_p2_btn[8];
};
void mai2_io_config_load(
struct mai2_io_config *cfg,
const wchar_t *filename);

83
mai2io/mai2io.c Normal file
View File

@ -0,0 +1,83 @@
#include <windows.h>
#include <xinput.h>
#include <limits.h>
#include <stdint.h>
#include "mai2io/mai2io.h"
#include "mai2io/config.h"
static struct mai2_io_config mai2_io_cfg;
uint16_t mai2_io_get_api_version(void)
{
return 0x0100;
}
HRESULT mai2_io_init(void)
{
mai2_io_config_load(&mai2_io_cfg, L".\\segatools.ini");
return S_OK;
}
HRESULT mai2_io_poll(uint8_t *opbtn, uint8_t *player1, uint8_t *player2)
{
uint8_t opts = 0;
uint8_t p1 = 0;
uint8_t p2 = 0;
if (GetAsyncKeyState(mai2_io_cfg.vk_test) & 0x8000) {
opts |= MAI2_IO_OPBTN_TEST;
}if (GetAsyncKeyState(mai2_io_cfg.vk_service) & 0x8000) {
opts |= MAI2_IO_OPBTN_SERVICE;
}if (GetAsyncKeyState(mai2_io_cfg.vk_coin) & 0x8000) {
opts |= MAI2_IO_OPBTN_COIN;
}if (GetAsyncKeyState(mai2_io_cfg.vk_p1_start) & 0x8000) {
opts |= MAI2_IO_P1_START;
}if (GetAsyncKeyState(mai2_io_cfg.vk_p2_start) & 0x8000) {
opts |= MAI2_IO_P2_START;
}
*opbtn = opts;
if (GetAsyncKeyState(mai2_io_cfg.vk_p1_btn[0]) & 0x8000) {
p1 |= MAI2_IO_GAMEBTN_1;
}if (GetAsyncKeyState(mai2_io_cfg.vk_p1_btn[1]) & 0x8000) {
p1 |= MAI2_IO_GAMEBTN_2;
}if (GetAsyncKeyState(mai2_io_cfg.vk_p1_btn[2]) & 0x8000) {
p1 |= MAI2_IO_GAMEBTN_3;
}if (GetAsyncKeyState(mai2_io_cfg.vk_p1_btn[3]) & 0x8000) {
p1 |= MAI2_IO_GAMEBTN_4;
}if (GetAsyncKeyState(mai2_io_cfg.vk_p1_btn[4]) & 0x8000) {
p1 |= MAI2_IO_GAMEBTN_5;
}if (GetAsyncKeyState(mai2_io_cfg.vk_p1_btn[5]) & 0x8000) {
p1 |= MAI2_IO_GAMEBTN_6;
}if (GetAsyncKeyState(mai2_io_cfg.vk_p1_btn[6]) & 0x8000) {
p1 |= MAI2_IO_GAMEBTN_7;
}if (GetAsyncKeyState(mai2_io_cfg.vk_p1_btn[7]) & 0x8000) {
p1 |= MAI2_IO_GAMEBTN_8;
}
*player1 = p1;
if (GetAsyncKeyState(mai2_io_cfg.vk_p2_btn[0]) & 0x8000) {
p2 |= MAI2_IO_GAMEBTN_1;
}if (GetAsyncKeyState(mai2_io_cfg.vk_p2_btn[1]) & 0x8000) {
p2 |= MAI2_IO_GAMEBTN_2;
}if (GetAsyncKeyState(mai2_io_cfg.vk_p2_btn[2]) & 0x8000) {
p2 |= MAI2_IO_GAMEBTN_3;
}if (GetAsyncKeyState(mai2_io_cfg.vk_p2_btn[3]) & 0x8000) {
p2 |= MAI2_IO_GAMEBTN_4;
}if (GetAsyncKeyState(mai2_io_cfg.vk_p2_btn[4]) & 0x8000) {
p2 |= MAI2_IO_GAMEBTN_5;
}if (GetAsyncKeyState(mai2_io_cfg.vk_p2_btn[5]) & 0x8000) {
p2 |= MAI2_IO_GAMEBTN_6;
}if (GetAsyncKeyState(mai2_io_cfg.vk_p2_btn[6]) & 0x8000) {
p2 |= MAI2_IO_GAMEBTN_7;
}if (GetAsyncKeyState(mai2_io_cfg.vk_p2_btn[7]) & 0x8000) {
p2 |= MAI2_IO_GAMEBTN_8;
}
*player2 = p2;
return S_OK;
}

49
mai2io/mai2io.h Normal file
View File

@ -0,0 +1,49 @@
#pragma once
#include <windows.h>
#include <stdint.h>
enum {
MAI2_IO_OPBTN_TEST = 0x01,
MAI2_IO_OPBTN_SERVICE = 0x02,
MAI2_IO_OPBTN_COIN = 0x04,
MAI2_IO_P1_START = 0x08,
MAI2_IO_P2_START = 0x10,
};
enum {
MAI2_IO_GAMEBTN_1 = 0x01,
MAI2_IO_GAMEBTN_2 = 0x02,
MAI2_IO_GAMEBTN_3 = 0x04,
MAI2_IO_GAMEBTN_4 = 0x08,
MAI2_IO_GAMEBTN_5 = 0x10,
MAI2_IO_GAMEBTN_6 = 0x20,
MAI2_IO_GAMEBTN_7 = 0x40,
MAI2_IO_GAMEBTN_8 = 0x80,
};
/* Get the version of the Mai2 IO API that this DLL supports. This
function should return a positive 16-bit integer, where the high byte is
the major version and the low byte is the minor version (as defined by the
Semantic Versioning standard).
The latest API version as of this writing is 0x0100. */
uint16_t mai2_io_get_api_version(void);
/* Initialize the IO DLL. This is the second function that will be called on
your DLL, after mai2_io_get_api_version.
All subsequent calls to this API may originate from arbitrary threads.
Minimum API version: 0x0100 */
HRESULT mai2_io_init(void);
/* Send any queued outputs (of which there are currently none, though this may
change in subsequent API versions) and retrieve any new inputs.
Minimum API version: 0x0100 */
HRESULT mai2_io_poll(uint8_t *opbtn, uint8_t *player1, uint8_t *player2);

16
mai2io/meson.build Normal file
View File

@ -0,0 +1,16 @@
mai2io_lib = static_library(
'mai2io',
name_prefix : '',
include_directories : inc,
implicit_include_directories : false,
c_pch : '../precompiled.h',
dependencies : [
xinput_lib,
],
sources : [
'mai2io.c',
'mai2io.h',
'config.c',
'config.h',
],
)

View File

@ -61,6 +61,7 @@ subdir('idzio')
subdir('mu3io')
subdir('mercuryio')
subdir('cxbio')
subdir('mai2io')
subdir('chunihook')
subdir('divahook')
@ -70,3 +71,4 @@ subdir('minihook')
subdir('mu3hook')
subdir('mercuryhook')
subdir('cxbhook')
subdir('mai2hook')

View File

@ -1,4 +1,4 @@
[wrap-git]
directory = capnhook
url = https://github.com/decafcode/capnhook
revision = 69f7e3b48c2e0ff5be1d7a83cdcc2597a458357b
url = https://github.com/Hay1tsme/capnhook
revision = 888d068d58e68cf702e0cee872959a71413a7b55