From 8ff356b84c7977339f19daab366c702fc5962d93 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Mon, 18 Sep 2023 04:05:22 -0400 Subject: [PATCH] add games --- Package.mk | 38 +++++++++ dist/exvs2/bananatools.ini | 77 +++++++++++++++++++ dist/exvs2/start.bat | 11 +++ dist/mkac/bananatools.ini | 59 ++++++++++++++ dist/mkac/start.bat | 10 +++ dist/sao/bananatools.ini | 62 +++++++++++++++ dist/sao/start.bat | 12 +++ doc/exvs2hook.md | 8 ++ doc/saohook.md | 8 ++ exvs2hook/bngrw.c | 0 exvs2hook/bngrw.h | 0 exvs2hook/config.c | 36 +++++++++ exvs2hook/config.h | 28 +++++++ exvs2hook/dllmain.c | 93 ++++++++++++++++++++++ exvs2hook/exvs2-dll.c | 100 ++++++++++++++++++++++++ exvs2hook/exvs2-dll.h | 20 +++++ exvs2hook/exvs2hook.def | 7 ++ exvs2hook/jvs.c | 125 ++++++++++++++++++++++++++++++ exvs2hook/jvs.h | 8 ++ exvs2hook/meson.build | 33 ++++++++ exvs2io/config.c | 23 ++++++ exvs2io/config.h | 21 +++++ exvs2io/exvs2io.c | 93 ++++++++++++++++++++++ exvs2io/exvs2io.def | 7 ++ exvs2io/exvs2io.h | 51 ++++++++++++ exvs2io/meson.build | 16 ++++ meson.build | 6 ++ mkachook/config.c | 47 +++++++++++ mkachook/config.h | 34 ++++++++ mkachook/dllmain.c | 103 +++++++++++++++++++++++++ mkachook/jvs.c | 100 ++++++++++++++++++++++++ mkachook/jvs.h | 8 ++ mkachook/meson.build | 34 ++++++++ mkachook/mkac-dll.c | 106 +++++++++++++++++++++++++ mkachook/mkac-dll.h | 22 ++++++ mkachook/mkachook.def | 9 +++ mkachook/xinput.c | 154 +++++++++++++++++++++++++++++++++++++ mkachook/xinput.h | 12 +++ mkacio/config.c | 32 ++++++++ mkacio/config.h | 30 ++++++++ mkacio/meson.build | 16 ++++ mkacio/mkacio.c | 128 ++++++++++++++++++++++++++++++ mkacio/mkacio.def | 9 +++ mkacio/mkacio.h | 68 ++++++++++++++++ saohook/config.c | 38 +++++++++ saohook/config.h | 34 ++++++++ saohook/dllmain.c | 107 ++++++++++++++++++++++++++ saohook/meson.build | 33 ++++++++ saohook/sao-dll.c | 103 +++++++++++++++++++++++++ saohook/sao-dll.h | 22 ++++++ saohook/saohook.def | 8 ++ saohook/unity.c | 105 +++++++++++++++++++++++++ saohook/unity.h | 3 + saohook/usio.c | 82 ++++++++++++++++++++ saohook/usio.h | 7 ++ saoio/config.c | 27 +++++++ saoio/config.h | 26 +++++++ saoio/meson.build | 16 ++++ saoio/saoio.c | 123 +++++++++++++++++++++++++++++ saoio/saoio.def | 8 ++ saoio/saoio.h | 67 ++++++++++++++++ 61 files changed, 2673 insertions(+) create mode 100644 dist/exvs2/bananatools.ini create mode 100644 dist/exvs2/start.bat create mode 100644 dist/mkac/bananatools.ini create mode 100644 dist/mkac/start.bat create mode 100644 dist/sao/bananatools.ini create mode 100644 dist/sao/start.bat create mode 100644 doc/exvs2hook.md create mode 100644 doc/saohook.md create mode 100644 exvs2hook/bngrw.c create mode 100644 exvs2hook/bngrw.h create mode 100644 exvs2hook/config.c create mode 100644 exvs2hook/config.h create mode 100644 exvs2hook/dllmain.c create mode 100644 exvs2hook/exvs2-dll.c create mode 100644 exvs2hook/exvs2-dll.h create mode 100644 exvs2hook/exvs2hook.def create mode 100644 exvs2hook/jvs.c create mode 100644 exvs2hook/jvs.h create mode 100644 exvs2hook/meson.build create mode 100644 exvs2io/config.c create mode 100644 exvs2io/config.h create mode 100644 exvs2io/exvs2io.c create mode 100644 exvs2io/exvs2io.def create mode 100644 exvs2io/exvs2io.h create mode 100644 exvs2io/meson.build create mode 100644 mkachook/config.c create mode 100644 mkachook/config.h create mode 100644 mkachook/dllmain.c create mode 100644 mkachook/jvs.c create mode 100644 mkachook/jvs.h create mode 100644 mkachook/meson.build create mode 100644 mkachook/mkac-dll.c create mode 100644 mkachook/mkac-dll.h create mode 100644 mkachook/mkachook.def create mode 100644 mkachook/xinput.c create mode 100644 mkachook/xinput.h create mode 100644 mkacio/config.c create mode 100644 mkacio/config.h create mode 100644 mkacio/meson.build create mode 100644 mkacio/mkacio.c create mode 100644 mkacio/mkacio.def create mode 100644 mkacio/mkacio.h create mode 100644 saohook/config.c create mode 100644 saohook/config.h create mode 100644 saohook/dllmain.c create mode 100644 saohook/meson.build create mode 100644 saohook/sao-dll.c create mode 100644 saohook/sao-dll.h create mode 100644 saohook/saohook.def create mode 100644 saohook/unity.c create mode 100644 saohook/unity.h create mode 100644 saohook/usio.c create mode 100644 saohook/usio.h create mode 100644 saoio/config.c create mode 100644 saoio/config.h create mode 100644 saoio/meson.build create mode 100644 saoio/saoio.c create mode 100644 saoio/saoio.def create mode 100644 saoio/saoio.h diff --git a/Package.mk b/Package.mk index 2d3765d..b6877c0 100644 --- a/Package.mk +++ b/Package.mk @@ -20,9 +20,44 @@ $(BUILD_DIR_ZIP)/taiko.zip: $(V)strip $(BUILD_DIR_ZIP)/taiko/*.{exe,dll} $(V)cd $(BUILD_DIR_ZIP)/taiko ; zip -r ../taiko.zip * +$(BUILD_DIR_ZIP)/exvs2.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/exvs2 + $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_64)/exvs2hook/exvs2hook.dll \ + $(DIST_DIR)/exvs2/bananatools.ini \ + $(DIST_DIR)/exvs2/start.bat \ + $(BUILD_DIR_ZIP)/exvs2 + $(V)strip $(BUILD_DIR_ZIP)/exvs2/*.{exe,dll} + $(V)cd $(BUILD_DIR_ZIP)/exvs2 ; zip -r ../exvs2.zip * + +$(BUILD_DIR_ZIP)/sao.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/sao + $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_64)/saohook/saohook.dll \ + $(DIST_DIR)/sao/bananatools.ini \ + $(DIST_DIR)/sao/start.bat \ + $(BUILD_DIR_ZIP)/sao + $(V)strip $(BUILD_DIR_ZIP)/sao/*.{exe,dll} + $(V)cd $(BUILD_DIR_ZIP)/sao ; zip -r ../sao.zip * + +$(BUILD_DIR_ZIP)/mkac.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/mkac + $(V)cp $(BUILD_DIR_32)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_32)/mkachook/mkachook.dll \ + $(DIST_DIR)/mkac/bananatools.ini \ + $(DIST_DIR)/mkac/start.bat \ + $(BUILD_DIR_ZIP)/mkac + $(V)strip $(BUILD_DIR_ZIP)/mkac/*.{exe,dll} + $(V)cd $(BUILD_DIR_ZIP)/mkac ; zip -r ../mkac.zip * + $(BUILD_DIR_ZIP)/doc.zip: \ $(DOC_DIR)/ferrumhook.md \ $(DOC_DIR)/taikohook.md \ + $(DOC_DIR)/exvs2hook.md \ + $(DOC_DIR)/saohook.md \ | $(zipdir)/ $(V)echo ... $@ $(V)zip -r $@ $^ @@ -30,6 +65,9 @@ $(BUILD_DIR_ZIP)/doc.zip: \ $(BUILD_DIR_ZIP)/bananatools.zip: \ $(BUILD_DIR_ZIP)/ferrum.zip \ $(BUILD_DIR_ZIP)/taiko.zip \ + $(BUILD_DIR_ZIP)/exvs2.zip \ + $(BUILD_DIR_ZIP)/sao.zip \ + $(BUILD_DIR_ZIP)/mkac.zip \ $(BUILD_DIR_ZIP)/doc.zip \ README.md \ diff --git a/dist/exvs2/bananatools.ini b/dist/exvs2/bananatools.ini new file mode 100644 index 0000000..21e4e34 --- /dev/null +++ b/dist/exvs2/bananatools.ini @@ -0,0 +1,77 @@ +; Controls the virtual file system hooks. These redirect file i/o +; requests to a folder specified below, instead of the drive. +; These are all required, even if the game doesn't use one of them. +[vfs] +path= + +[dns] +default=localhost + +; Security dongle emulation, disable if you have a +; real dongle connected that you want to use +[dongle] +enable=1 +serial=123456789012 + +; Set the network environment. Most games seem to want 192.168.123.X +[netenv] +enable=1 +subnet=192.168.85.0 + +[misc] +systemVersion=GX2100-1-NA-SYS0-A06 + +; Graphics hook, may cause crashes in some games +[gfx] +enable=1 +windowed=1 +framed=0 +monitor=0 + +; Control the AMCUS replacement class +[amcus] +enable=1 +game_id=SBUZ +game_cd=GX21 +am_game_ver=4.00 +cacfg_game_ver=29.31 +server_uri=localhost +server_host=localhost + +; Controlls the xinput hooks +[xinput] +enable=1 + +[reader] +enable=1 +access_code=00000000000000000000 + +; JVS config +[jvs] +enable=1 +port=3 + +; Mappings for the najv4 IO board. To disable JVS emulation and use +; a real board, set enable to 0 in the "jvs" section. +[najv4] +test=0x24 ; "Home" key +coin=0x2D ; "Insert" key +service=0x2E ; "Delete" key +up=0x26 ; Up arrow +down=0x28 ; Down arrow +enter=0x0D ; "Enter" key + +; Mappings for the gamepad. To disable gamepad eumlation and use +; a real pokken arcade controller, set enable to 0 in the "xinput" section +[gamepad] +dpad_up=0x57 ; W +dpad_down=0x53 ; A +dpad_left=0x41 ; S +dpad_right=0x44 ; D +button_a=0x4F ; O +button_b=0x4B ; K +button_x=0x49 ; I +button_y=0x4A ; J +trigger_l=0x51 ; Q +trigger_r=0x45 ; E +button_start=0xA0 ; Left Shift diff --git a/dist/exvs2/start.bat b/dist/exvs2/start.bat new file mode 100644 index 0000000..f51b412 --- /dev/null +++ b/dist/exvs2/start.bat @@ -0,0 +1,11 @@ +@echo off + +pushd %~dp0 + +REM Comment out this line if you intend to use the AMCUS emulator +start inject.exe -d -k exvs2hook.dll AMCUS\AMAuthd.exe +inject.exe -d -k exvs2hook.dll exvs2_exe_Release.exe + +echo. +echo The game process has terminated +pause \ No newline at end of file diff --git a/dist/mkac/bananatools.ini b/dist/mkac/bananatools.ini new file mode 100644 index 0000000..a8582ae --- /dev/null +++ b/dist/mkac/bananatools.ini @@ -0,0 +1,59 @@ +; Controls the virtual file system hooks. These redirect file i/o +; requests to a folder specified below, instead of the drive. +; These are all required, even if the game doesn't use one of them. +[vfs] +path= + +[dns] +default=localhost + +; Security dongle emulation, disable if you have a +; real dongle connected that you want to use +[dongle] +enable=1 +serial=271013020001 + +; Set the network environment. Most games seem to want 192.168.123.X +[netenv] +enable=1 +subnet=192.168.112.0 + +; Graphics hook, may cause crashes in some games +[gfx] +enable=1 +windowed=1 +framed=0 +monitor=0 + +; Control the AMCUS replacement class +[amcus] +enable=1 +game_id=SBZB +game_cd=MK31 +am_game_ver=1.10 +cacfg_game_ver=18.16 +server_uri=localhost +server_host=localhost + +; Controlls the xinput hooks +[xinput] +enable=1 + +[reader] +enable=1 +access_code=00000000000000000000 + +; JVS config +[jvs] +enable=1 +port=3 + +; Mappings for the najv4 IO board. To disable JVS emulation and use +; a real board, set enable to 0 in the "jvs" section. +[najv4] +test=0x24 ; "Home" key +coin=0x2D ; "Insert" key +service=0x2E ; "Delete" key +up=0x26 ; Up arrow +down=0x28 ; Down arrow +enter=0x0D ; "Enter" key diff --git a/dist/mkac/start.bat b/dist/mkac/start.bat new file mode 100644 index 0000000..a14eb0c --- /dev/null +++ b/dist/mkac/start.bat @@ -0,0 +1,10 @@ +@echo off + +pushd %~dp0 + +inject.exe -d -k mkachook.dll AMCUS/AMAuthd.exe +inject.exe -d -k mkachook.dll MK_AGP3_FINAL.exe + +echo. +echo The game process has terminated +pause \ No newline at end of file diff --git a/dist/sao/bananatools.ini b/dist/sao/bananatools.ini new file mode 100644 index 0000000..85d5bf6 --- /dev/null +++ b/dist/sao/bananatools.ini @@ -0,0 +1,62 @@ +; Controls the virtual file system hooks. Redirects all drive to +; [path you set here]\\[drive letter] +; !! REQUIRED !! +[vfs] +path= + +; Hooks for DNS (allnet, etc) +; Set 'default' to the IP/hostname of the server +; you're trying to connect to +[dns] +default=localhost + +; Security dongle emulation, disable if you have a +; real dongle connected that you want to use. Some games +; validate the S/N, some don't seem to care as long as it's +; formatted like below +[dongle] +enable=1 +serial=282513040001 + +; Set the network environment. Most games seem to want 192.168.123.X +[netenv] +enable=1 +subnet=192.168.170.0 + +; Graphics hook, may cause crashes in some games +[gfx] +enable=0 +windowed=1 +framed=0 +monitor=0 + +; Banapass reader +[reader] +enable=1 +access_code=00000000000000000000 + +; Control the AMCUS replacement class +[amcus] +enable=0 +game_id=SDEW +game_cd=SAO1 +am_game_ver=1.00 +cacfg_game_ver=33.11 +server_uri=localhost +server_host=localhost + +; Controls for USIO buttons +; Test: Home +; Service: Delete +; Coin: Insert +; Up: Up arrow +; Down: Down arrow +; Enter: Enter +[usio] +enable=1 +test=0x24 +service=0x2E +coin=0x2D +up=0x26 +down=0x28 +enter=0x0D diff --git a/dist/sao/start.bat b/dist/sao/start.bat new file mode 100644 index 0000000..e16c010 --- /dev/null +++ b/dist/sao/start.bat @@ -0,0 +1,12 @@ +@echo off + +pushd %~dp0 + +start inject.exe -d -k saohook.dll AMCUS/AMAuthd.exe +inject.exe -d -k saohook.dll game/link.exe -screen-fullscreen 0 -screen-width 1920 -screen-height 1080 -logfile link.log + +taskkill /im AMAuthd.exe /f + +echo. +echo The game process has terminated +pause \ No newline at end of file diff --git a/doc/exvs2hook.md b/doc/exvs2hook.md new file mode 100644 index 0000000..724f3cf --- /dev/null +++ b/doc/exvs2hook.md @@ -0,0 +1,8 @@ +# ferrumhook + +# Supported games + +* EX Vs 2 v29.31 + +## General remarks +* Very much WIP, game currently does not work in any capacity diff --git a/doc/saohook.md b/doc/saohook.md new file mode 100644 index 0000000..5844b40 --- /dev/null +++ b/doc/saohook.md @@ -0,0 +1,8 @@ +# ferrumhook + +# Supported games + +* Sword Art Online Arcade - All Versions + +## General remarks +* Very much WIP, game currently does not work in any capacity diff --git a/exvs2hook/bngrw.c b/exvs2hook/bngrw.c new file mode 100644 index 0000000..e69de29 diff --git a/exvs2hook/bngrw.h b/exvs2hook/bngrw.h new file mode 100644 index 0000000..e69de29 diff --git a/exvs2hook/config.c b/exvs2hook/config.c new file mode 100644 index 0000000..d3c737a --- /dev/null +++ b/exvs2hook/config.c @@ -0,0 +1,36 @@ +#include +#include + +#include "exvs2hook/config.h" + +#include "platform/config.h" + +void exvs2_dll_config_load( + struct exvs2_dll_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + GetPrivateProfileStringW( + L"exvs2io", + L"path", + L"", + cfg->path, + _countof(cfg->path), + filename); +} + +void exvs2_hook_config_load( + struct exvs2_hook_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + platform_config_load(&cfg->platform, filename); + exvs2_dll_config_load(&cfg->dll, filename); + gfx_config_load(&cfg->gfx, filename); + qr_config_load(&cfg->qr, filename); + bpreader_config_load(&cfg->reader, filename); +} diff --git a/exvs2hook/config.h b/exvs2hook/config.h new file mode 100644 index 0000000..0f0cb7a --- /dev/null +++ b/exvs2hook/config.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include "exvs2hook/exvs2-dll.h" + +#include "platform/config.h" +#include "gfxhook/config.h" +#include "amcus/config.h" +#include "board/config.h" + +struct exvs2_hook_config { + struct platform_config platform; + struct exvs2_dll_config dll; + struct gfx_config gfx; + struct amcus_config amcus; + struct qr_config qr; + struct bpreader_config reader; +}; + +void exvs2_dll_config_load( + struct exvs2_dll_config *cfg, + const wchar_t *filename); + +void exvs2_hook_config_load( + struct exvs2_hook_config *cfg, + const wchar_t *filename); + diff --git a/exvs2hook/dllmain.c b/exvs2hook/dllmain.c new file mode 100644 index 0000000..dedba41 --- /dev/null +++ b/exvs2hook/dllmain.c @@ -0,0 +1,93 @@ +#include +#include + +#include "exvs2hook/config.h" +#include "exvs2hook/exvs2-dll.h" +#include "exvs2hook/jvs.h" + +#include "amcus/amcus.h" + +#include "hook/process.h" + +#include "hooklib/serial.h" + +#include "board/bpreader.h" +#include "board/qr.h" + +#include "platform/platform.h" +#include "gfxhook/gfx.h" +#include "gfxhook/dxgi.h" + +#include "util/dprintf.h" + +static HMODULE exvs2_hook_mod; +static process_entry_t exvs2_startup; +static struct exvs2_hook_config exvs2_hook_cfg; + +static DWORD CALLBACK exvs2_pre_startup(void) +{ + HRESULT hr; + + dprintf("--- Begin exvs2_pre_startup ---\n"); + + exvs2_hook_config_load(&exvs2_hook_cfg, L".\\bananatools.ini"); + + serial_hook_init(); + + struct dongle_info dinfo; + dinfo.vid = 0x0B9A; + dinfo.pid = 0x0C20; + wcscpy_s(dinfo.manufacturer, _countof(dinfo.manufacturer), L"UFD 3.0"); + wcscpy_s(dinfo.product, _countof(dinfo.product), L"Silicon-Power8G"); + + hr = platform_hook_init(&exvs2_hook_cfg.platform, PLATFORM_BNA1, exvs2_jvs_init, exvs2_hook_mod, dinfo); + + if (FAILED(hr)) { + ExitProcess(EXIT_FAILURE); + } + + hr = exvs2_dll_init(&exvs2_hook_cfg.dll, exvs2_hook_mod); + + if (FAILED(hr)) { + ExitProcess(EXIT_FAILURE); + } + + hr = qr_hook_init(&exvs2_hook_cfg.qr, 1); + + if (FAILED(hr)) { + ExitProcess(EXIT_FAILURE); + } + + hr = amcus_hook_init(&exvs2_hook_cfg.amcus); + + if (FAILED(hr)) { + ExitProcess(EXIT_FAILURE); + } + + gfx_hook_init(&exvs2_hook_cfg.gfx); + gfx_dxgi_hook_init(&exvs2_hook_cfg.gfx, exvs2_hook_mod); + + dprintf("--- End exvs2_pre_startup ---\n"); + + return exvs2_startup(); + +} + +BOOL WINAPI DllMain(HMODULE mod, DWORD cause, void *ctx) +{ + HRESULT hr; + + if (cause != DLL_PROCESS_ATTACH) { + return TRUE; + } + + exvs2_hook_mod = mod; + + hr = process_hijack_startup(exvs2_pre_startup, &exvs2_startup); + + if (!SUCCEEDED(hr)) { + dprintf("Failed to hijack process startup: %x\n", (int) hr); + } + + return SUCCEEDED(hr); +} \ No newline at end of file diff --git a/exvs2hook/exvs2-dll.c b/exvs2hook/exvs2-dll.c new file mode 100644 index 0000000..028c414 --- /dev/null +++ b/exvs2hook/exvs2-dll.c @@ -0,0 +1,100 @@ +#include + +#include +#include + +#include "exvs2hook/exvs2-dll.h" + +#include "util/dll-bind.h" +#include "util/dprintf.h" + +const struct dll_bind_sym exvs2_dll_syms[] = { + { + .sym = "exvs2_io_jvs_init", + .off = offsetof(struct exvs2_dll, jvs_init), + }, { + .sym = "exvs2_io_jvs_poll", + .off = offsetof(struct exvs2_dll, jvs_poll), + }, { + .sym = "exvs2_io_jvs_read_coin_counter", + .off = offsetof(struct exvs2_dll, jvs_read_coin_counter), + } +}; + +struct exvs2_dll exvs2_dll; + +HRESULT exvs2_dll_init(const struct exvs2_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("exvs2 IO: Failed to load IO DLL: %lx: %S\n", + hr, + cfg->path); + + goto end; + } + + dprintf("exvs2 IO: Using custom IO DLL: %S\n", cfg->path); + src = owned; + } else { + owned = NULL; + src = self; + } + + get_api_version = (void *) GetProcAddress(src, "exvs2_io_get_api_version"); + + if (get_api_version != NULL) { + exvs2_dll.api_version = get_api_version(); + } else { + exvs2_dll.api_version = 0x0100; + dprintf("Custom IO DLL does not expose exvs2_io_get_api_version, " + "assuming API version 1.0.\n" + "Please ask the developer to update their DLL.\n"); + } + + if (exvs2_dll.api_version >= 0x0200) { + hr = E_NOTIMPL; + dprintf("exvs2 IO: Custom IO DLL implements an unsupported " + "API version (%#04x). Please update Segatools.\n", + exvs2_dll.api_version); + + goto end; + } + + sym = exvs2_dll_syms; + hr = dll_bind(&exvs2_dll, src, &sym, _countof(exvs2_dll_syms)); + + if (FAILED(hr)) { + if (src != self) { + dprintf("exvs2 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; +} \ No newline at end of file diff --git a/exvs2hook/exvs2-dll.h b/exvs2hook/exvs2-dll.h new file mode 100644 index 0000000..0a0a6a5 --- /dev/null +++ b/exvs2hook/exvs2-dll.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include "exvs2io/exvs2io.h" + +struct exvs2_dll { + uint16_t api_version; + HRESULT (*jvs_init)(void); + HRESULT (*jvs_poll)(uint8_t *opbtn, uint16_t *gamepad); + void (*jvs_read_coin_counter)(uint16_t *coins); +}; + +struct exvs2_dll_config { + wchar_t path[MAX_PATH]; +}; + +extern struct exvs2_dll exvs2_dll; + +HRESULT exvs2_dll_init(const struct exvs2_dll_config *cfg, HINSTANCE self); diff --git a/exvs2hook/exvs2hook.def b/exvs2hook/exvs2hook.def new file mode 100644 index 0000000..9665d7a --- /dev/null +++ b/exvs2hook/exvs2hook.def @@ -0,0 +1,7 @@ +LIBRARY exvs2hook + +EXPORTS + exvs2_io_get_api_version + exvs2_io_jvs_init + exvs2_io_jvs_poll + exvs2_io_jvs_read_coin_counter \ No newline at end of file diff --git a/exvs2hook/jvs.c b/exvs2hook/jvs.c new file mode 100644 index 0000000..508a4ab --- /dev/null +++ b/exvs2hook/jvs.c @@ -0,0 +1,125 @@ +#include + +#include +#include +#include +#include +#include + +#include "hook/iobuf.h" +#include "hook/iohook.h" + +#include "hooklib/uart.h" +#include "hooklib/fdshark.h" + +#include "util/dprintf.h" +#include "util/dump.h" + +#include "board/najv4.h" + +#include "exvs2hook/jvs.h" +#include "exvs2hook/exvs2-dll.h" + +static void exvs2_jvs_read_switches(void *ctx, struct najv4_switch_state *out); +static void exvs2_jvs_read_coin_counter(void *ctx, uint8_t slot_no, uint16_t *out); + +static const struct najv4_ops exvs2_jvs_najv4_ops = { + .read_switches = exvs2_jvs_read_switches, + .read_coin_counter = exvs2_jvs_read_coin_counter, +}; + +static struct najv4 exvs2_jvs_najv4; + +HRESULT exvs2_jvs_init(struct jvs_node **out) +{ + HRESULT hr; + + assert(out != NULL); + assert(exvs2_dll.jvs_init != NULL); + + dprintf("exvs2 JVS: Starting IO backend\n"); + hr = exvs2_dll.jvs_init(); + + if (FAILED(hr)) { + dprintf("exvs2 JVS: Backend error, I/O disconnected: %x\n", (int) hr); + + return hr; + } + + najv4_init(&exvs2_jvs_najv4, NULL, &exvs2_jvs_najv4_ops, NULL); + *out = najv4_to_jvs_node(&exvs2_jvs_najv4); + + return S_OK; +} + +static void exvs2_jvs_read_switches(void *ctx, struct najv4_switch_state *out) +{ + uint8_t opbtn = 0; + uint16_t gamebtn = 0; + + assert(out != NULL); + assert(exvs2_dll.jvs_poll != NULL); + + exvs2_dll.jvs_poll(&opbtn, &gamebtn); + + out->system = 0; + out->p1 = 0; + out->p2 = 0; + + + if (opbtn & EXVS2_IO_OPBTN_TEST) { // Test + out->system = 0x80; + } + + if (gamebtn & EXVS2_IO_GAMEBTN_4) { // Btn4 + out->p1 |= 0x40; + } + + if (gamebtn & EXVS2_IO_GAMEBTN_3) { // Btn3 + out->p1 |= 0x80; + } + + if (gamebtn & EXVS2_IO_GAMEBTN_2) { // Btn2 + out->p1 |= 0x100; + } + + if (gamebtn & EXVS2_IO_GAMEBTN_1) { // Btn1 + out->p1 |= 0x200; + } + + if (gamebtn & EXVS2_IO_GAMEBTN_RIGHT) { // Right + out->p1 |= 0x400; + } + + if (gamebtn & EXVS2_IO_GAMEBTN_LEFT) { // Left + out->p1 |= 0x800; + } + + if (gamebtn & EXVS2_IO_GAMEBTN_DOWN) { // Down + out->p1 |= 0x1000; + } + + if (gamebtn & EXVS2_IO_GAMEBTN_UP) { // Up + out->p1 |= 0x2000; + } + + if (opbtn & EXVS2_IO_OPBTN_SERVICE) { // Service + out->p1 |= 0x4000; + } + + if (gamebtn & EXVS2_IO_GAMEBTN_START) { // Start + out->p1 = 0x8000; + } +} + +static void exvs2_jvs_read_coin_counter(void *ctx, uint8_t slot_no, uint16_t *out) +{ + assert(out != NULL); + assert(exvs2_dll.jvs_read_coin_counter != NULL); + + if (slot_no > 0) { + return; + } + + exvs2_dll.jvs_read_coin_counter(out); +} \ No newline at end of file diff --git a/exvs2hook/jvs.h b/exvs2hook/jvs.h new file mode 100644 index 0000000..e517765 --- /dev/null +++ b/exvs2hook/jvs.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include + +#include "jvs/jvs-bus.h" + +HRESULT exvs2_jvs_init(struct jvs_node **root); \ No newline at end of file diff --git a/exvs2hook/meson.build b/exvs2hook/meson.build new file mode 100644 index 0000000..7408c98 --- /dev/null +++ b/exvs2hook/meson.build @@ -0,0 +1,33 @@ +shared_library( + 'exvs2hook', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + vs_module_defs : 'exvs2hook.def', + c_pch : '../precompiled.h', + dependencies : [ + capnhook.get_variable('hook_dep'), + capnhook.get_variable('hooklib_dep'), + ], + link_with : [ + exvs2io_lib, + amcus_lib, + platform_lib, + util_lib, + hooklib_lib, + gfxhook_lib, + jvs_lib, + board_lib + ], + sources : [ + 'dllmain.c', + 'config.c', + 'config.h', + 'exvs2-dll.c', + 'exvs2-dll.h', + 'jvs.c', + 'jvs.h', + 'bngrw.c', + 'bngrw.h', + ], +) diff --git a/exvs2io/config.c b/exvs2io/config.c new file mode 100644 index 0000000..3bdc464 --- /dev/null +++ b/exvs2io/config.c @@ -0,0 +1,23 @@ +#include + +#include +#include +#include + +#include "exvs2io/config.h" + +void exvs2_io_najv4_config_load(struct exvs2_najv4_config *cfg, const wchar_t *filename) +{ + cfg->test = GetPrivateProfileIntW(L"najv4", L"test", VK_HOME, filename); + cfg->service = GetPrivateProfileIntW(L"najv4", L"service", VK_DELETE, filename); + cfg->coin = GetPrivateProfileIntW(L"najv4", L"coin", VK_INSERT, filename); + cfg->up = GetPrivateProfileIntW(L"najv4", L"up", VK_UP, filename); + cfg->down = GetPrivateProfileIntW(L"najv4", L"down", VK_DOWN, filename); + cfg->left = GetPrivateProfileIntW(L"najv4", L"left", VK_LEFT, filename); + cfg->right = GetPrivateProfileIntW(L"najv4", L"right", VK_RIGHT, filename); + cfg->start = GetPrivateProfileIntW(L"najv4", L"start", VK_RETURN, filename); + cfg->btn1 = GetPrivateProfileIntW(L"najv4", L"btn1", '1', filename); + cfg->btn2 = GetPrivateProfileIntW(L"najv4", L"btn2", '2', filename); + cfg->btn3 = GetPrivateProfileIntW(L"najv4", L"btn3", '3', filename); + cfg->btn4 = GetPrivateProfileIntW(L"najv4", L"btn4", '4', filename); +} diff --git a/exvs2io/config.h b/exvs2io/config.h new file mode 100644 index 0000000..a981d9e --- /dev/null +++ b/exvs2io/config.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +struct exvs2_najv4_config { + uint8_t test; + uint8_t service; + uint8_t coin; + uint8_t up; + uint8_t down; + uint8_t left; + uint8_t right; + uint8_t start; + uint8_t btn1; + uint8_t btn2; + uint8_t btn3; + uint8_t btn4; +}; + +void exvs2_io_najv4_config_load(struct exvs2_najv4_config *cfg, const wchar_t *filename); diff --git a/exvs2io/exvs2io.c b/exvs2io/exvs2io.c new file mode 100644 index 0000000..15c6b3e --- /dev/null +++ b/exvs2io/exvs2io.c @@ -0,0 +1,93 @@ +#include +#include + +#include +#include +#include + +#include "exvs2io/exvs2io.h" +#include "exvs2io/config.h" + +#include "util/dprintf.h" + +static bool exvs2_io_coin = false; +static uint16_t exvs2_coin_ct = 0; +static struct exvs2_najv4_config najv4_cfg; + +uint16_t exvs2_io_get_api_version(void) +{ + return 0x0100; +} + +HRESULT exvs2_io_jvs_init(void) +{ + dprintf("exvs2 IO: JVS Init\n"); + exvs2_io_najv4_config_load(&najv4_cfg, L".\\bananatools.ini"); + return S_OK; +} + +HRESULT exvs2_io_jvs_poll(uint8_t *opbtn, uint16_t *gamepad) +{ + *opbtn = 0; + *gamepad = 0; + + if ((GetAsyncKeyState(najv4_cfg.test) & 0x8000)) { + *opbtn |= EXVS2_IO_OPBTN_TEST; + } + + if (GetAsyncKeyState(najv4_cfg.service) & 0x8000) { + *opbtn |= EXVS2_IO_OPBTN_SERVICE; + } + + if (GetAsyncKeyState(najv4_cfg.up) & 0x8000) { + *gamepad |= EXVS2_IO_GAMEBTN_UP; + } + + if (GetAsyncKeyState(najv4_cfg.down) & 0x8000) { + *gamepad |= EXVS2_IO_GAMEBTN_DOWN; + } + + if (GetAsyncKeyState(najv4_cfg.left) & 0x8000) { + *gamepad |= EXVS2_IO_GAMEBTN_LEFT; + } + + if (GetAsyncKeyState(najv4_cfg.right) & 0x8000) { + *gamepad |= EXVS2_IO_GAMEBTN_RIGHT; + } + + if (GetAsyncKeyState(najv4_cfg.start) & 0x8000) { + *gamepad |= EXVS2_IO_GAMEBTN_START; + } + + if (GetAsyncKeyState(najv4_cfg.btn1) & 0x8000) { + *gamepad |= EXVS2_IO_GAMEBTN_1; + } + + if (GetAsyncKeyState(najv4_cfg.btn1) & 0x8000) { + *gamepad |= EXVS2_IO_GAMEBTN_2; + } + + if (GetAsyncKeyState(najv4_cfg.btn1) & 0x8000) { + *gamepad |= EXVS2_IO_GAMEBTN_3; + } + + if (GetAsyncKeyState(najv4_cfg.btn1) & 0x8000) { + *gamepad |= EXVS2_IO_GAMEBTN_4; + } + + return S_OK; +} + +void exvs2_io_jvs_read_coin_counter(uint16_t *coins) +{ + if (GetAsyncKeyState(najv4_cfg.coin) & 0x8000) { + if (!exvs2_io_coin) { + exvs2_io_coin = true; + exvs2_coin_ct++; + } + } else { + exvs2_io_coin = false; + } + + *coins = exvs2_coin_ct; +} \ No newline at end of file diff --git a/exvs2io/exvs2io.def b/exvs2io/exvs2io.def new file mode 100644 index 0000000..908d950 --- /dev/null +++ b/exvs2io/exvs2io.def @@ -0,0 +1,7 @@ +LIBRARY exvs2hook + +EXPORTS + exvs2_io_get_api_version + exvs2_io_jvs_init + exvs2_io_jvs_poll + exvs2_io_read_coin_counter \ No newline at end of file diff --git a/exvs2io/exvs2io.h b/exvs2io/exvs2io.h new file mode 100644 index 0000000..0bf86ab --- /dev/null +++ b/exvs2io/exvs2io.h @@ -0,0 +1,51 @@ +#pragma once + +#include + +#include + +#include "exvs2io/config.h" + +enum { + EXVS2_IO_OPBTN_TEST = 0x01, + EXVS2_IO_OPBTN_SERVICE = 0x02, + EXVS2_IO_OPBTN_COIN = 0x20, +}; +enum { + EXVS2_IO_GAMEBTN_1 = 0x1, + EXVS2_IO_GAMEBTN_2 = 0x2, + EXVS2_IO_GAMEBTN_3 = 0x4, + EXVS2_IO_GAMEBTN_4 = 0x8, + EXVS2_IO_GAMEBTN_UP = 0x10, + EXVS2_IO_GAMEBTN_DOWN = 0x20, + EXVS2_IO_GAMEBTN_LEFT = 0x40, + EXVS2_IO_GAMEBTN_RIGHT = 0x80, + EXVS2_IO_GAMEBTN_START = 0x100, +}; + +/* Get the version of the Pokken 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 exvs2_io_get_api_version(void); + +/* Initialize the IO DLL. This is the second function that will be called on + your DLL, after exvs2_io_get_api_version. + + All subsequent calls to this API may originate from arbitrary threads. + + Minimum API version: 0x0100 */ + +HRESULT exvs2_io_jvs_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 exvs2_io_jvs_poll(uint8_t *opbtn, uint16_t *gamepad); + +void exvs2_io_jvs_read_coin_counter(uint16_t *coins); \ No newline at end of file diff --git a/exvs2io/meson.build b/exvs2io/meson.build new file mode 100644 index 0000000..736528e --- /dev/null +++ b/exvs2io/meson.build @@ -0,0 +1,16 @@ +exvs2io_lib = static_library( + 'exvs2io', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + dependencies : [ + xinput_lib, + ], + sources : [ + 'exvs2io.c', + 'exvs2io.h', + 'config.c', + 'config.h', + ], +) diff --git a/meson.build b/meson.build index d79ac63..5132938 100644 --- a/meson.build +++ b/meson.build @@ -56,6 +56,12 @@ subdir('amcus') subdir('ferrumio') subdir('taikoio') +subdir('exvs2io') +subdir('saoio') +subdir('mkacio') subdir('taikohook') subdir('ferrumhook') +subdir('exvs2hook') +subdir('saohook') +subdir('mkachook') \ No newline at end of file diff --git a/mkachook/config.c b/mkachook/config.c new file mode 100644 index 0000000..f2df06a --- /dev/null +++ b/mkachook/config.c @@ -0,0 +1,47 @@ +#include +#include + +#include "mkachook/config.h" + +#include "platform/config.h" + +void mkac_dll_config_load( + struct mkac_dll_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + GetPrivateProfileStringW( + L"mkacio", + L"path", + L"", + cfg->path, + _countof(cfg->path), + filename); +} + +void mkac_xinput_config_load( + struct mkac_xinput_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"xinput", L"enable", 1, filename); +} + + +void mkac_hook_config_load( + struct mkac_hook_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + platform_config_load(&cfg->platform, filename); + mkac_dll_config_load(&cfg->dll, filename); + mkac_xinput_config_load(&cfg->xinput, filename); + gfx_config_load(&cfg->gfx, filename); + bpreader_config_load(&cfg->reader, filename); +} diff --git a/mkachook/config.h b/mkachook/config.h new file mode 100644 index 0000000..f23c9a5 --- /dev/null +++ b/mkachook/config.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include "mkachook/mkac-dll.h" +#include "mkachook/xinput.h" +#include "mkachook/jvs.h" +#include "board/config.h" + +#include "platform/config.h" +#include "gfxhook/config.h" +#include "amcus/config.h" + +struct mkac_hook_config { + struct platform_config platform; + struct mkac_dll_config dll; + struct mkac_xinput_config xinput; + struct gfx_config gfx; + struct amcus_config amcus; + struct bpreader_config reader; +}; + +void mkac_dll_config_load( + struct mkac_dll_config *cfg, + const wchar_t *filename); + +void mkac_xinput_config_load( + struct mkac_xinput_config *cfg, + const wchar_t *filename); + +void mkac_hook_config_load( + struct mkac_hook_config *cfg, + const wchar_t *filename); + diff --git a/mkachook/dllmain.c b/mkachook/dllmain.c new file mode 100644 index 0000000..4495472 --- /dev/null +++ b/mkachook/dllmain.c @@ -0,0 +1,103 @@ +#include +#include + +#include "mkachook/config.h" +#include "mkachook/mkac-dll.h" +#include "mkachook/xinput.h" +#include "mkachook/jvs.h" + +#include "amcus/amcus.h" + +#include "hook/process.h" + +#include "hooklib/serial.h" +#include "hooklib/debug.h" + +#include "platform/platform.h" +#include "gfxhook/gfx.h" +#include "gfxhook/dxgi.h" +#include "gfxhook/d3d11.h" +#include "board/bpreader.h" + +#include "util/dprintf.h" + +static HMODULE mkac_hook_mod; +static process_entry_t mkac_startup; +static struct mkac_hook_config mkac_hook_cfg; + +static DWORD CALLBACK mkac_pre_startup(void) +{ + HRESULT hr; + + dprintf("--- Begin mkac_pre_startup ---\n"); + + mkac_hook_config_load(&mkac_hook_cfg, L".\\bananatools.ini"); + + serial_hook_init(); + + struct dongle_info dinfo; + dinfo.vid = 0x0B9A; + dinfo.pid = 0x0C10; + wcscpy_s(dinfo.manufacturer, _countof(dinfo.manufacturer), L"BM"); + wcscpy_s(dinfo.product, _countof(dinfo.product), L"RUDI04GBN-274713"); + + hr = platform_hook_init(&mkac_hook_cfg.platform, PLATFORM_ES3, mkac_jvs_init, mkac_hook_mod, dinfo); + + if (FAILED(hr)) { + ExitProcess(EXIT_FAILURE); + } + + hr = mkac_dll_init(&mkac_hook_cfg.dll, mkac_hook_mod); + + if (FAILED(hr)) { + ExitProcess(EXIT_FAILURE); + } + + hr = mkac_xinput_init(&mkac_hook_cfg.xinput); + + if (FAILED(hr)) { + ExitProcess(EXIT_FAILURE); + } + + hr = amcus_hook_init(&mkac_hook_cfg.amcus); + + if (FAILED(hr)) { + ExitProcess(EXIT_FAILURE); + } + + hr = bpreader_init(&mkac_hook_cfg.reader, 4); + + if (FAILED(hr)) { + ExitProcess(EXIT_FAILURE); + } + + debug_hook_init(); + + gfx_hook_init(&mkac_hook_cfg.gfx); + gfx_d3d11_hook_init(&mkac_hook_cfg.gfx, mkac_hook_mod); + gfx_dxgi_hook_init(&mkac_hook_cfg.gfx, mkac_hook_mod); + + dprintf("--- End mkac_pre_startup ---\n"); + + return mkac_startup(); + +} + +BOOL WINAPI DllMain(HMODULE mod, DWORD cause, void *ctx) +{ + HRESULT hr; + + if (cause != DLL_PROCESS_ATTACH) { + return TRUE; + } + + mkac_hook_mod = mod; + + hr = process_hijack_startup(mkac_pre_startup, &mkac_startup); + + if (!SUCCEEDED(hr)) { + dprintf("Failed to hijack process startup: %x\n", (int) hr); + } + + return SUCCEEDED(hr); +} diff --git a/mkachook/jvs.c b/mkachook/jvs.c new file mode 100644 index 0000000..831328f --- /dev/null +++ b/mkachook/jvs.c @@ -0,0 +1,100 @@ +#include + +#include +#include +#include +#include +#include + +#include "hook/iobuf.h" +#include "hook/iohook.h" + +#include "hooklib/uart.h" +#include "hooklib/fdshark.h" + +#include "util/dprintf.h" +#include "util/dump.h" + +#include "board/najv4.h" + +#include "mkachook/jvs.h" +#include "mkachook/mkac-dll.h" + +static void mkac_jvs_read_switches(void *ctx, struct najv4_switch_state *out); +static void mkac_jvs_read_coin_counter(void *ctx, uint8_t slot_no, uint16_t *out); + +static const struct najv4_ops mkac_jvs_najv4_ops = { + .read_switches = mkac_jvs_read_switches, + .read_coin_counter = mkac_jvs_read_coin_counter, +}; + +static struct najv4 mkac_jvs_najv4; + +HRESULT mkac_jvs_init(struct jvs_node **out) +{ + HRESULT hr; + + assert(out != NULL); + assert(mkac_dll.jvs_init != NULL); + + dprintf("mkac JVS: Starting IO backend\n"); + hr = mkac_dll.jvs_init(); + + if (FAILED(hr)) { + dprintf("mkac JVS: Backend error, I/O disconnected: %x\n", (int) hr); + + return hr; + } + + najv4_init(&mkac_jvs_najv4, NULL, &mkac_jvs_najv4_ops, NULL); + *out = najv4_to_jvs_node(&mkac_jvs_najv4); + + return S_OK; +} + +static void mkac_jvs_read_switches(void *ctx, struct najv4_switch_state *out) +{ + uint8_t opbtn = 0; + + //dprintf("mkac JVS: Read Switches\n"); + + assert(out != NULL); + assert(mkac_dll.jvs_poll != NULL); + + mkac_dll.jvs_poll(&opbtn); + + out->system = 0; + out->p1 = 0; + out->p2 = 0; + + if (opbtn & 0x01) { // Test + out->system = 0x80; + } + if (opbtn & 0x02) { // Service + out->p1 |= 0x4000; + } + + if (opbtn & 0x04) { // Up + out->p1 |= 0x2000; + } + if (opbtn & 0x08) { // Down + out->p1 |= 0x1000; + } + if (opbtn & 0x10) { // Enter + out->p1 |= 0x0200; + } +} + +static void mkac_jvs_read_coin_counter(void *ctx, uint8_t slot_no, uint16_t *out) +{ + //dprintf("mkac JVS: Read coin counter\n"); + + assert(out != NULL); + assert(mkac_dll.jvs_read_coin_counter != NULL); + + if (slot_no > 0) { + return; + } + + mkac_dll.jvs_read_coin_counter(out); +} \ No newline at end of file diff --git a/mkachook/jvs.h b/mkachook/jvs.h new file mode 100644 index 0000000..235f952 --- /dev/null +++ b/mkachook/jvs.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include + +#include "jvs/jvs-bus.h" + +HRESULT mkac_jvs_init(struct jvs_node **root); \ No newline at end of file diff --git a/mkachook/meson.build b/mkachook/meson.build new file mode 100644 index 0000000..e1e2231 --- /dev/null +++ b/mkachook/meson.build @@ -0,0 +1,34 @@ +shared_library( + 'mkachook', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + vs_module_defs : 'mkachook.def', + c_pch : '../precompiled.h', + dependencies : [ + capnhook.get_variable('hook_dep'), + capnhook.get_variable('hooklib_dep'), + xinput_lib, + ], + link_with : [ + mkacio_lib, + amcus_lib, + platform_lib, + util_lib, + hooklib_lib, + gfxhook_lib, + jvs_lib, + board_lib, + ], + sources : [ + 'dllmain.c', + 'config.c', + 'config.h', + 'mkac-dll.c', + 'mkac-dll.h', + 'xinput.c', + 'xinput.h', + 'jvs.c', + 'jvs.h', + ], +) diff --git a/mkachook/mkac-dll.c b/mkachook/mkac-dll.c new file mode 100644 index 0000000..8a8f4d0 --- /dev/null +++ b/mkachook/mkac-dll.c @@ -0,0 +1,106 @@ +#include + +#include +#include + +#include "mkachook/mkac-dll.h" + +#include "util/dll-bind.h" +#include "util/dprintf.h" + +const struct dll_bind_sym mkac_dll_syms[] = { + { + .sym = "mkac_io_jvs_init", + .off = offsetof(struct mkac_dll, jvs_init), + },{ + .sym = "mkac_io_gamepad_init", + .off = offsetof(struct mkac_dll, gamepad_init), + }, { + .sym = "mkac_io_jvs_poll", + .off = offsetof(struct mkac_dll, jvs_poll), + }, { + .sym = "mkac_io_gamepad_poll", + .off = offsetof(struct mkac_dll, gamepad_poll), + }, { + .sym = "mkac_io_jvs_read_coin_counter", + .off = offsetof(struct mkac_dll, jvs_read_coin_counter), + } +}; + +struct mkac_dll mkac_dll; + +HRESULT mkac_dll_init(const struct mkac_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("mkac IO: Failed to load IO DLL: %lx: %S\n", + hr, + cfg->path); + + goto end; + } + + dprintf("mkac IO: Using custom IO DLL: %S\n", cfg->path); + src = owned; + } else { + owned = NULL; + src = self; + } + + get_api_version = (void *) GetProcAddress(src, "mkac_io_get_api_version"); + + if (get_api_version != NULL) { + mkac_dll.api_version = get_api_version(); + } else { + mkac_dll.api_version = 0x0100; + dprintf("Custom IO DLL does not expose mkac_io_get_api_version, " + "assuming API version 1.0.\n" + "Please ask the developer to update their DLL.\n"); + } + + if (mkac_dll.api_version >= 0x0200) { + hr = E_NOTIMPL; + dprintf("mkac IO: Custom IO DLL implements an unsupported " + "API version (%#04x). Please update Segatools.\n", + mkac_dll.api_version); + + goto end; + } + + sym = mkac_dll_syms; + hr = dll_bind(&mkac_dll, src, &sym, _countof(mkac_dll_syms)); + + if (FAILED(hr)) { + if (src != self) { + dprintf("mkac 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; +} \ No newline at end of file diff --git a/mkachook/mkac-dll.h b/mkachook/mkac-dll.h new file mode 100644 index 0000000..702167f --- /dev/null +++ b/mkachook/mkac-dll.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include "mkacio/mkacio.h" + +struct mkac_dll { + uint16_t api_version; + HRESULT (*jvs_init)(void); + HRESULT (*gamepad_init)(void); + HRESULT (*jvs_poll)(uint8_t *opbtn); + HRESULT (*gamepad_poll)(uint16_t *gamebtn); + void (*jvs_read_coin_counter)(uint16_t *coins); +}; + +struct mkac_dll_config { + wchar_t path[MAX_PATH]; +}; + +extern struct mkac_dll mkac_dll; + +HRESULT mkac_dll_init(const struct mkac_dll_config *cfg, HINSTANCE self); diff --git a/mkachook/mkachook.def b/mkachook/mkachook.def new file mode 100644 index 0000000..e6b2b1e --- /dev/null +++ b/mkachook/mkachook.def @@ -0,0 +1,9 @@ +LIBRARY mkachook + +EXPORTS + mkac_io_get_api_version + mkac_io_jvs_init + mkac_io_gamepad_init + mkac_io_jvs_poll + mkac_io_gamepad_poll + mkac_io_jvs_read_coin_counter \ No newline at end of file diff --git a/mkachook/xinput.c b/mkachook/xinput.c new file mode 100644 index 0000000..78eb5fb --- /dev/null +++ b/mkachook/xinput.c @@ -0,0 +1,154 @@ +#include +#include +#include + +#ifdef __GNUC__ +#include +#else +#include +#endif + +#include "hook/table.h" + +#include "mkachook/xinput.h" +#include "mkachook/mkac-dll.h" + +#include "util/dprintf.h" +#include "util/str.h" + +static DWORD WINAPI my_XInputGetCapabilities(DWORD dwUserIndex, DWORD dwFlags, XINPUT_CAPABILITIES *pCapabilities); +static DWORD WINAPI my_XInputGetState(DWORD dwUserIndex, XINPUT_STATE *pState); +static DWORD WINAPI my_XInputSetState(DWORD dwUserIndex, XINPUT_VIBRATION *pVibration); +static DWORD my_driverUtilGetControllerUsbIdPairs(uint64_t qwUnknown, unsigned int *numControllers); + +static DWORD (WINAPI *next_XInputGetCapabilities)(DWORD dwUserIndex, DWORD dwFlags, XINPUT_CAPABILITIES *pCapabilities); +static DWORD (WINAPI *next_XInputGetState)(DWORD dwUserIndex, DWORD dwFlags, XINPUT_CAPABILITIES *pCapabilities); +static DWORD (WINAPI *next_XInputSetState)(DWORD dwUserIndex, DWORD dwFlags, XINPUT_CAPABILITIES *pCapabilities); +static DWORD (*next_driverUtilGetControllerUsbIdPairs)(uint64_t qwUnknown, unsigned int *numControllers); + +uint8_t lastBtnState = 0; +DWORD packetNum = 0; + +static const struct hook_symbol xinput_hook_syms[] = { + { + .ordinal = 4, + .patch = my_XInputGetCapabilities, + .link = (void **) &next_XInputGetCapabilities, + }, { + .ordinal = 2, + .patch = my_XInputGetState, + .link = (void **) &next_XInputGetState, + }, { + .ordinal = 3, + .patch = my_XInputSetState, + .link = (void **) &next_XInputSetState, + }, +}; + +static const struct hook_symbol driverutil_hook_syms[] = { + { + .name = "driverUtilGetControllerUsbIdPairs", + .ordinal = 1, + .patch = my_driverUtilGetControllerUsbIdPairs, + .link = (void **) &next_driverUtilGetControllerUsbIdPairs, + } +}; + +HRESULT mkac_xinput_init(struct mkac_xinput_config *cfg) +{ + if (!cfg->enable) { + dprintf("Xinput: Emulation disabled\n"); + return S_OK; + } + + dprintf("Xinput: init\n"); + + hook_table_apply( + NULL, + "XINPUT1_3.dll", + xinput_hook_syms, + _countof(xinput_hook_syms)); + + hook_table_apply( + NULL, + "driverUtil.dll", + driverutil_hook_syms, + _countof(driverutil_hook_syms)); + + return S_OK; +} + +static DWORD WINAPI my_XInputGetCapabilities(DWORD dwUserIndex, DWORD dwFlags, XINPUT_CAPABILITIES *pCapabilities) +{ + //dprintf("Xinput: %s dwUserIndex %li dwFlags %li \n", __func__, dwUserIndex, dwFlags); + + if (!dwUserIndex) { + HRESULT hr = mkac_dll.gamepad_init(); + + if (FAILED(hr)) { + return ERROR_DEVICE_NOT_CONNECTED; + } + + pCapabilities->Flags = XINPUT_CAPS_VOICE_SUPPORTED; + pCapabilities->Type = XINPUT_DEVTYPE_GAMEPAD; + pCapabilities->SubType = XINPUT_DEVSUBTYPE_GAMEPAD; + + pCapabilities->Gamepad.wButtons = 0xF3FF; + + pCapabilities->Gamepad.bLeftTrigger = 0xFF; + pCapabilities->Gamepad.bRightTrigger = 0xFF; + + pCapabilities->Gamepad.sThumbLX = (SHORT)0xFFC0; + pCapabilities->Gamepad.sThumbLY = (SHORT)0xFFC0; + pCapabilities->Gamepad.sThumbRX = (SHORT)0xFFC0; + pCapabilities->Gamepad.sThumbRY = (SHORT)0xFFC0; + + pCapabilities->Vibration.wLeftMotorSpeed = 0xFF; + pCapabilities->Vibration.wRightMotorSpeed = 0xFF; + + return ERROR_SUCCESS; + } else { + return ERROR_DEVICE_NOT_CONNECTED; + } + +} + +static DWORD WINAPI my_XInputGetState(DWORD dwUserIndex, XINPUT_STATE *pState) +{ + //dprintf("Xinput: %s dwUserIndex %li\n", __func__, dwUserIndex); + assert(mkac_dll.gamepad_poll != NULL); + + if (!dwUserIndex) { + uint16_t gamebtn = 0; + mkac_dll.gamepad_poll(&gamebtn); + + pState->Gamepad.wButtons = gamebtn; + + if (gamebtn == lastBtnState) { + pState->dwPacketNumber = packetNum; + } else { + pState->dwPacketNumber = packetNum++; + } + + return ERROR_SUCCESS; + } + else { + return ERROR_DEVICE_NOT_CONNECTED; + } + +} + +static DWORD WINAPI my_XInputSetState(DWORD dwUserIndex, XINPUT_VIBRATION *pVibration) +{ + //dprintf("Xinput: %s dwUserIndex %li left %i right %i\n", __func__, dwUserIndex, pVibration->wLeftMotorSpeed, pVibration->wRightMotorSpeed); + if (!dwUserIndex) + return ERROR_SUCCESS; + else + return ERROR_DEVICE_NOT_CONNECTED; +} + +static DWORD my_driverUtilGetControllerUsbIdPairs(uint64_t qwUnknown, unsigned int *numControllers) +{ + dprintf("Xinput: %s hit!\n", __func__); + return 1; +} \ No newline at end of file diff --git a/mkachook/xinput.h b/mkachook/xinput.h new file mode 100644 index 0000000..33f3866 --- /dev/null +++ b/mkachook/xinput.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +#include +#include + +struct mkac_xinput_config { + bool enable; +}; + +HRESULT mkac_xinput_init(struct mkac_xinput_config *cfg); \ No newline at end of file diff --git a/mkacio/config.c b/mkacio/config.c new file mode 100644 index 0000000..9201a3c --- /dev/null +++ b/mkacio/config.c @@ -0,0 +1,32 @@ +#include + +#include +#include +#include + +#include "mkacio/config.h" + +void mkac_io_najv4_config_load(struct mkac_najv4_config *cfg, const wchar_t *filename) +{ + cfg->test = GetPrivateProfileIntW(L"najv4", L"test", VK_HOME, filename); + cfg->service = GetPrivateProfileIntW(L"najv4", L"service", VK_DELETE, filename); + cfg->coin = GetPrivateProfileIntW(L"najv4", L"coin", VK_INSERT, filename); + cfg->up = GetPrivateProfileIntW(L"najv4", L"up", VK_UP, filename); + cfg->down = GetPrivateProfileIntW(L"najv4", L"down", VK_DOWN, filename); + cfg->enter = GetPrivateProfileIntW(L"najv4", L"enter", VK_RETURN, filename); +} + +void mkac_io_gamepad_config_load(struct mkac_gamepad_config *cfg, const wchar_t *filename) +{ + cfg->dpad_up = GetPrivateProfileIntW(L"gamepad", L"dpad_up", 'W', filename); + cfg->dpad_down = GetPrivateProfileIntW(L"gamepad", L"dpad_down", 'S', filename); + cfg->dpad_left = GetPrivateProfileIntW(L"gamepad", L"dpad_left", 'A', filename); + cfg->dpad_right = GetPrivateProfileIntW(L"gamepad", L"dpad_right", 'D', filename); + cfg->btn_a = GetPrivateProfileIntW(L"gamepad", L"button_a", 'O', filename); + cfg->btn_b = GetPrivateProfileIntW(L"gamepad", L"button_b", 'K', filename); + cfg->btn_x = GetPrivateProfileIntW(L"gamepad", L"button_x", 'I', filename); + cfg->btn_y = GetPrivateProfileIntW(L"gamepad", L"button_y", 'J', filename); + cfg->trigger_l = GetPrivateProfileIntW(L"gamepad", L"trigger_l", 'Q', filename); + cfg->trigger_r = GetPrivateProfileIntW(L"gamepad", L"trigger_r", 'E', filename); + cfg->btn_start = GetPrivateProfileIntW(L"gamepad", L"button_start", VK_LSHIFT, filename); +} \ No newline at end of file diff --git a/mkacio/config.h b/mkacio/config.h new file mode 100644 index 0000000..7a9e3e5 --- /dev/null +++ b/mkacio/config.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +struct mkac_najv4_config { + uint8_t test; + uint8_t service; + uint8_t up; + uint8_t down; + uint8_t enter; + uint8_t coin; +}; + +struct mkac_gamepad_config { + uint8_t dpad_up; + uint8_t dpad_down; + uint8_t dpad_left; + uint8_t dpad_right; + uint8_t btn_a; + uint8_t btn_b; + uint8_t btn_x; + uint8_t btn_y; + uint8_t trigger_l; + uint8_t trigger_r; + uint8_t btn_start; +}; + +void mkac_io_najv4_config_load(struct mkac_najv4_config *cfg, const wchar_t *filename); +void mkac_io_gamepad_config_load(struct mkac_gamepad_config *cfg, const wchar_t *filename); \ No newline at end of file diff --git a/mkacio/meson.build b/mkacio/meson.build new file mode 100644 index 0000000..fed8e6c --- /dev/null +++ b/mkacio/meson.build @@ -0,0 +1,16 @@ +mkacio_lib = static_library( + 'mkacio', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + dependencies : [ + xinput_lib, + ], + sources : [ + 'mkacio.c', + 'mkacio.h', + 'config.c', + 'config.h', + ], +) diff --git a/mkacio/mkacio.c b/mkacio/mkacio.c new file mode 100644 index 0000000..6e16390 --- /dev/null +++ b/mkacio/mkacio.c @@ -0,0 +1,128 @@ +#include +#include + +#include +#include +#include + +#include "mkacio/mkacio.h" +#include "mkacio/config.h" + +#include "util/dprintf.h" + +static bool mkac_io_coin = false; +static bool mkac_test_toggle = false; +static uint16_t mkac_coin_ct = 0; +static struct mkac_gamepad_config gamepad_cfg; +static struct mkac_najv4_config najv4_cfg; + +uint16_t mkac_io_get_api_version(void) +{ + return 0x0100; +} + +HRESULT mkac_io_jvs_init(void) +{ + dprintf("mkac IO: JVS Init\n"); + mkac_io_najv4_config_load(&najv4_cfg, L".\\bananatools.ini"); + return S_OK; +} + +HRESULT mkac_io_gamepad_init(void) +{ + dprintf("mkac IO: Gamepad Init\n"); + mkac_io_gamepad_config_load(&gamepad_cfg, L".\\bananatools.ini"); + return S_OK; +} + +HRESULT mkac_io_jvs_poll(uint8_t *opbtn) +{ + *opbtn = 0; + + if ((GetAsyncKeyState(najv4_cfg.test) & 0x8000)) { + *opbtn |= mkac_IO_OPBTN_TEST; + } + + if (GetAsyncKeyState(najv4_cfg.service) & 0x8000) { + *opbtn |= mkac_IO_OPBTN_SERVICE; + } + + if (GetAsyncKeyState(najv4_cfg.up) & 0x8000) { + *opbtn |= mkac_IO_OPBTN_UP; + } + + if (GetAsyncKeyState(najv4_cfg.down) & 0x8000) { + *opbtn |= mkac_IO_OPBTN_DOWN; + } + + if (GetAsyncKeyState(najv4_cfg.enter) & 0x8000) { + *opbtn |= mkac_IO_OPBTN_ENTER; + } + + return S_OK; +} + +HRESULT mkac_io_gamepad_poll(uint16_t *gamepad) +{ + *gamepad = 0; + + if (GetAsyncKeyState(gamepad_cfg.dpad_up) & 0x8000) { + *gamepad |= mkac_IO_GAMEBTN_UP; + } + + if (GetAsyncKeyState(gamepad_cfg.dpad_left) & 0x8000) { + *gamepad |= mkac_IO_GAMEBTN_LEFT; + } + + if (GetAsyncKeyState(gamepad_cfg.dpad_down) & 0x8000) { + *gamepad |= mkac_IO_GAMEBTN_DOWN; + } + + if (GetAsyncKeyState(gamepad_cfg.dpad_right) & 0x8000) { + *gamepad |= mkac_IO_GAMEBTN_RIGHT; + } + + if (GetAsyncKeyState(gamepad_cfg.trigger_l) & 0x8000) { + *gamepad |= mkac_IO_GAMEBTN_TRIGGER_L; + } + + if (GetAsyncKeyState(gamepad_cfg.trigger_r) & 0x8000) { + *gamepad |= mkac_IO_GAMEBTN_TRIGGER_R; + } + + if (GetAsyncKeyState(gamepad_cfg.btn_a) & 0x8000) { + *gamepad |= mkac_IO_GAMEBTN_A; + } + + if (GetAsyncKeyState(gamepad_cfg.btn_b) & 0x8000) { + *gamepad |= mkac_IO_GAMEBTN_B; + } + + if (GetAsyncKeyState(gamepad_cfg.btn_x) & 0x8000) { + *gamepad |= mkac_IO_GAMEBTN_X; + } + + if (GetAsyncKeyState(gamepad_cfg.btn_y) & 0x8000) { + *gamepad |= mkac_IO_GAMEBTN_Y; + } + + if (GetAsyncKeyState(gamepad_cfg.btn_start) & 0x8000) { + *gamepad |= mkac_IO_GAMEBTN_START; + } + + return S_OK; +} + +void mkac_io_jvs_read_coin_counter(uint16_t *coins) +{ + if (GetAsyncKeyState(VK_INSERT) & 0x8000) { + if (!mkac_io_coin) { + mkac_io_coin = true; + mkac_coin_ct++; + } + } else { + mkac_io_coin = false; + } + + *coins = mkac_coin_ct; +} \ No newline at end of file diff --git a/mkacio/mkacio.def b/mkacio/mkacio.def new file mode 100644 index 0000000..8343a4a --- /dev/null +++ b/mkacio/mkacio.def @@ -0,0 +1,9 @@ +LIBRARY mkachook + +EXPORTS + mkac_io_get_api_version + mkac_io_jvs_init + mkac_io_gamepad_init + mkac_io_jvs_poll + mkac_io_gamepad_poll + mkac_io_read_coin_counter \ No newline at end of file diff --git a/mkacio/mkacio.h b/mkacio/mkacio.h new file mode 100644 index 0000000..1154db4 --- /dev/null +++ b/mkacio/mkacio.h @@ -0,0 +1,68 @@ +#pragma once + +#include + +#include + +#include "mkacio/config.h" + +enum { + mkac_IO_OPBTN_TEST = 0x01, + mkac_IO_OPBTN_SERVICE = 0x02, + mkac_IO_OPBTN_UP = 0x04, + mkac_IO_OPBTN_DOWN = 0x08, + mkac_IO_OPBTN_ENTER = 0x10, + mkac_IO_OPBTN_COIN = 0x20, +}; + +// Chagned to match xinput masks for ease of use +enum { + mkac_IO_GAMEBTN_UP = 0x0001, + mkac_IO_GAMEBTN_DOWN = 0x0002, + mkac_IO_GAMEBTN_LEFT = 0x0004, + mkac_IO_GAMEBTN_RIGHT = 0x0008, + mkac_IO_GAMEBTN_START = 0x0010, + mkac_IO_GAMEBTN_TRIGGER_L = 0x0100, + mkac_IO_GAMEBTN_TRIGGER_R = 0x0200, + mkac_IO_GAMEBTN_A = 0x2000, + mkac_IO_GAMEBTN_B = 0x1000, + mkac_IO_GAMEBTN_X = 0x8000, + mkac_IO_GAMEBTN_Y = 0x4000, +}; + +/* Get the version of the Pokken 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 mkac_io_get_api_version(void); + +/* Initialize the IO DLL. This is the second function that will be called on + your DLL, after mkac_io_get_api_version. + + All subsequent calls to this API may originate from arbitrary threads. + + Minimum API version: 0x0100 */ + +HRESULT mkac_io_jvs_init(void); + +/* Initialize the IO DLL. This is the second function that will be called on + your DLL, after mkac_io_get_api_version. + + All subsequent calls to this API may originate from arbitrary threads. + + Minimum API version: 0x0100 */ +HRESULT mkac_io_gamepad_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 mkac_io_jvs_poll(uint8_t *opbtn); + +HRESULT mkac_io_gamepad_poll(uint16_t *gamebtn); + +void mkac_io_jvs_read_coin_counter(uint16_t *coins); \ No newline at end of file diff --git a/saohook/config.c b/saohook/config.c new file mode 100644 index 0000000..5210e01 --- /dev/null +++ b/saohook/config.c @@ -0,0 +1,38 @@ +#include +#include + +#include "saohook/config.h" + +#include "platform/config.h" + +void sao_dll_config_load( + struct sao_dll_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + GetPrivateProfileStringW( + L"saoio", + L"path", + L"", + cfg->path, + _countof(cfg->path), + filename); +} + +void sao_hook_config_load( + struct sao_hook_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + platform_config_load(&cfg->platform, filename); + sao_dll_config_load(&cfg->dll, filename); + gfx_config_load(&cfg->gfx, filename); + qr_config_load(&cfg->qr, filename); + bpreader_config_load(&cfg->reader, filename); + usio_config_load(&cfg->usio, filename); + +} diff --git a/saohook/config.h b/saohook/config.h new file mode 100644 index 0000000..c8b98aa --- /dev/null +++ b/saohook/config.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include "saohook/sao-dll.h" + +#include "platform/config.h" +#include "gfxhook/config.h" +#include "amcus/config.h" +#include "board/config.h" + +struct sao_hook_config { + struct platform_config platform; + struct aime_config aime; + struct sao_dll_config dll; + struct gfx_config gfx; + struct amcus_config amcus; + struct qr_config qr; + struct bpreader_config reader; + struct usio_config usio; +}; + +void sao_dll_config_load( + struct sao_dll_config *cfg, + const wchar_t *filename); + +void qr_config_load( + struct qr_config *cfg, + const wchar_t *filename); + +void sao_hook_config_load( + struct sao_hook_config *cfg, + const wchar_t *filename); + diff --git a/saohook/dllmain.c b/saohook/dllmain.c new file mode 100644 index 0000000..0d6387d --- /dev/null +++ b/saohook/dllmain.c @@ -0,0 +1,107 @@ +#include +#include + +#include "saohook/config.h" +#include "saohook/sao-dll.h" +#include "saohook/usio.h" +#include "saohook/unity.h" + +#include "amcus/amcus.h" + +#include "hook/process.h" + +#include "hooklib/serial.h" +#include "board/sg-reader.h" +#include "board/vfd.h" + +#include "platform/platform.h" +#include "gfxhook/gfx.h" +#include "gfxhook/dxgi.h" +#include "gfxhook/d3d11.h" + +#include "util/dprintf.h" + +static HMODULE sao_hook_mod; +static process_entry_t sao_startup; +static struct sao_hook_config sao_hook_cfg; + +static DWORD CALLBACK sao_pre_startup(void) +{ + HRESULT hr; + + dprintf("--- Begin sao_pre_startup ---\n"); + + sao_hook_config_load(&sao_hook_cfg, L".\\bananatools.ini"); + + serial_hook_init(); + + struct dongle_info dinfo; + dinfo.pid = 0x0C00; + dinfo.vid = 0x0B9A; + + hr = platform_hook_init(&sao_hook_cfg.platform, PLATFORM_BNA1, NULL, sao_hook_mod, dinfo); + + if (FAILED(hr)) { + ExitProcess(EXIT_FAILURE); + } + + hr = sg_reader_hook_init(&sao_hook_cfg.aime, 1, sao_hook_mod); + + if (FAILED(hr)) { + ExitProcess(EXIT_FAILURE); + } + + hr = vfd_hook_init(2); + + if (FAILED(hr)) { + ExitProcess(EXIT_FAILURE); + } + + hr = sao_dll_init(&sao_hook_cfg.dll, sao_hook_mod); + + if (FAILED(hr)) { + ExitProcess(EXIT_FAILURE); + } + + hr = sao_usio_hook_init(&sao_hook_cfg.usio); + + if (FAILED(hr)) { + ExitProcess(EXIT_FAILURE); + } + + hr = amcus_hook_init(&sao_hook_cfg.amcus); + + if (FAILED(hr)) { + ExitProcess(EXIT_FAILURE); + } + + unity_hook_init(); + + gfx_hook_init(&sao_hook_cfg.gfx); + gfx_d3d11_hook_init(&sao_hook_cfg.gfx, sao_hook_mod); + gfx_dxgi_hook_init(&sao_hook_cfg.gfx, sao_hook_mod); + + dprintf("--- End sao_pre_startup ---\n"); + + return sao_startup(); + +} + +BOOL WINAPI DllMain(HMODULE mod, DWORD cause, void *ctx) +{ + HRESULT hr; + + if (cause != DLL_PROCESS_ATTACH) { + return TRUE; + } + + sao_hook_mod = mod; + + hr = process_hijack_startup(sao_pre_startup, &sao_startup); + + if (!SUCCEEDED(hr)) { + dprintf("Failed to hijack process startup: %x\n", (int) hr); + } + + return SUCCEEDED(hr); +} \ No newline at end of file diff --git a/saohook/meson.build b/saohook/meson.build new file mode 100644 index 0000000..7f58ec9 --- /dev/null +++ b/saohook/meson.build @@ -0,0 +1,33 @@ +shared_library( + 'saohook', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + vs_module_defs : 'saohook.def', + c_pch : '../precompiled.h', + dependencies : [ + capnhook.get_variable('hook_dep'), + capnhook.get_variable('hooklib_dep'), + xinput_lib, + ], + link_with : [ + saoio_lib, + amcus_lib, + platform_lib, + util_lib, + hooklib_lib, + gfxhook_lib, + jvs_lib, + board_lib + ], + sources : [ + 'dllmain.c', + 'config.c', + 'config.h', + 'sao-dll.c', + 'sao-dll.h', + 'usio.c', + 'usio.h', + 'unity.c', + ], +) diff --git a/saohook/sao-dll.c b/saohook/sao-dll.c new file mode 100644 index 0000000..7037e59 --- /dev/null +++ b/saohook/sao-dll.c @@ -0,0 +1,103 @@ +#include + +#include +#include + +#include "saohook/sao-dll.h" + +#include "util/dll-bind.h" +#include "util/dprintf.h" + +const struct dll_bind_sym sao_dll_syms[] = { + { + .sym = "sao_io_init", + .off = offsetof(struct sao_dll, init), + }, { + .sym = "sao_io_read_coin_counter", + .off = offsetof(struct sao_dll, read_coin_counter), + }, { + .sym = "sao_io_get_opbtns", + .off = offsetof(struct sao_dll, get_opbtns), + }, { + .sym = "sao_io_get_drum_analog", + .off = offsetof(struct sao_dll, get_drum_analog), + } +}; + +struct sao_dll sao_dll; + +HRESULT sao_dll_init(const struct sao_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("Sao IO: Failed to load IO DLL: %lx: %S\n", + hr, + cfg->path); + + goto end; + } + + dprintf("Sao IO: Using custom IO DLL: %S\n", cfg->path); + src = owned; + } else { + owned = NULL; + src = self; + } + + get_api_version = (void *) GetProcAddress(src, "sao_io_get_api_version"); + + if (get_api_version != NULL) { + sao_dll.api_version = get_api_version(); + } else { + sao_dll.api_version = 0x0100; + dprintf("Custom IO DLL does not expose sao_io_get_api_version, " + "assuming API version 1.0.\n" + "Please ask the developer to update their DLL.\n"); + } + + if (sao_dll.api_version >= 0x0200) { + hr = E_NOTIMPL; + dprintf("Sao IO: Custom IO DLL implements an unsupported " + "API version (%#04x). Please update Segatools.\n", + sao_dll.api_version); + + goto end; + } + + sym = sao_dll_syms; + hr = dll_bind(&sao_dll, src, &sym, _countof(sao_dll_syms)); + + if (FAILED(hr)) { + if (src != self) { + dprintf("Sao 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; +} \ No newline at end of file diff --git a/saohook/sao-dll.h b/saohook/sao-dll.h new file mode 100644 index 0000000..dfc3209 --- /dev/null +++ b/saohook/sao-dll.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include "saoio/saoio.h" + +struct sao_dll { + uint16_t api_version; + HRESULT (*init)(void); + HRESULT (*poll)(void); + void (*read_coin_counter)(uint16_t *coins, uint16_t *services); + void (*get_opbtns)(uint8_t *opbtn); + void (*get_drum_analog)(uint8_t *gamebtn); +}; + +struct sao_dll_config { + wchar_t path[MAX_PATH]; +}; + +extern struct sao_dll sao_dll; + +HRESULT sao_dll_init(const struct sao_dll_config *cfg, HINSTANCE self); diff --git a/saohook/saohook.def b/saohook/saohook.def new file mode 100644 index 0000000..c8fb6f5 --- /dev/null +++ b/saohook/saohook.def @@ -0,0 +1,8 @@ +LIBRARY saohook + +EXPORTS + sao_io_get_api_version + sao_io_init + sao_io_read_coin_counter + sao_io_get_drum_analog + sao_io_get_opbtns \ No newline at end of file diff --git a/saohook/unity.c b/saohook/unity.c new file mode 100644 index 0000000..97d32e3 --- /dev/null +++ b/saohook/unity.c @@ -0,0 +1,105 @@ +#include + +#include + +#include "hook/table.h" + +#include "hooklib/dll.h" +#include "hooklib/path.h" +#include "amcus/amcus.h" +#include "board/usio.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 const struct hook_symbol unity_kernel32_syms[] = { + { + .name = "LoadLibraryW", + .patch = my_LoadLibraryW, + .link = (void **) &next_LoadLibraryW, + }, +}; + +static const wchar_t *target_modules[] = { + L"mono.dll", + L"cri_ware_unity.dll", + L"aime_rw_adapter.dll", + L"AMPFServiceClient.dll", + L"bnAMPF.dll", + L"bnAMUpdater.dll", + L"bnReader.dll", + L"libamw.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_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); + amcus_insert_hooks(result); + usio_hook_proc_addr(result); + } + } + + return result; +} diff --git a/saohook/unity.h b/saohook/unity.h new file mode 100644 index 0000000..99c3bd9 --- /dev/null +++ b/saohook/unity.h @@ -0,0 +1,3 @@ +#pragma once + +void unity_hook_init(void); diff --git a/saohook/usio.c b/saohook/usio.c new file mode 100644 index 0000000..94bcf02 --- /dev/null +++ b/saohook/usio.c @@ -0,0 +1,82 @@ +#include + +#include +#include +#include +#include + +#include "board/usio.h" + +#include "saohook/sao-dll.h" + +#include "util/dprintf.h" + +bool sao_io_coin = false; +uint16_t sao_io_coins = 0; + +static HRESULT sao_usio_poll(void *ctx, struct usio_state *state); + +static const struct usio_ops sao_usio_ops = { + .poll = sao_usio_poll, +}; + +HRESULT sao_usio_hook_init(const struct usio_config *cfg) +{ + HRESULT hr; + assert(sao_dll.init != NULL); + + hr = usio_hook_init(cfg, &sao_usio_ops, NULL, NULL); + + if (FAILED(hr)) { + return hr; + } + + dprintf("Sao USIO: Init\n"); + + return sao_dll.init(); +} + +static HRESULT sao_usio_poll(void *ctx, struct usio_state *state) +{ + uint8_t opbtn_out = 0; + uint8_t analog_out = 0; + uint16_t coin_ct = 0; + uint16_t service_ct = 0; + sao_dll.get_opbtns(&opbtn_out); + sao_dll.get_drum_analog(&analog_out); + sao_dll.read_coin_counter(&coin_ct, &service_ct); + + state->op_btns = 0; + state->p1_btns = 0; + state->p2_btns = 0; + + if (opbtn_out & 0x01) { + state->op_btns |= 0x80; // Test + } + if (opbtn_out & 0x02) { + state->p1_btns |= 0x40; // Service + } + if (opbtn_out & 0x04) { + state->p1_btns |= 0x20; // Up + } + if (opbtn_out & 0x08) { + state->p1_btns |= 0x10; // Down + } + if (opbtn_out & 0x10) { + state->p1_btns |= 0x02; // Enter + } + + for (int i = 0; i < _countof(state->analog); i++) { + if (analog_out & 1 << i) { + state->analog[i] = 0x3FFF; + } else { + state->analog[i] = 0x00; + } + + } + + state->coins[0].current_coin_count = coin_ct; + state->service.current_coin_count = service_ct; + + return S_OK; +} \ No newline at end of file diff --git a/saohook/usio.h b/saohook/usio.h new file mode 100644 index 0000000..c13b7e1 --- /dev/null +++ b/saohook/usio.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "board/usio.h" + +HRESULT sao_usio_hook_init(const struct usio_config *cfg); \ No newline at end of file diff --git a/saoio/config.c b/saoio/config.c new file mode 100644 index 0000000..8123953 --- /dev/null +++ b/saoio/config.c @@ -0,0 +1,27 @@ +#include + +#include +#include +#include + +#include "saoio/config.h" + +void sao_io_config_load(struct sao_input_config *cfg, const wchar_t *filename) +{ + cfg->test = GetPrivateProfileIntW(L"usio", L"test", VK_HOME, filename); + cfg->service = GetPrivateProfileIntW(L"usio", L"service", VK_DELETE, filename); + cfg->coin = GetPrivateProfileIntW(L"usio", L"coin", VK_INSERT, filename); + cfg->up = GetPrivateProfileIntW(L"usio", L"up", VK_UP, filename); + cfg->down = GetPrivateProfileIntW(L"usio", L"down", VK_DOWN, filename); + cfg->enter = GetPrivateProfileIntW(L"usio", L"enter", VK_RETURN, filename); + + cfg->p1_rim_l = GetPrivateProfileIntW(L"drum", L"p1_rim_l", 'Z', filename); + cfg->p1_center_l = GetPrivateProfileIntW(L"usio", L"p1_center_l", 'X', filename); + cfg->p1_center_r = GetPrivateProfileIntW(L"usio", L"p1_center_r", 'C', filename); + cfg->p1_rim_r = GetPrivateProfileIntW(L"usio", L"p1_rim_r", 'V', filename); + + cfg->p2_rim_l = GetPrivateProfileIntW(L"drum", L"p2_rim_l", 'U', filename); + cfg->p2_center_l = GetPrivateProfileIntW(L"usio", L"p2_center_l", 'I', filename); + cfg->p2_center_r = GetPrivateProfileIntW(L"usio", L"p2_center_r", 'O', filename); + cfg->p2_rim_r = GetPrivateProfileIntW(L"usio", L"p2_rim_r", 'P', filename); +} \ No newline at end of file diff --git a/saoio/config.h b/saoio/config.h new file mode 100644 index 0000000..84e4e0d --- /dev/null +++ b/saoio/config.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +#pragma pack(push, 1) +struct sao_input_config { + uint8_t test; + uint8_t service; + uint8_t up; + uint8_t down; + uint8_t enter; + uint8_t coin; + + uint8_t p1_rim_l; + uint8_t p1_center_l; + uint8_t p1_center_r; + uint8_t p1_rim_r; + uint8_t p2_rim_l; + uint8_t p2_center_l; + uint8_t p2_center_r; + uint8_t p2_rim_r; +}; +#pragma pack(pop) + +void sao_io_config_load(struct sao_input_config *cfg, const wchar_t *filename); \ No newline at end of file diff --git a/saoio/meson.build b/saoio/meson.build new file mode 100644 index 0000000..afa481f --- /dev/null +++ b/saoio/meson.build @@ -0,0 +1,16 @@ +saoio_lib = static_library( + 'saoio', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + dependencies : [ + xinput_lib, + ], + sources : [ + 'saoio.c', + 'saoio.h', + 'config.c', + 'config.h', + ], +) diff --git a/saoio/saoio.c b/saoio/saoio.c new file mode 100644 index 0000000..fe5c550 --- /dev/null +++ b/saoio/saoio.c @@ -0,0 +1,123 @@ +#include +#include + +#include +#include +#include + +#include "saoio/saoio.h" +#include "saoio/config.h" + +#include "util/dprintf.h" + +static bool sao_io_coin = false; +static bool sao_io_service = false; +static bool sao_test_toggle = false; +static bool sao_test_last_state = false; +static uint16_t sao_coin_ct = 0; +static uint16_t sao_service_ct = 0; +static struct sao_input_config cfg; + +uint16_t sao_io_get_api_version(void) +{ + return 0x0100; +} + +HRESULT sao_io_init(void) +{ + dprintf("Sao IO: Init\n"); + sao_io_config_load(&cfg, L".\\bananatools.ini"); + return S_OK; +} + +void sao_io_get_opbtns(uint8_t *opbtn) +{ + if ((GetAsyncKeyState(cfg.test) & 0x8000)) { + if (!sao_test_last_state) { + sao_test_toggle = !sao_test_toggle; + } + sao_test_last_state = true; + } else { + sao_test_last_state = false; + } + + if (GetAsyncKeyState(cfg.service) & 0x8000) { + *opbtn |= SAO_IO_OPBTN_SERVICE; + } + + if (GetAsyncKeyState(cfg.up) & 0x8000) { + *opbtn |= SAO_IO_OPBTN_UP; + } + + if (GetAsyncKeyState(cfg.down) & 0x8000) { + *opbtn |= SAO_IO_OPBTN_DOWN; + } + + if (GetAsyncKeyState(cfg.enter) & 0x8000) { + *opbtn |= SAO_IO_OPBTN_ENTER; + } + + if (sao_test_toggle) { + *opbtn |= SAO_IO_OPBTN_TEST; + } +} + +void sao_io_get_drum_analog(uint8_t *gamebtn) +{ + if (GetAsyncKeyState(cfg.p1_rim_l) & 0x8000) { + *gamebtn |= SAO_IO_P1_RIM_L; + } + + if (GetAsyncKeyState(cfg.p1_center_l) & 0x8000) { + *gamebtn |= SAO_IO_P1_CENTER_L; + } + + if (GetAsyncKeyState(cfg.p1_center_r) & 0x8000) { + *gamebtn |= SAO_IO_P1_CENTER_R; + } + + if (GetAsyncKeyState(cfg.p1_rim_r) & 0x8000) { + *gamebtn |= SAO_IO_P1_RIM_R; + } + + if (GetAsyncKeyState(cfg.p2_rim_l) & 0x8000) { + *gamebtn |= SAO_IO_P2_RIM_L; + } + + if (GetAsyncKeyState(cfg.p2_center_l) & 0x8000) { + *gamebtn |= SAO_IO_P2_CENTER_L; + } + + if (GetAsyncKeyState(cfg.p2_center_r) & 0x8000) { + *gamebtn |= SAO_IO_P2_CENTER_R; + } + + if (GetAsyncKeyState(cfg.p2_rim_r) & 0x8000) { + *gamebtn |= SAO_IO_P2_RIM_R; + } +} + +void sao_io_read_coin_counter(uint16_t *coins, uint16_t *services) +{ + + if (GetAsyncKeyState(cfg.coin) & 0x8000) { + if (!sao_io_coin) { + sao_io_coin = true; + sao_coin_ct++; + } + } else { + sao_io_coin = false; + } + + if (GetAsyncKeyState(cfg.service) & 0x8000) { + if (!sao_io_service) { + sao_io_service = true; + sao_service_ct++; + } + } else { + sao_io_service = false; + } + + *coins = sao_coin_ct; + *services = sao_service_ct; +} \ No newline at end of file diff --git a/saoio/saoio.def b/saoio/saoio.def new file mode 100644 index 0000000..c8fb6f5 --- /dev/null +++ b/saoio/saoio.def @@ -0,0 +1,8 @@ +LIBRARY saohook + +EXPORTS + sao_io_get_api_version + sao_io_init + sao_io_read_coin_counter + sao_io_get_drum_analog + sao_io_get_opbtns \ No newline at end of file diff --git a/saoio/saoio.h b/saoio/saoio.h new file mode 100644 index 0000000..cfa0c40 --- /dev/null +++ b/saoio/saoio.h @@ -0,0 +1,67 @@ +#pragma once + +#include + +#include + +#include "saoio/config.h" + +enum { + SAO_IO_OPBTN_TEST = 0x01, + SAO_IO_OPBTN_SERVICE = 0x02, + SAO_IO_OPBTN_UP = 0x04, + SAO_IO_OPBTN_DOWN = 0x08, + SAO_IO_OPBTN_ENTER = 0x10, +}; + +enum { + SAO_IO_P1_RIM_L = 0x0001, + SAO_IO_P1_CENTER_L = 0x0002, + SAO_IO_P1_CENTER_R = 0x0004, + SAO_IO_P1_RIM_R = 0x0008, + SAO_IO_P2_RIM_L = 0x0100, + SAO_IO_P2_CENTER_L = 0x0200, + SAO_IO_P2_CENTER_R = 0x1000, + SAO_IO_P2_RIM_R = 0x2000, +}; + +/* Get the version of the Pokken 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 sao_io_get_api_version(void); + +/* Initialize the IO DLL. This is the second function that will be called on + your DLL, after sao_io_get_api_version. + + All subsequent calls to this API may originate from arbitrary threads. + + Minimum API version: 0x0100 */ + +HRESULT sao_io_init(void); + + +/* Get the state of the cabinet's operator buttons as of the last poll. See + SAO_IO_OPBTN enum above: this contains bit mask definitions for button + states returned in *opbtn. All buttons are active-high. + + Minimum API version: 0x0100 */ + +void sao_io_get_opbtns(uint8_t *opbtn); + +/* Get the state of the cabinet's gameplay buttons as of the last poll. See + SAO_IO_GAMEBTN enum above for bit mask definitions. Inputs are split into + a left hand side set of inputs and a right hand side set of inputs: the bit + mappings are the same in both cases. + + All buttons are active-high, even though some buttons' electrical signals + on a real cabinet are active-low. + + Minimum API version: 0x0100 */ + +void sao_io_get_drum_analog(uint8_t *gamebtn); + +void sao_io_read_coin_counter(uint16_t *coins, uint16_t *services); \ No newline at end of file