From 0ee081ca1bf2cba8ba8ad89801992afda8ff640b Mon Sep 17 00:00:00 2001 From: Dniel97 Date: Fri, 14 Jul 2023 01:00:28 +0200 Subject: [PATCH] mai2: segatools added --- README.md | 6 ++ dist/mai2/segatools.ini | 70 ++++++++++++++++++ dist/mai2/start.bat | 11 +++ mai2hook/config.c | 41 +++++++++++ mai2hook/config.h | 27 +++++++ mai2hook/dllmain.c | 116 ++++++++++++++++++++++++++++++ mai2hook/io4.c | 153 ++++++++++++++++++++++++++++++++++++++++ mai2hook/io4.h | 7 ++ mai2hook/mai2-dll.c | 109 ++++++++++++++++++++++++++++ mai2hook/mai2-dll.h | 21 ++++++ mai2hook/mai2hook.def | 18 +++++ mai2hook/meson.build | 31 ++++++++ mai2hook/unity.c | 95 +++++++++++++++++++++++++ mai2hook/unity.h | 3 + mai2io/config.c | 48 +++++++++++++ mai2io/config.h | 18 +++++ mai2io/mai2io.c | 143 +++++++++++++++++++++++++++++++++++++ mai2io/mai2io.h | 68 ++++++++++++++++++ mai2io/meson.build | 13 ++++ meson.build | 2 + 20 files changed, 1000 insertions(+) create mode 100644 dist/mai2/segatools.ini create mode 100644 dist/mai2/start.bat create mode 100644 mai2hook/config.c create mode 100644 mai2hook/config.h create mode 100644 mai2hook/dllmain.c create mode 100644 mai2hook/io4.c create mode 100644 mai2hook/io4.h create mode 100644 mai2hook/mai2-dll.c create mode 100644 mai2hook/mai2-dll.h create mode 100644 mai2hook/mai2hook.def create mode 100644 mai2hook/meson.build create mode 100644 mai2hook/unity.c create mode 100644 mai2hook/unity.h create mode 100644 mai2io/config.c create mode 100644 mai2io/config.h create mode 100644 mai2io/mai2io.c create mode 100644 mai2io/mai2io.h create mode 100644 mai2io/meson.build diff --git a/README.md b/README.md index 4f48d8d..7077fde 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,12 @@ Loaders and hardware emulators for SEGA games that run on the Nu and ALLS platfo * Initial D The Arcade * SEGA World Drivers Championship * SEGA World Drivers Championship 2019 +* ONGEKI + * bright MEMORY +* maimai DX + * maimai DX FESTiVAL +* Card Maker + * Card Maker 1.35 * Wacca * Wacca Lilly R (WIP) diff --git a/dist/mai2/segatools.ini b/dist/mai2/segatools.ini new file mode 100644 index 0000000..8676568 --- /dev/null +++ b/dist/mai2/segatools.ini @@ -0,0 +1,70 @@ +[vfs] +; Insert the path to the game AMFS directory here (contains ICF1 and ICF2) +amfs= +; Insert the path to the game Option directory here (contains Axxx, Bxxx directories) +option= +; 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= + +[aime] +; Enable aime reader emulation. +enable=1 + +[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= + +[netenv] +; Simulate an ideal LAN environment. This may interfere with head-to-head play. +; SEGA games are somewhat picky about its 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.100.0 + +; ----------------------------------------------------------------------------- +; Input settings +; ----------------------------------------------------------------------------- + +; Keyboard bindings are specified as hexadecimal (prefixed with 0x) or decimal +; (not prefixed with 0x) virtual-key codes, a list of which can be found here: +; +; https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes +; +; This is, admittedly, not the most user-friendly configuration method in the +; world. An improved solution will be provided later. + +[io4] +; Test button virtual-key code. Default is the 1 key. +test=0x31 +; Service button virtual-key code. Default is the 2 key. +service=0x32 +; Keyboard button to increment coin counter. Default is the 3 key. +coin=0x33 + +; Key bindings for buttons around screen. The default key map, depicted +; in clockwise order, is as follows: +; +; Player 1 Ring buttons: WEDCXZAQ, Select button: 3 +; Player 2 Ring buttons: (Numpad) 89632147, Select button: (Numpad) * +; +; Select buttons are considered as button 9. +; +; Uncomment and complete the following sequence of settings to configure a +; custom keybinding. +[button] +;1p_btn1=0x53 +;1p_btn2=0x53 +;1p_btn3=0x53 +; ... etc ... +;2p_btn1=0x53 +;2p_btn2=0x53 +;2p_btn3=0x53 +; ... etc ... diff --git a/dist/mai2/start.bat b/dist/mai2/start.bat new file mode 100644 index 0000000..a89a1d5 --- /dev/null +++ b/dist/mai2/start.bat @@ -0,0 +1,11 @@ +@echo off + +pushd %~dp0 + +start /min inject -d -k mai2hook.dll amdaemon.exe -f -c config_common.json config_server.json config_client.json +inject -d -k mai2hook.dll sinmai -screen-fullscreen 0 +taskkill /f /im amdaemon.exe > nul 2>&1 + +echo. +echo Game processes have terminated +pause \ No newline at end of file diff --git a/mai2hook/config.c b/mai2hook/config.c new file mode 100644 index 0000000..87615e9 --- /dev/null +++ b/mai2hook/config.c @@ -0,0 +1,41 @@ +#include +#include + +#include "board/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 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); + mai2_dll_config_load(&cfg->dll, filename); +} diff --git a/mai2hook/config.h b/mai2hook/config.h new file mode 100644 index 0000000..3b7fc22 --- /dev/null +++ b/mai2hook/config.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include "board/config.h" + +#include "hooklib/dvd.h" + +#include "mai2hook/mai2-dll.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 mai2_dll_config dll; +}; + +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); diff --git a/mai2hook/dllmain.c b/mai2hook/dllmain.c new file mode 100644 index 0000000..3232d6c --- /dev/null +++ b/mai2hook/dllmain.c @@ -0,0 +1,116 @@ +#include + +#include "board/io4.h" +#include "board/sg-reader.h" +#include "board/vfd.h" + +#include "hook/process.h" + +#include "hooklib/serial.h" +#include "hooklib/spike.h" + +#include "mai2hook/config.h" +#include "mai2hook/io4.h" +#include "mai2hook/mai2-dll.h" +#include "mai2hook/unity.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; + +/* This hook is based on mu3hook, with leaked mai2hook i/o codes. */ + +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"); + + /* Hook Win32 APIs */ + + dvd_hook_init(&mai2_hook_cfg.dvd, 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; + } + + /* 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); +} diff --git a/mai2hook/io4.c b/mai2hook/io4.c new file mode 100644 index 0000000..4eb7f6b --- /dev/null +++ b/mai2hook/io4.c @@ -0,0 +1,153 @@ +#include + +#include +#include +#include + +#include "board/io4.h" + +#include "mai2hook/mai2-dll.h" + +#include "util/dprintf.h" + +static HRESULT mai2_io4_poll(void *ctx, struct io4_state *state); +static uint16_t coins; + +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; + uint16_t player1; + uint16_t player2; + HRESULT hr; + + assert(mai2_dll.poll != NULL); + assert(mai2_dll.get_opbtns != NULL); + assert(mai2_dll.get_gamebtns != NULL); + + memset(state, 0, sizeof(*state)); + + hr = mai2_dll.poll(); + + if (FAILED(hr)) { + return hr; + } + + opbtn = 0; + player1 = 0; + player2 = 0; + + mai2_dll.get_opbtns(&opbtn); + mai2_dll.get_gamebtns(&player1, &player2); + + 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_OPBTN_COIN) { + coins++; + } + state->chutes[0] = coins << 8; + + // Buttons around screen are active-low, select button is active-high + + // Player 1 + + 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 (player1 & MAI2_IO_GAMEBTN_SELECT) { + state->buttons[0] |= 1 << 1; + } + + // Player 2 + + 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 (player2 & MAI2_IO_GAMEBTN_SELECT) { + state->buttons[1] |= 1 << 4; + } + + return S_OK; +} diff --git a/mai2hook/io4.h b/mai2hook/io4.h new file mode 100644 index 0000000..5a0a2a0 --- /dev/null +++ b/mai2hook/io4.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "board/io4.h" + +HRESULT mai2_io4_hook_init(const struct io4_config *cfg); diff --git a/mai2hook/mai2-dll.c b/mai2hook/mai2-dll.c new file mode 100644 index 0000000..dd172c3 --- /dev/null +++ b/mai2hook/mai2-dll.c @@ -0,0 +1,109 @@ +#include + +#include +#include + +#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), + }, { + .sym = "mai2_io_get_opbtns", + .off = offsetof(struct mai2_dll, get_opbtns), + }, { + .sym = "mai2_io_get_gamebtns", + .off = offsetof(struct mai2_dll, get_gamebtns), + } +}; + +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("Maimai DX IO: Failed to load IO DLL: %lx: %S\n", + hr, + cfg->path); + + goto end; + } + + dprintf("Maimai DX 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("Maimai DX 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("Maimai DX 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; +} diff --git a/mai2hook/mai2-dll.h b/mai2hook/mai2-dll.h new file mode 100644 index 0000000..6e53454 --- /dev/null +++ b/mai2hook/mai2-dll.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include "mai2io/mai2io.h" + +struct mai2_dll { + uint16_t api_version; + HRESULT (*init)(void); + HRESULT (*poll)(void); + void (*get_opbtns)(uint8_t *opbtn); + void (*get_gamebtns)(uint16_t *player1, uint16_t *player2); +}; + +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); diff --git a/mai2hook/mai2hook.def b/mai2hook/mai2hook.def new file mode 100644 index 0000000..2d0ac8b --- /dev/null +++ b/mai2hook/mai2hook.def @@ -0,0 +1,18 @@ +LIBRARY mai2hook + +EXPORTS + 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_get_gamebtns + mai2_io_get_opbtns + mai2_io_init + mai2_io_poll \ No newline at end of file diff --git a/mai2hook/meson.build b/mai2hook/meson.build new file mode 100644 index 0000000..8578c51 --- /dev/null +++ b/mai2hook/meson.build @@ -0,0 +1,31 @@ +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'), + ], + link_with : [ + aimeio_lib, + board_lib, + hooklib_lib, + mai2io_lib, + platform_lib, + util_lib, + ], + sources : [ + 'config.c', + 'config.h', + 'dllmain.c', + 'io4.c', + 'io4.h', + 'mai2-dll.c', + 'mai2-dll.h', + 'unity.h', + 'unity.c', + ], +) diff --git a/mai2hook/unity.c b/mai2hook/unity.c new file mode 100644 index 0000000..64195be --- /dev/null +++ b/mai2hook/unity.c @@ -0,0 +1,95 @@ +#include + +#include + +#include "hook/table.h" + +#include "hooklib/dll.h" +#include "hooklib/path.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-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_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); + } + } + + return result; +} diff --git a/mai2hook/unity.h b/mai2hook/unity.h new file mode 100644 index 0000000..99c3bd9 --- /dev/null +++ b/mai2hook/unity.h @@ -0,0 +1,3 @@ +#pragma once + +void unity_hook_init(void); diff --git a/mai2io/config.c b/mai2io/config.c new file mode 100644 index 0000000..75e842a --- /dev/null +++ b/mai2io/config.c @@ -0,0 +1,48 @@ +#include + +#include +#include +#include + +#include "mai2io/config.h" + +/* +Maimai DX Default key binding +1P: self-explanatory +2P: (Numpad) 8, 9, 6, 3, 2, 1, 4, 7, * +*/ +static const int mai2_io_1p_default[] = {'W', 'E', 'D', 'C', 'X', 'Z', 'A', 'Q', '3'}; +static const int mai2_io_2p_default[] = {0x68, 0x69, 0x66, 0x63, 0x62, 0x61, 0x64, 0x67, 0x54}; + +void mai2_io_config_load( + struct mai2_io_config *cfg, + const wchar_t *filename) +{ + wchar_t key[16]; + int i; + + assert(cfg != NULL); + assert(filename != NULL); + + cfg->vk_test = GetPrivateProfileIntW(L"io4", L"test", '1', filename); + cfg->vk_service = GetPrivateProfileIntW(L"io4", L"service", '2', filename); + cfg->vk_coin = GetPrivateProfileIntW(L"io4", L"coin", '3', filename); + + for (i = 0 ; i < 9 ; i++) { + swprintf_s(key, _countof(key), L"1p_btn%i", i + 1); + cfg->vk_1p_btn[i] = GetPrivateProfileIntW( + L"button", + key, + mai2_io_1p_default[i], + filename); + } + + for (i = 0 ; i < 9 ; i++) { + swprintf_s(key, _countof(key), L"2p_btn%i", i + 1); + cfg->vk_2p_btn[i] = GetPrivateProfileIntW( + L"button", + key, + mai2_io_2p_default[i], + filename); + } +} diff --git a/mai2io/config.h b/mai2io/config.h new file mode 100644 index 0000000..568b832 --- /dev/null +++ b/mai2io/config.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +#include + +struct mai2_io_config { + uint8_t vk_test; + uint8_t vk_service; + uint8_t vk_coin; + uint8_t vk_1p_btn[9]; + uint8_t vk_2p_btn[9]; +}; + +void mai2_io_config_load( + struct mai2_io_config *cfg, + const wchar_t *filename); diff --git a/mai2io/mai2io.c b/mai2io/mai2io.c new file mode 100644 index 0000000..40d6bfb --- /dev/null +++ b/mai2io/mai2io.c @@ -0,0 +1,143 @@ +#include + +#include +#include + +#include "mai2io/mai2io.h" +#include "mai2io/config.h" + +static uint8_t mai2_opbtn; +static uint16_t mai2_player1_btn; +static uint16_t mai2_player2_btn; +static struct mai2_io_config mai2_io_cfg; +static bool mai2_io_coin; + +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(void) +{ + mai2_opbtn = 0; + mai2_player1_btn = 0; + mai2_player2_btn = 0; + + if (GetAsyncKeyState(mai2_io_cfg.vk_test) & 0x8000) { + mai2_opbtn |= MAI2_IO_OPBTN_TEST; + } + + if (GetAsyncKeyState(mai2_io_cfg.vk_service) & 0x8000) { + mai2_opbtn |= MAI2_IO_OPBTN_SERVICE; + } + + if (GetAsyncKeyState(mai2_io_cfg.vk_coin) & 0x8000) { + if (!mai2_io_coin) { + mai2_io_coin = true; + mai2_opbtn |= MAI2_IO_OPBTN_COIN; + } + } else { + mai2_io_coin = false; + } + + //Player 1 + if (GetAsyncKeyState(mai2_io_cfg.vk_1p_btn[0])) { + mai2_player1_btn |= MAI2_IO_GAMEBTN_1; + } + + if (GetAsyncKeyState(mai2_io_cfg.vk_1p_btn[1])) { + mai2_player1_btn |= MAI2_IO_GAMEBTN_2; + } + + if (GetAsyncKeyState(mai2_io_cfg.vk_1p_btn[2])) { + mai2_player1_btn |= MAI2_IO_GAMEBTN_3; + } + + if (GetAsyncKeyState(mai2_io_cfg.vk_1p_btn[3])) { + mai2_player1_btn |= MAI2_IO_GAMEBTN_4; + } + + if (GetAsyncKeyState(mai2_io_cfg.vk_1p_btn[4])) { + mai2_player1_btn |= MAI2_IO_GAMEBTN_5; + } + + if (GetAsyncKeyState(mai2_io_cfg.vk_1p_btn[5])) { + mai2_player1_btn |= MAI2_IO_GAMEBTN_6; + } + + if (GetAsyncKeyState(mai2_io_cfg.vk_1p_btn[6])) { + mai2_player1_btn |= MAI2_IO_GAMEBTN_7; + } + + if (GetAsyncKeyState(mai2_io_cfg.vk_1p_btn[7])) { + mai2_player1_btn |= MAI2_IO_GAMEBTN_8; + } + + if (GetAsyncKeyState(mai2_io_cfg.vk_1p_btn[8])) { + mai2_player1_btn |= MAI2_IO_GAMEBTN_SELECT; + } + + //Player 2 + if (GetAsyncKeyState(mai2_io_cfg.vk_2p_btn[0])) { + mai2_player2_btn |= MAI2_IO_GAMEBTN_1; + } + + if (GetAsyncKeyState(mai2_io_cfg.vk_2p_btn[1])) { + mai2_player2_btn |= MAI2_IO_GAMEBTN_2; + } + + if (GetAsyncKeyState(mai2_io_cfg.vk_2p_btn[2])) { + mai2_player2_btn |= MAI2_IO_GAMEBTN_3; + } + + if (GetAsyncKeyState(mai2_io_cfg.vk_2p_btn[3])) { + mai2_player2_btn |= MAI2_IO_GAMEBTN_4; + } + + if (GetAsyncKeyState(mai2_io_cfg.vk_2p_btn[4])) { + mai2_player2_btn |= MAI2_IO_GAMEBTN_5; + } + + if (GetAsyncKeyState(mai2_io_cfg.vk_2p_btn[5])) { + mai2_player2_btn |= MAI2_IO_GAMEBTN_6; + } + + if (GetAsyncKeyState(mai2_io_cfg.vk_2p_btn[6])) { + mai2_player2_btn |= MAI2_IO_GAMEBTN_7; + } + + if (GetAsyncKeyState(mai2_io_cfg.vk_2p_btn[7])) { + mai2_player2_btn |= MAI2_IO_GAMEBTN_8; + } + + if (GetAsyncKeyState(mai2_io_cfg.vk_2p_btn[8])) { + mai2_player2_btn |= MAI2_IO_GAMEBTN_SELECT; + } + + return S_OK; +} + +void mai2_io_get_opbtns(uint8_t *opbtn) +{ + if (opbtn != NULL) { + *opbtn = mai2_opbtn; + } +} + +void mai2_io_get_gamebtns(uint16_t *player1, uint16_t *player2) +{ + if (player1 != NULL) { + *player1 = mai2_player1_btn; + } + + if (player2 != NULL ){ + *player2 = mai2_player2_btn; + } +} \ No newline at end of file diff --git a/mai2io/mai2io.h b/mai2io/mai2io.h new file mode 100644 index 0000000..084228c --- /dev/null +++ b/mai2io/mai2io.h @@ -0,0 +1,68 @@ +#pragma once + +#include + +#include + +enum { + MAI2_IO_OPBTN_TEST = 0x01, + MAI2_IO_OPBTN_SERVICE = 0x02, + MAI2_IO_OPBTN_COIN = 0x04, +}; + +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, + MAI2_IO_GAMEBTN_SELECT = 0x100, +}; + +/* Get the version of the Maimai 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(void); + +/* Get the state of the cabinet's operator buttons as of the last poll. See + MAI2_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 mai2_io_get_opbtns(uint8_t *opbtn); + +/* Get the state of the cabinet's gameplay buttons as of the last poll. See + MAI2_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 mai2_io_get_gamebtns(uint16_t *player1, uint16_t *player2); diff --git a/mai2io/meson.build b/mai2io/meson.build new file mode 100644 index 0000000..c448136 --- /dev/null +++ b/mai2io/meson.build @@ -0,0 +1,13 @@ +mai2io_lib = static_library( + 'mai2io', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + sources : [ + 'mai2io.c', + 'mai2io.h', + 'config.c', + 'config.h', + ], +) diff --git a/meson.build b/meson.build index 851e57a..2032f0e 100644 --- a/meson.build +++ b/meson.build @@ -61,6 +61,7 @@ subdir('idzio') subdir('idacio') subdir('swdcio') subdir('mu3io') +subdir('mai2io') subdir('cmio') subdir('mercuryio') subdir('cxbio') @@ -74,6 +75,7 @@ subdir('swdchook') subdir('minihook') subdir('chusanhook') subdir('mu3hook') +subdir('mai2hook') subdir('cmhook') subdir('mercuryhook') subdir('cxbhook')