From ea60cc4a18d7f2f2e063b6ec8a627bb2eba82958 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Mon, 18 Sep 2023 04:15:27 -0400 Subject: [PATCH] add tekken --- Package.mk | 12 +++++ dist/tekken/bananatools.ini | 58 +++++++++++++++++++++ dist/tekken/start.bat | 10 ++++ meson.build | 4 +- tekkenhook/config.c | 35 +++++++++++++ tekkenhook/config.h | 28 ++++++++++ tekkenhook/dllmain.c | 96 ++++++++++++++++++++++++++++++++++ tekkenhook/jvs.c | 100 ++++++++++++++++++++++++++++++++++++ tekkenhook/jvs.h | 8 +++ tekkenhook/meson.build | 31 +++++++++++ tekkenhook/tekken-dll.c | 100 ++++++++++++++++++++++++++++++++++++ tekkenhook/tekken-dll.h | 20 ++++++++ tekkenhook/tekkenhook.def | 7 +++ tekkenio/config.c | 17 ++++++ tekkenio/config.h | 15 ++++++ tekkenio/meson.build | 13 +++++ tekkenio/tekkenio.c | 69 +++++++++++++++++++++++++ tekkenio/tekkenio.def | 7 +++ tekkenio/tekkenio.h | 43 ++++++++++++++++ 19 files changed, 672 insertions(+), 1 deletion(-) create mode 100644 dist/tekken/bananatools.ini create mode 100644 dist/tekken/start.bat create mode 100644 tekkenhook/config.c create mode 100644 tekkenhook/config.h create mode 100644 tekkenhook/dllmain.c create mode 100644 tekkenhook/jvs.c create mode 100644 tekkenhook/jvs.h create mode 100644 tekkenhook/meson.build create mode 100644 tekkenhook/tekken-dll.c create mode 100644 tekkenhook/tekken-dll.h create mode 100644 tekkenhook/tekkenhook.def create mode 100644 tekkenio/config.c create mode 100644 tekkenio/config.h create mode 100644 tekkenio/meson.build create mode 100644 tekkenio/tekkenio.c create mode 100644 tekkenio/tekkenio.def create mode 100644 tekkenio/tekkenio.h diff --git a/Package.mk b/Package.mk index b6877c0..a59e755 100644 --- a/Package.mk +++ b/Package.mk @@ -53,6 +53,17 @@ $(BUILD_DIR_ZIP)/mkac.zip: $(V)strip $(BUILD_DIR_ZIP)/mkac/*.{exe,dll} $(V)cd $(BUILD_DIR_ZIP)/mkac ; zip -r ../mkac.zip * +$(BUILD_DIR_ZIP)/tekken.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/tekken + $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_64)/tekkenhook/tekkenhook.dll \ + $(DIST_DIR)/tekken/bananatools.ini \ + $(DIST_DIR)/tekken/start.bat \ + $(BUILD_DIR_ZIP)/tekken + $(V)strip $(BUILD_DIR_ZIP)/tekken/*.{exe,dll} + $(V)cd $(BUILD_DIR_ZIP)/tekken ; zip -r ../tekken.zip * + $(BUILD_DIR_ZIP)/doc.zip: \ $(DOC_DIR)/ferrumhook.md \ $(DOC_DIR)/taikohook.md \ @@ -68,6 +79,7 @@ $(BUILD_DIR_ZIP)/bananatools.zip: \ $(BUILD_DIR_ZIP)/exvs2.zip \ $(BUILD_DIR_ZIP)/sao.zip \ $(BUILD_DIR_ZIP)/mkac.zip \ + $(BUILD_DIR_ZIP)/tekken.zip \ $(BUILD_DIR_ZIP)/doc.zip \ README.md \ diff --git a/dist/tekken/bananatools.ini b/dist/tekken/bananatools.ini new file mode 100644 index 0000000..02198b3 --- /dev/null +++ b/dist/tekken/bananatools.ini @@ -0,0 +1,58 @@ +; 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=282814450001 + +; Set the network environment. Most games seem to want 192.168.123.X +[netenv] +enable=1 +subnet=192.168.123.0 + +; Graphics hook, may cause crashes in some games +[gfx] +enable=1 +windowed=1 +framed=0 +monitor=0 + +[misc] +systemVersion=TE7100-1-NA-SYS0-A03 + +; Control the AMCUS replacement class +[amcus] +enable=1 +game_id=SDBS +game_cd=TR21 +am_game_ver=1.80 +cacfg_game_ver=10.03 +server_uri=localhost +server_host=localhost + +[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/tekken/start.bat b/dist/tekken/start.bat new file mode 100644 index 0000000..d6042d9 --- /dev/null +++ b/dist/tekken/start.bat @@ -0,0 +1,10 @@ +@echo off + +pushd %~dp0 + +start inject.exe -d -k tekkenhook.dll AMCUS\AMAuthd.exe +inject.exe -d -k tekkenhook.dll TekkenGame\Binaries\Win64\TekkenGame-Win64-Shipping.exe + +echo. +echo The game process has terminated +pause \ No newline at end of file diff --git a/meson.build b/meson.build index 5132938..d77b982 100644 --- a/meson.build +++ b/meson.build @@ -59,9 +59,11 @@ subdir('taikoio') subdir('exvs2io') subdir('saoio') subdir('mkacio') +subdir('tekkenio') subdir('taikohook') subdir('ferrumhook') subdir('exvs2hook') subdir('saohook') -subdir('mkachook') \ No newline at end of file +subdir('mkachook') +subdir('tekkenhook') \ No newline at end of file diff --git a/tekkenhook/config.c b/tekkenhook/config.c new file mode 100644 index 0000000..f3c7be4 --- /dev/null +++ b/tekkenhook/config.c @@ -0,0 +1,35 @@ +#include +#include + +#include "tekkenhook/config.h" + +#include "platform/config.h" + +void tekken_dll_config_load( + struct tekken_dll_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + GetPrivateProfileStringW( + L"tekkenio", + L"path", + L"", + cfg->path, + _countof(cfg->path), + filename); +} + +void tekken_hook_config_load( + struct tekken_hook_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + platform_config_load(&cfg->platform, filename); + tekken_dll_config_load(&cfg->dll, filename); + gfx_config_load(&cfg->gfx, filename); + bpreader_config_load(&cfg->reader, filename); +} diff --git a/tekkenhook/config.h b/tekkenhook/config.h new file mode 100644 index 0000000..7c21a6a --- /dev/null +++ b/tekkenhook/config.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include "tekkenhook/tekken-dll.h" +#include "tekkenhook/jvs.h" +#include "board/config.h" + +#include "platform/config.h" +#include "gfxhook/config.h" +#include "amcus/config.h" + +struct tekken_hook_config { + struct platform_config platform; + struct tekken_dll_config dll; + struct gfx_config gfx; + struct amcus_config amcus; + struct bpreader_config reader; +}; + +void tekken_dll_config_load( + struct tekken_dll_config *cfg, + const wchar_t *filename); + +void tekken_hook_config_load( + struct tekken_hook_config *cfg, + const wchar_t *filename); + diff --git a/tekkenhook/dllmain.c b/tekkenhook/dllmain.c new file mode 100644 index 0000000..472552f --- /dev/null +++ b/tekkenhook/dllmain.c @@ -0,0 +1,96 @@ +#include +#include + +#include "tekkenhook/config.h" +#include "tekkenhook/tekken-dll.h" +#include "tekkenhook/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 tekken_hook_mod; +static process_entry_t tekken_startup; +static struct tekken_hook_config tekken_hook_cfg; + +static DWORD CALLBACK tekken_pre_startup(void) +{ + HRESULT hr; + + dprintf("--- Begin tekken_pre_startup ---\n"); + + tekken_hook_config_load(&tekken_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(&tekken_hook_cfg.platform, PLATFORM_ES3, tekken_jvs_init, tekken_hook_mod, dinfo); + + if (FAILED(hr)) { + ExitProcess(EXIT_FAILURE); + } + + hr = tekken_dll_init(&tekken_hook_cfg.dll, tekken_hook_mod); + + if (FAILED(hr)) { + ExitProcess(EXIT_FAILURE); + } + + hr = amcus_hook_init(&tekken_hook_cfg.amcus); + + if (FAILED(hr)) { + ExitProcess(EXIT_FAILURE); + } + + hr = bpreader_init(&tekken_hook_cfg.reader, 4); + + if (FAILED(hr)) { + ExitProcess(EXIT_FAILURE); + } + + debug_hook_init(); + + gfx_hook_init(&tekken_hook_cfg.gfx); + gfx_d3d11_hook_init(&tekken_hook_cfg.gfx, tekken_hook_mod); + gfx_dxgi_hook_init(&tekken_hook_cfg.gfx, tekken_hook_mod); + + dprintf("--- End tekken_pre_startup ---\n"); + + return tekken_startup(); + +} + +BOOL WINAPI DllMain(HMODULE mod, DWORD cause, void *ctx) +{ + HRESULT hr; + + if (cause != DLL_PROCESS_ATTACH) { + return TRUE; + } + + tekken_hook_mod = mod; + + hr = process_hijack_startup(tekken_pre_startup, &tekken_startup); + + if (!SUCCEEDED(hr)) { + dprintf("Failed to hijack process startup: %x\n", (int) hr); + } + + return SUCCEEDED(hr); +} diff --git a/tekkenhook/jvs.c b/tekkenhook/jvs.c new file mode 100644 index 0000000..b9e6c45 --- /dev/null +++ b/tekkenhook/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 "tekkenhook/jvs.h" +#include "tekkenhook/tekken-dll.h" + +static void tekken_jvs_read_switches(void *ctx, struct najv4_switch_state *out); +static void tekken_jvs_read_coin_counter(void *ctx, uint8_t slot_no, uint16_t *out); + +static const struct najv4_ops tekken_jvs_najv4_ops = { + .read_switches = tekken_jvs_read_switches, + .read_coin_counter = tekken_jvs_read_coin_counter, +}; + +static struct najv4 tekken_jvs_najv4; + +HRESULT tekken_jvs_init(struct jvs_node **out) +{ + HRESULT hr; + + assert(out != NULL); + assert(tekken_dll.jvs_init != NULL); + + dprintf("Tekken JVS: Starting IO backend\n"); + hr = tekken_dll.jvs_init(); + + if (FAILED(hr)) { + dprintf("Tekken JVS: Backend error, I/O disconnected: %x\n", (int) hr); + + return hr; + } + + najv4_init(&tekken_jvs_najv4, NULL, &tekken_jvs_najv4_ops, NULL); + *out = najv4_to_jvs_node(&tekken_jvs_najv4); + + return S_OK; +} + +static void tekken_jvs_read_switches(void *ctx, struct najv4_switch_state *out) +{ + uint8_t opbtn = 0; + + //dprintf("Tekken JVS: Read Switches\n"); + + assert(out != NULL); + assert(tekken_dll.jvs_poll != NULL); + + tekken_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 tekken_jvs_read_coin_counter(void *ctx, uint8_t slot_no, uint16_t *out) +{ + //dprintf("Tekken JVS: Read coin counter\n"); + + assert(out != NULL); + assert(tekken_dll.jvs_read_coin_counter != NULL); + + if (slot_no > 0) { + return; + } + + tekken_dll.jvs_read_coin_counter(out); +} \ No newline at end of file diff --git a/tekkenhook/jvs.h b/tekkenhook/jvs.h new file mode 100644 index 0000000..8e8e650 --- /dev/null +++ b/tekkenhook/jvs.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include + +#include "jvs/jvs-bus.h" + +HRESULT tekken_jvs_init(struct jvs_node **root); \ No newline at end of file diff --git a/tekkenhook/meson.build b/tekkenhook/meson.build new file mode 100644 index 0000000..036fcfa --- /dev/null +++ b/tekkenhook/meson.build @@ -0,0 +1,31 @@ +shared_library( + 'tekkenhook', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + vs_module_defs : 'tekkenhook.def', + c_pch : '../precompiled.h', + dependencies : [ + capnhook.get_variable('hook_dep'), + capnhook.get_variable('hooklib_dep'), + ], + link_with : [ + tekkenio_lib, + amcus_lib, + platform_lib, + util_lib, + hooklib_lib, + gfxhook_lib, + jvs_lib, + board_lib, + ], + sources : [ + 'dllmain.c', + 'config.c', + 'config.h', + 'tekken-dll.c', + 'tekken-dll.h', + 'jvs.c', + 'jvs.h', + ], +) diff --git a/tekkenhook/tekken-dll.c b/tekkenhook/tekken-dll.c new file mode 100644 index 0000000..5ddf247 --- /dev/null +++ b/tekkenhook/tekken-dll.c @@ -0,0 +1,100 @@ +#include + +#include +#include + +#include "tekkenhook/tekken-dll.h" + +#include "util/dll-bind.h" +#include "util/dprintf.h" + +const struct dll_bind_sym tekken_dll_syms[] = { + { + .sym = "tekken_io_jvs_init", + .off = offsetof(struct tekken_dll, jvs_init), + }, { + .sym = "tekken_io_jvs_poll", + .off = offsetof(struct tekken_dll, jvs_poll), + }, { + .sym = "tekken_io_jvs_read_coin_counter", + .off = offsetof(struct tekken_dll, jvs_read_coin_counter), + } +}; + +struct tekken_dll tekken_dll; + +HRESULT tekken_dll_init(const struct tekken_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("Tekken IO: Failed to load IO DLL: %lx: %S\n", + hr, + cfg->path); + + goto end; + } + + dprintf("Tekken IO: Using custom IO DLL: %S\n", cfg->path); + src = owned; + } else { + owned = NULL; + src = self; + } + + get_api_version = (void *) GetProcAddress(src, "tekken_io_get_api_version"); + + if (get_api_version != NULL) { + tekken_dll.api_version = get_api_version(); + } else { + tekken_dll.api_version = 0x0100; + dprintf("Custom IO DLL does not expose tekken_io_get_api_version, " + "assuming API version 1.0.\n" + "Please ask the developer to update their DLL.\n"); + } + + if (tekken_dll.api_version >= 0x0200) { + hr = E_NOTIMPL; + dprintf("Tekken IO: Custom IO DLL implements an unsupported " + "API version (%#04x). Please update Segatools.\n", + tekken_dll.api_version); + + goto end; + } + + sym = tekken_dll_syms; + hr = dll_bind(&tekken_dll, src, &sym, _countof(tekken_dll_syms)); + + if (FAILED(hr)) { + if (src != self) { + dprintf("Tekken 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/tekkenhook/tekken-dll.h b/tekkenhook/tekken-dll.h new file mode 100644 index 0000000..df183b4 --- /dev/null +++ b/tekkenhook/tekken-dll.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include "tekkenio/tekkenio.h" + +struct tekken_dll { + uint16_t api_version; + HRESULT (*jvs_init)(void); + HRESULT (*jvs_poll)(uint8_t *opbtn); + void (*jvs_read_coin_counter)(uint16_t *coins); +}; + +struct tekken_dll_config { + wchar_t path[MAX_PATH]; +}; + +extern struct tekken_dll tekken_dll; + +HRESULT tekken_dll_init(const struct tekken_dll_config *cfg, HINSTANCE self); diff --git a/tekkenhook/tekkenhook.def b/tekkenhook/tekkenhook.def new file mode 100644 index 0000000..9eaaca9 --- /dev/null +++ b/tekkenhook/tekkenhook.def @@ -0,0 +1,7 @@ +LIBRARY tekkenhook + +EXPORTS + tekken_io_get_api_version + tekken_io_jvs_init + tekken_io_jvs_poll + tekken_io_jvs_read_coin_counter \ No newline at end of file diff --git a/tekkenio/config.c b/tekkenio/config.c new file mode 100644 index 0000000..2a2f2d6 --- /dev/null +++ b/tekkenio/config.c @@ -0,0 +1,17 @@ +#include + +#include +#include +#include + +#include "tekkenio/config.h" + +void tekken_io_najv4_config_load(struct tekken_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); +} diff --git a/tekkenio/config.h b/tekkenio/config.h new file mode 100644 index 0000000..e779170 --- /dev/null +++ b/tekkenio/config.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +struct tekken_najv4_config { + uint8_t test; + uint8_t service; + uint8_t up; + uint8_t down; + uint8_t enter; + uint8_t coin; +}; + +void tekken_io_najv4_config_load(struct tekken_najv4_config *cfg, const wchar_t *filename); diff --git a/tekkenio/meson.build b/tekkenio/meson.build new file mode 100644 index 0000000..58a8a24 --- /dev/null +++ b/tekkenio/meson.build @@ -0,0 +1,13 @@ +tekkenio_lib = static_library( + 'tekkenio', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + sources : [ + 'tekkenio.c', + 'tekkenio.h', + 'config.c', + 'config.h', + ], +) diff --git a/tekkenio/tekkenio.c b/tekkenio/tekkenio.c new file mode 100644 index 0000000..5e74c33 --- /dev/null +++ b/tekkenio/tekkenio.c @@ -0,0 +1,69 @@ +#include +#include + +#include +#include +#include + +#include "tekkenio/tekkenio.h" +#include "tekkenio/config.h" + +#include "util/dprintf.h" + +static bool tekken_io_coin = false; +static bool tekken_test_toggle = false; +static uint16_t tekken_coin_ct = 0; +static struct tekken_najv4_config najv4_cfg; + +uint16_t tekken_io_get_api_version(void) +{ + return 0x0100; +} + +HRESULT tekken_io_jvs_init(void) +{ + dprintf("Tekken IO: JVS Init\n"); + tekken_io_najv4_config_load(&najv4_cfg, L".\\bananatools.ini"); + return S_OK; +} + +HRESULT tekken_io_jvs_poll(uint8_t *opbtn) +{ + *opbtn = 0; + + if ((GetAsyncKeyState(najv4_cfg.test) & 0x8000)) { + *opbtn |= TEKKEN_IO_OPBTN_TEST; + } + + if (GetAsyncKeyState(najv4_cfg.service) & 0x8000) { + *opbtn |= TEKKEN_IO_OPBTN_SERVICE; + } + + if (GetAsyncKeyState(najv4_cfg.up) & 0x8000) { + *opbtn |= TEKKEN_IO_OPBTN_UP; + } + + if (GetAsyncKeyState(najv4_cfg.down) & 0x8000) { + *opbtn |= TEKKEN_IO_OPBTN_DOWN; + } + + if (GetAsyncKeyState(najv4_cfg.enter) & 0x8000) { + *opbtn |= TEKKEN_IO_OPBTN_ENTER; + } + + return S_OK; +} + +void tekken_io_jvs_read_coin_counter(uint16_t *coins) +{ + if (GetAsyncKeyState(VK_INSERT) & 0x8000) { + if (!tekken_io_coin) { + tekken_io_coin = true; + tekken_coin_ct++; + } + } else { + tekken_io_coin = false; + } + + *coins = tekken_coin_ct; +} diff --git a/tekkenio/tekkenio.def b/tekkenio/tekkenio.def new file mode 100644 index 0000000..4a5e0e0 --- /dev/null +++ b/tekkenio/tekkenio.def @@ -0,0 +1,7 @@ +LIBRARY tekkenhook + +EXPORTS + tekken_io_get_api_version + tekken_io_jvs_init + tekken_io_jvs_poll + tekken_io_read_coin_counter \ No newline at end of file diff --git a/tekkenio/tekkenio.h b/tekkenio/tekkenio.h new file mode 100644 index 0000000..6a0d5b1 --- /dev/null +++ b/tekkenio/tekkenio.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +#include + +#include "tekkenio/config.h" + +enum { + TEKKEN_IO_OPBTN_TEST = 0x01, + TEKKEN_IO_OPBTN_SERVICE = 0x02, + TEKKEN_IO_OPBTN_UP = 0x04, + TEKKEN_IO_OPBTN_DOWN = 0x08, + TEKKEN_IO_OPBTN_ENTER = 0x10, + TEKKEN_IO_OPBTN_COIN = 0x20, +}; + +/* 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 tekken_io_get_api_version(void); + +/* Initialize the IO DLL. This is the second function that will be called on + your DLL, after tekken_io_get_api_version. + + All subsequent calls to this API may originate from arbitrary threads. + + Minimum API version: 0x0100 */ + +HRESULT tekken_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 tekken_io_jvs_poll(uint8_t *opbtn); + +void tekken_io_jvs_read_coin_counter(uint16_t *coins);