diff --git a/.vscode/settings.json b/.vscode/settings.json index 7c2feb7..487d6ff 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,28 @@ { - "editor.formatOnSave": false + "editor.formatOnSave": false, + "files.associations": { + "assert.h": "c", + "config.h": "c", + "backend.h": "c", + "idacio.h": "c", + "idac-dll.h": "c", + "d3d11.h": "c", + "mu3-dll.h": "c", + "printer.h": "c", + "netenv.h": "c", + "stdint.h": "c", + "string.h": "c", + "platform.h": "c", + "jvs.h": "c", + "sg-reader.h": "c", + "carol-dll.h": "c", + "slider.h": "c", + "slider-cmd.h": "c", + "touch.h": "c", + "iobd.h": "c", + "stdbool.h": "c", + "sg-frame.h": "c", + "sg-cmd.h": "c", + "controlbd.h": "c" + } } diff --git a/Dockerfile b/Dockerfile index 279c6b6..5a3ff66 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,6 +22,8 @@ COPY chuniio chuniio COPY dist dist COPY divahook divahook COPY divaio divaio +COPY carolhook carolhook +COPY carolio carolio COPY doc doc COPY hooklib hooklib COPY iccard iccard diff --git a/Package.mk b/Package.mk index 53fd832..4f21e64 100644 --- a/Package.mk +++ b/Package.mk @@ -12,6 +12,36 @@ $(BUILD_DIR_ZIP)/chuni.zip: $(BUILD_DIR_ZIP)/chuni/DEVICE $(V)strip $(BUILD_DIR_ZIP)/chuni/*.{exe,dll} $(V)cd $(BUILD_DIR_ZIP)/chuni ; zip -r ../chuni.zip * + +$(BUILD_DIR_ZIP)/diva.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/diva + $(V)mkdir -p $(BUILD_DIR_ZIP)/diva/DEVICE + $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_64)/divahook/divahook.dll \ + $(DIST_DIR)/diva/segatools.ini \ + $(DIST_DIR)/diva/start.bat \ + $(BUILD_DIR_ZIP)/diva + $(V)cp pki/billing.pub \ + pki/ca.crt \ + $(BUILD_DIR_ZIP)/diva/DEVICE + $(V)strip $(BUILD_DIR_ZIP)/diva/*.{exe,dll} + $(V)cd $(BUILD_DIR_ZIP)/diva ; zip -r ../diva.zip * + +$(BUILD_DIR_ZIP)/carol.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/carol + $(V)mkdir -p $(BUILD_DIR_ZIP)/carol/DEVICE + $(V)cp $(BUILD_DIR_32)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_32)/carolhook/carolhook.dll \ + $(DIST_DIR)/carol/segatools.ini \ + $(DIST_DIR)/carol/start.bat \ + $(BUILD_DIR_ZIP)/carol + $(V)cp pki/billing.pub \ + pki/ca.crt \ + $(BUILD_DIR_ZIP)/carol/DEVICE + $(V)strip $(BUILD_DIR_ZIP)/carol/*.{exe,dll} + $(V)cd $(BUILD_DIR_ZIP)/carol ; zip -r ../carol.zip * $(BUILD_DIR_ZIP)/idz.zip: $(V)echo ... $@ @@ -69,6 +99,8 @@ $(BUILD_DIR_ZIP)/doc.zip: \ $(BUILD_DIR_ZIP)/segatools.zip: \ $(BUILD_DIR_ZIP)/chuni.zip \ + $(BUILD_DIR_ZIP)/carol.zip \ + $(BUILD_DIR_ZIP)/diva.zip \ $(BUILD_DIR_ZIP)/doc.zip \ $(BUILD_DIR_ZIP)/idz.zip \ $(BUILD_DIR_ZIP)/mercury.zip \ diff --git a/carolhook/carol-dll.c b/carolhook/carol-dll.c new file mode 100644 index 0000000..5cc8e98 --- /dev/null +++ b/carolhook/carol-dll.c @@ -0,0 +1,112 @@ +#include + +#include +#include + +#include "carolhook/carol-dll.h" + +#include "util/dll-bind.h" +#include "util/dprintf.h" + +const struct dll_bind_sym carol_dll_syms[] = { + { + .sym = "carol_io_jvs_init", + .off = offsetof(struct carol_dll, jvs_init), + }, { + .sym = "carol_io_jvs_poll", + .off = offsetof(struct carol_dll, jvs_poll), + }, { + .sym = "carol_io_jvs_read_coin_counter", + .off = offsetof(struct carol_dll, jvs_read_coin_counter), + }, { + .sym = "carol_io_touch_init", + .off = offsetof(struct carol_dll, touch_init), + }, { + .sym = "carol_io_controlbd_init", + .off = offsetof(struct carol_dll, controlbd_init), + } +}; + +struct carol_dll carol_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 carol_dll_init(const struct carol_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("carol IO: Failed to load IO DLL: %lx: %S\n", + hr, + cfg->path); + + goto end; + } + + dprintf("carol IO: Using custom IO DLL: %S\n", cfg->path); + src = owned; + } else { + owned = NULL; + src = self; + } + + get_api_version = (void *) GetProcAddress(src, "carol_io_get_api_version"); + + if (get_api_version != NULL) { + carol_dll.api_version = get_api_version(); + } else { + carol_dll.api_version = 0x0100; + dprintf("Custom IO DLL does not expose carol_io_get_api_version, " + "assuming API version 1.0.\n" + "Please ask the developer to update their DLL.\n"); + } + + if (carol_dll.api_version >= 0x0200) { + hr = E_NOTIMPL; + dprintf("carol IO: Custom IO DLL implements an unsupported " + "API version (%#04x). Please update Segatools.\n", + carol_dll.api_version); + + goto end; + } + + sym = carol_dll_syms; + hr = dll_bind(&carol_dll, src, &sym, _countof(carol_dll_syms)); + + if (FAILED(hr)) { + if (src != self) { + dprintf("carol 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/carolhook/carol-dll.h b/carolhook/carol-dll.h new file mode 100644 index 0000000..095a1fd --- /dev/null +++ b/carolhook/carol-dll.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include "carolio/carolio.h" + +struct carol_dll { + uint16_t api_version; + HRESULT (*jvs_init)(void); + void (*jvs_poll)(uint8_t *opbtn, uint8_t *beams); + void (*jvs_read_coin_counter)(uint16_t *total); + HRESULT (*touch_init)(); + HRESULT (*controlbd_init)(); +}; + +struct carol_dll_config { + wchar_t path[MAX_PATH]; +}; + +extern struct carol_dll carol_dll; + +HRESULT carol_dll_init(const struct carol_dll_config *cfg, HINSTANCE self); diff --git a/carolhook/carolhook.def b/carolhook/carolhook.def new file mode 100644 index 0000000..3c324c4 --- /dev/null +++ b/carolhook/carolhook.def @@ -0,0 +1,19 @@ +LIBRARY carolhook + +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 + carol_io_get_api_version + carol_io_jvs_init + carol_io_jvs_poll + carol_io_jvs_read_coin_counter + carol_io_touch_init + carol_io_controlbd_init diff --git a/carolhook/config.c b/carolhook/config.c new file mode 100644 index 0000000..740a162 --- /dev/null +++ b/carolhook/config.c @@ -0,0 +1,76 @@ +#include +#include +#include + +#include "amex/amex.h" +#include "amex/config.h" +#include "gfxhook/config.h" + +#include "board/config.h" +#include "board/sg-reader.h" + +#include "carolhook/config.h" + +#include "platform/config.h" +#include "platform/platform.h" + +void carol_dll_config_load( + struct carol_dll_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + GetPrivateProfileStringW( + L"carolio", + L"path", + L"", + cfg->path, + _countof(cfg->path), + filename); +} + +void touch_config_load( + struct touch_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW( + L"touch", + L"enable", + 1, + filename); +} + +void controlbd_config_load( + struct controlbd_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW( + L"controlbd", + L"enable", + 1, + filename); +} + + +void carol_hook_config_load( + struct carol_hook_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + platform_config_load(&cfg->platform, filename); + amex_config_load(&cfg->amex, filename); + aime_config_load(&cfg->aime, filename); + carol_dll_config_load(&cfg->dll, filename); + gfx_config_load(&cfg->gfx, filename); + touch_config_load(&cfg->touch, filename); + controlbd_config_load(&cfg->controlbd, filename); +} diff --git a/carolhook/config.h b/carolhook/config.h new file mode 100644 index 0000000..50b7f0b --- /dev/null +++ b/carolhook/config.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include "amex/amex.h" + +#include "board/sg-reader.h" + +#include "carolhook/carol-dll.h" + +#include "platform/platform.h" + +#include "gfxhook/gfx.h" + +#include "carolhook/touch.h" +#include "carolhook/controlbd.h" + +struct carol_hook_config { + struct platform_config platform; + struct amex_config amex; + struct aime_config aime; + struct carol_dll_config dll; + struct gfx_config gfx; + struct touch_config touch; + struct controlbd_config controlbd; +}; + +void carol_dll_config_load( + struct carol_dll_config *cfg, + const wchar_t *filename); +void carol_hook_config_load( + struct carol_hook_config *cfg, + const wchar_t *filename); diff --git a/carolhook/controlbd.c b/carolhook/controlbd.c new file mode 100644 index 0000000..44fa5a6 --- /dev/null +++ b/carolhook/controlbd.c @@ -0,0 +1,178 @@ +#include + +#include +#include +#include +#include + +#include "hook/iobuf.h" +#include "hook/iohook.h" + +#include "carolhook/carol-dll.h" +#include "carolhook/controlbd.h" + +#include "hooklib/uart.h" + +#include "util/dprintf.h" +#include "util/dump.h" + +static HRESULT controlbd_handle_irp(struct irp *irp); +static HRESULT controlbd_handle_irp_locked(struct irp *irp); +static HRESULT controlbd_frame_decode(struct controlbd_req *dest, struct iobuf *iobuf); +static HRESULT controlbd_frame_dispatch(struct controlbd_req *dest); + +static HRESULT controlbd_req_nop(uint8_t cmd); + +static CRITICAL_SECTION controlbd_lock; +static struct uart controlbd_uart; +static uint8_t controlbd_written_bytes[520]; +static uint8_t controlbd_readable_bytes[520]; + +HRESULT controlbd_hook_init(const struct controlbd_config *cfg) +{ + if (!cfg->enable) { + return S_OK; + } + + InitializeCriticalSection(&controlbd_lock); + + uart_init(&controlbd_uart, 11); + controlbd_uart.written.bytes = controlbd_written_bytes; + controlbd_uart.written.nbytes = sizeof(controlbd_written_bytes); + controlbd_uart.readable.bytes = controlbd_readable_bytes; + controlbd_uart.readable.nbytes = sizeof(controlbd_readable_bytes); + + dprintf("Control Board: Init\n"); + + return iohook_push_handler(controlbd_handle_irp); +} + +static HRESULT controlbd_handle_irp(struct irp *irp) +{ + HRESULT hr; + + assert(irp != NULL); + + if (!uart_match_irp(&controlbd_uart, irp)) { + return iohook_invoke_next(irp); + } + + EnterCriticalSection(&controlbd_lock); + hr = controlbd_handle_irp_locked(irp); + LeaveCriticalSection(&controlbd_lock); + + return hr; +} + +static HRESULT controlbd_handle_irp_locked(struct irp *irp) +{ + struct controlbd_req req; + HRESULT hr; + + assert(carol_dll.controlbd_init != NULL); + + if (irp->op == IRP_OP_OPEN) { + dprintf("Control Board: Starting backend DLL\n"); + hr = carol_dll.controlbd_init(); + + if (FAILED(hr)) { + dprintf("Control Board: Backend DLL error: %x\n", (int) hr); + + return hr; + } + } + + hr = uart_handle_irp(&controlbd_uart, irp); + + if (FAILED(hr) || irp->op != IRP_OP_WRITE) { + return hr; + } + + for (;;) { +#if 0 + dprintf("Control Board: TX Buffer:\n"); + dump_iobuf(&controlbd_uart.written); +#endif + hr = controlbd_frame_decode(&req, &controlbd_uart.written); + + if (FAILED(hr)) { + dprintf("Control Board: Deframe Error: %x\n", (int) hr); + + return hr; + } + + hr = controlbd_frame_dispatch(&req); + if (FAILED(hr)) { + dprintf("Control Board: Dispatch Error: %x\n", (int) hr); + + return hr; + } + + return hr; + } +} + +static HRESULT controlbd_frame_dispatch(struct controlbd_req *req) +{ + switch (req->cmd) { + case CONTROLBD_CMD_UNK_11: + return controlbd_req_nop(req->cmd); + default: + dprintf("Unhandled command %#02x\n", req->cmd); + + return S_OK; + } +} + +static HRESULT controlbd_req_nop(uint8_t cmd) +{ + dprintf("Control Board: No-op cmd %#02x\n", cmd); + + controlbd_uart.readable.bytes[controlbd_uart.readable.pos++] = 0xE0; + controlbd_uart.readable.bytes[controlbd_uart.readable.pos++] = 0x01; + controlbd_uart.readable.bytes[controlbd_uart.readable.pos++] = 0x11; + controlbd_uart.readable.bytes[controlbd_uart.readable.pos++] = 0x03; + controlbd_uart.readable.bytes[controlbd_uart.readable.pos++] = 0x01; + controlbd_uart.readable.bytes[controlbd_uart.readable.pos++] = 0x10; + controlbd_uart.readable.bytes[controlbd_uart.readable.pos++] = 0x01; + controlbd_uart.readable.bytes[controlbd_uart.readable.pos++] = 0x27; + + return S_OK; +} + +/* Decodes the response into a struct that's easier to work with. */ +static HRESULT controlbd_frame_decode(struct controlbd_req *dest, struct iobuf *iobuf) +{ + int initial_pos = iobuf->pos; + uint8_t check = 0; + + dest->sync = iobuf->bytes[0]; + iobuf->pos--; + + dest->cmd = iobuf->bytes[1]; + iobuf->pos--; + check += dest->cmd; + + dest->checksum = iobuf->bytes[initial_pos - 1]; + iobuf->pos--; + + dest->data_length = initial_pos - 3; // sync, cmd, checksum + if (dest->data_length > 0) { + for (int i = 0; i < dest->data_length; i++) { + dest->data[i] = iobuf->bytes[i+2]; + check += dest->data[i]; + } + } + iobuf->pos -= dest->data_length; + + if (dest->sync != 0xe0) { + dprintf("Control Board: Sync error, expected 0xe0, got %x\n", dest->sync); + return E_FAIL; + } + if (dest->checksum != check) { + dprintf("Control Board: Checksum error, expected %x, got %x\n", check, dest->checksum); + return E_FAIL; + } + + return S_OK; +} \ No newline at end of file diff --git a/carolhook/controlbd.h b/carolhook/controlbd.h new file mode 100644 index 0000000..500efd7 --- /dev/null +++ b/carolhook/controlbd.h @@ -0,0 +1,21 @@ +#pragma once +#include + +#include +#include + +struct controlbd_config { + bool enable; +}; +enum controlbd_cmd { + CONTROLBD_CMD_UNK_11 = 0x11 +}; +struct controlbd_req { + uint8_t sync; // First byte is the sync + uint8_t cmd; // Command byte + uint8_t data[256]; // Request body goes here + uint8_t checksum; // Final byte is all bytes added, except the sync + uint8_t data_length; // Size of the data including command byte +}; + +HRESULT controlbd_hook_init(const struct controlbd_config *cfg); \ No newline at end of file diff --git a/carolhook/dllmain.c b/carolhook/dllmain.c new file mode 100644 index 0000000..bb664d1 --- /dev/null +++ b/carolhook/dllmain.c @@ -0,0 +1,131 @@ +#include + +#include + +#include "amex/amex.h" +#include "gfxhook/gfx.h" +#include "gfxhook/d3d9.h" + +#include "board/sg-reader.h" + +#include "carolhook/config.h" +#include "carolhook/carol-dll.h" +#include "carolhook/jvs.h" +#include "carolhook/touch.h" +#include "carolhook/controlbd.h" +#include "carolhook/serial.h" + +#include "hook/process.h" + +#include "hooklib/serial.h" +#include "hooklib/spike.h" + +#include "platform/platform.h" + +#include "util/dprintf.h" + +static HMODULE carol_hook_mod; +static process_entry_t carol_startup; +static struct carol_hook_config carol_hook_cfg; + +/* +COM Layout +01:(?) Touchscreen +10: Aime reader +11: Control board +12(?): LED Board +*/ + +static DWORD CALLBACK carol_pre_startup(void) +{ + HRESULT hr; + + dprintf("--- Begin carol_pre_startup ---\n"); + + /* Config load */ + + carol_hook_config_load(&carol_hook_cfg, L".\\segatools.ini"); + + /* Hook Win32 APIs */ + + serial_hook_init(); + + /* Initialize emulation hooks */ + + hr = platform_hook_init( + &carol_hook_cfg.platform, + "SDAP", + "AAV0", + carol_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = carol_dll_init(&carol_hook_cfg.dll, carol_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = amex_hook_init(&carol_hook_cfg.amex, carol_jvs_init); + + if (FAILED(hr)) { + goto fail; + } + + hr = sg_reader_hook_init(&carol_hook_cfg.aime, 10, carol_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + gfx_hook_init(&carol_hook_cfg.gfx); + gfx_d3d9_hook_init(&carol_hook_cfg.gfx, carol_hook_mod); + //serial_init(); + + + hr = touch_hook_init(&carol_hook_cfg.touch); + + if (FAILED(hr)) { + goto fail; + } + + hr = controlbd_hook_init(&carol_hook_cfg.controlbd); + + if (FAILED(hr)) { + goto fail; + } + + /* Initialize debug helpers */ + + spike_hook_init(L".\\segatools.ini"); + + dprintf("--- End carol_pre_startup ---\n"); + + /* Jump to EXE start address */ + + return carol_startup(); + +fail: + ExitProcess(EXIT_FAILURE); +} + +BOOL WINAPI DllMain(HMODULE mod, DWORD cause, void *ctx) +{ + HRESULT hr; + + if (cause != DLL_PROCESS_ATTACH) { + return TRUE; + } + + carol_hook_mod = mod; + + hr = process_hijack_startup(carol_pre_startup, &carol_startup); + + if (!SUCCEEDED(hr)) { + dprintf("Failed to hijack process startup: %x\n", (int) hr); + } + + return SUCCEEDED(hr); +} diff --git a/carolhook/jvs.c b/carolhook/jvs.c new file mode 100644 index 0000000..6aeb288 --- /dev/null +++ b/carolhook/jvs.c @@ -0,0 +1,109 @@ +#include + +#include +#include +#include +#include + +#include "amex/jvs.h" + +#include "board/io3.h" + +#include "carolhook/carol-dll.h" + +#include "jvs/jvs-bus.h" + +#include "util/dprintf.h" + +static void carol_jvs_read_switches(void *ctx, struct io3_switch_state *out); +static void carol_jvs_read_coin_counter( + void *ctx, + uint8_t slot_no, + uint16_t *out); + +static const struct io3_ops carol_jvs_io3_ops = { + .read_switches = carol_jvs_read_switches, + .read_coin_counter = carol_jvs_read_coin_counter, +}; + +static struct io3 carol_jvs_io3; + +HRESULT carol_jvs_init(struct jvs_node **out) +{ + HRESULT hr; + + assert(out != NULL); + assert(carol_dll.jvs_init != NULL); + + dprintf("JVS I/O: Starting carol backend DLL\n"); + hr = carol_dll.jvs_init(); + + if (FAILED(hr)) { + dprintf("JVS I/O: Backend error, I/O disconnected: %x\n", (int) hr); + + return hr; + } + + io3_init(&carol_jvs_io3, NULL, &carol_jvs_io3_ops, NULL); + *out = io3_to_jvs_node(&carol_jvs_io3); + + return S_OK; +} + +static void carol_jvs_read_switches(void *ctx, struct io3_switch_state *out) +{ + uint8_t opbtn; + uint8_t gamebtn; + + assert(out != NULL); + assert(carol_dll.jvs_poll != NULL); + + opbtn = 0; + gamebtn = 0; + + carol_dll.jvs_poll(&opbtn, &gamebtn); + + if (gamebtn & 0x01) { + out->p1 |= 1 << 6; + } + + if (gamebtn & 0x02) { + out->p1 |= 1 << 7; + } + + if (gamebtn & 0x04) { + out->p1 |= 1 << 8; + } + + if (gamebtn & 0x08) { + out->p1 |= 1 << 9; + } + + if (gamebtn & 0x10) { + out->p1 |= 1 << 15; + } + + if (opbtn & 0x01) { + out->system = 0x80; + } else { + out->system = 0; + } + + if (opbtn & 0x02) { + out->p1 |= 1 << 14; + } +} + +static void carol_jvs_read_coin_counter( + void *ctx, + uint8_t slot_no, + uint16_t *out) +{ + assert(carol_dll.jvs_read_coin_counter != NULL); + + if (slot_no > 0) { + return; + } + + carol_dll.jvs_read_coin_counter(out); +} diff --git a/carolhook/jvs.h b/carolhook/jvs.h new file mode 100644 index 0000000..ef95817 --- /dev/null +++ b/carolhook/jvs.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "jvs/jvs-bus.h" + +HRESULT carol_jvs_init(struct jvs_node **root); diff --git a/carolhook/meson.build b/carolhook/meson.build new file mode 100644 index 0000000..ac4c3cd --- /dev/null +++ b/carolhook/meson.build @@ -0,0 +1,38 @@ +shared_library( + 'carolhook', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + vs_module_defs : 'carolhook.def', + c_pch : '../precompiled.h', + dependencies : [ + capnhook.get_variable('hook_dep'), + capnhook.get_variable('hooklib_dep'), + ], + link_with : [ + aimeio_lib, + amex_lib, + board_lib, + carolio_lib, + hooklib_lib, + jvs_lib, + platform_lib, + util_lib, + gfxhook_lib, + ], + sources : [ + 'config.c', + 'config.h', + 'carol-dll.c', + 'carol-dll.h', + 'dllmain.c', + 'jvs.c', + 'jvs.h', + 'touch.c', + 'touch.h', + 'controlbd.c', + 'controlbd.h', + 'serial.c', + 'serial.h', + ], +) diff --git a/carolhook/serial.c b/carolhook/serial.c new file mode 100644 index 0000000..3a50907 --- /dev/null +++ b/carolhook/serial.c @@ -0,0 +1,39 @@ +#include +#include + +#include "hook/table.h" + +#include "util/dprintf.h" + +static BOOL WINAPI my_SetCommState(HANDLE hFile, LPDCB lpDCB); +static BOOL (WINAPI *next_SetCommState)(HANDLE hFile, LPDCB lpDCB); +static void com_hook_insert_hooks(HMODULE target); + +static const struct hook_symbol win32_hooks[] = { + { + .name = "SetCommState", + .patch = my_SetCommState, + .link = (void **) &next_SetCommState + } +}; + +void serial_init() +{ + com_hook_insert_hooks(NULL); + dprintf("Serial: Spy init\n"); +} + +static void com_hook_insert_hooks(HMODULE target) +{ + hook_table_apply( + target, + "kernel32.dll", + win32_hooks, + _countof(win32_hooks)); +} + +static BOOL WINAPI my_SetCommState(HANDLE hFile, LPDCB lpDCB) +{ + dprintf("Serial: my_SetCommState with baudrate %ld\n", lpDCB->BaudRate); + return next_SetCommState(hFile, lpDCB); +} \ No newline at end of file diff --git a/carolhook/serial.h b/carolhook/serial.h new file mode 100644 index 0000000..8a682ac --- /dev/null +++ b/carolhook/serial.h @@ -0,0 +1,5 @@ +#pragma once +#include +#include + +void serial_init(); \ No newline at end of file diff --git a/carolhook/touch.c b/carolhook/touch.c new file mode 100644 index 0000000..8cbb8ca --- /dev/null +++ b/carolhook/touch.c @@ -0,0 +1,118 @@ +#include + +#include +#include +#include +#include + +#include "carolhook/carol-dll.h" +#include "carolhook/touch.h" + +#include "hooklib/uart.h" + +#include "util/dprintf.h" +#include "util/dump.h" + +static HRESULT touch_handle_irp(struct irp *irp); +static HRESULT touch_handle_irp_locked(struct irp *irp); +static HRESULT touch_frame_decode(struct touch_req *dest, struct iobuf *iobuf); + +static CRITICAL_SECTION touch_lock; +static struct uart touch_uart; +static uint8_t touch_written_bytes[520]; +static uint8_t touch_readable_bytes[520]; + +HRESULT touch_hook_init(const struct touch_config *cfg) +{ + if (!cfg->enable) { + return S_OK; + } + + InitializeCriticalSection(&touch_lock); + + uart_init(&touch_uart, 1); + touch_uart.written.bytes = touch_written_bytes; + touch_uart.written.nbytes = sizeof(touch_written_bytes); + touch_uart.readable.bytes = touch_readable_bytes; + touch_uart.readable.nbytes = sizeof(touch_readable_bytes); + + dprintf("Touchscreen: Init\n"); + + return iohook_push_handler(touch_handle_irp); + return S_OK; +} + +static HRESULT touch_handle_irp(struct irp *irp) +{ + HRESULT hr; + + assert(irp != NULL); + + if (!uart_match_irp(&touch_uart, irp)) { + return iohook_invoke_next(irp); + } + + EnterCriticalSection(&touch_lock); + hr = touch_handle_irp_locked(irp); + LeaveCriticalSection(&touch_lock); + + return hr; +} + +static HRESULT touch_handle_irp_locked(struct irp *irp) +{ + struct touch_req req; + HRESULT hr; + + assert(carol_dll.touch_init != NULL); + + if (irp->op == IRP_OP_OPEN) { + dprintf("Touchscreen: Starting backend DLL\n"); + hr = carol_dll.touch_init(); + + if (FAILED(hr)) { + dprintf("Touchscreen: Backend DLL error: %x\n", (int) hr); + + return hr; + } + } + + hr = uart_handle_irp(&touch_uart, irp); + + if (FAILED(hr) || irp->op != IRP_OP_WRITE) { + return hr; + } + + for (;;) { +#if 1 + dprintf("Touchscreen: TX Buffer:\n"); + dump_iobuf(&touch_uart.written); +#endif + hr = touch_frame_decode(&req, &touch_uart.written); + + if (FAILED(hr)) { + dprintf("Touchscreen: Deframe Error: %x\n", (int) hr); + + return hr; + } + + return hr; + } +} + +/* Decodes the response into a struct that's easier to work with. */ +static HRESULT touch_frame_decode(struct touch_req *dest, struct iobuf *iobuf) +{ + dest->cmd = iobuf->bytes[0]; + iobuf->pos--; + dest->data_length = iobuf->pos; + + if (dest->data_length > 0) { + for (int i = 1; i < dest->data_length; i++) { + dest->data[i-1] = iobuf->bytes[i]; + } + } + iobuf->pos -= dest->data_length; + + return S_OK; +} \ No newline at end of file diff --git a/carolhook/touch.h b/carolhook/touch.h new file mode 100644 index 0000000..6881b8f --- /dev/null +++ b/carolhook/touch.h @@ -0,0 +1,17 @@ +#pragma once +#include + +#include +#include + +struct touch_config { + bool enable; +}; + +struct touch_req { + uint8_t cmd; // First byte is the command byte + uint8_t data[256]; // rest of the data goes here + uint8_t data_length; // Size of the data including command byte +}; + +HRESULT touch_hook_init(const struct touch_config *cfg); \ No newline at end of file diff --git a/carolio/carolio.c b/carolio/carolio.c new file mode 100644 index 0000000..1ae4817 --- /dev/null +++ b/carolio/carolio.c @@ -0,0 +1,79 @@ +#include + +#include +#include +#include +#include + +#include "carolio/carolio.h" +#include "carolio/config.h" + +static bool carol_io_coin; +static uint16_t carol_io_coins; +static struct carol_io_config carol_io_cfg; + +uint16_t carol_io_get_api_version(void) +{ + return 0x0100; +} + +HRESULT carol_io_jvs_init(void) +{ + carol_io_config_load(&carol_io_cfg, L".\\segatools.ini"); + + return S_OK; +} + +void carol_io_jvs_poll(uint8_t *opbtn_out, uint8_t *gamebtn_out) +{ + uint8_t opbtn; + uint8_t gamebtn; + size_t i; + + opbtn = 0; + + if (GetAsyncKeyState(carol_io_cfg.vk_test) & 0x8000) { + opbtn |= 1; + } + + if (GetAsyncKeyState(carol_io_cfg.vk_service) & 0x8000) { + opbtn |= 2; + } + + for (i = 0 ; i < _countof(carol_io_cfg.vk_buttons) ; i++) { + if (GetAsyncKeyState(carol_io_cfg.vk_buttons[i]) & 0x8000) { + gamebtn |= 1 << i; + } + } + + *opbtn_out = opbtn; + *gamebtn_out = gamebtn; +} + +void carol_io_jvs_read_coin_counter(uint16_t *out) +{ + if (out == NULL) { + return; + } + + if (GetAsyncKeyState(carol_io_cfg.vk_coin) & 0x8000) { + if (!carol_io_coin) { + carol_io_coin = true; + carol_io_coins++; + } + } else { + carol_io_coin = false; + } + + *out = carol_io_coins; +} + +HRESULT carol_io_touch_init() +{ + return S_OK; +} + +HRESULT carol_io_controlbd_init() +{ + return S_OK; +} \ No newline at end of file diff --git a/carolio/carolio.h b/carolio/carolio.h new file mode 100644 index 0000000..dbe1091 --- /dev/null +++ b/carolio/carolio.h @@ -0,0 +1,52 @@ +#pragma once + +#include + +#include +#include + +/* Get the version of the Project carol 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 carol_io_get_api_version(void); + +/* Initialize JVS-based input. This function will be called before any other + carol_io_jvs_*() function calls. Errors returned from this function will + manifest as a disconnected JVS bus. + + All subsequent calls may originate from arbitrary threads and some may + overlap with each other. Ensuring synchronization inside your IO DLL is + your responsibility. + + Minimum API version: 0x0100 */ + +HRESULT carol_io_jvs_init(void); + +/* Poll JVS input. + + opbtn returns the cabinet test/service state, where bit 0 is Test and Bit 1 + is Service. + + gamebtn bits, from least significant to most significant, are: + + Circle Cross Square Triangle Start UNUSED UNUSED UNUSED + + Minimum API version: 0x0100 */ + +void carol_io_jvs_poll(uint8_t *opbtn, uint8_t *gamebtn); + +/* Read the current state of the coin counter. This value should be incremented + for every coin detected by the coin acceptor mechanism. This count does not + need to persist beyond the lifetime of the process. + + Minimum API Version: 0x0100 */ + +void carol_io_jvs_read_coin_counter(uint16_t *out); + +HRESULT carol_io_touch_init(); + +HRESULT carol_io_controlbd_init(); \ No newline at end of file diff --git a/carolio/config.c b/carolio/config.c new file mode 100644 index 0000000..151e909 --- /dev/null +++ b/carolio/config.c @@ -0,0 +1,20 @@ +#include + +#include +#include +#include +#include + +#include "carolio/config.h" + +void carol_io_config_load( + struct carol_io_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->vk_test = GetPrivateProfileIntW(L"io3", L"test", '1', filename); + cfg->vk_service = GetPrivateProfileIntW(L"io3", L"service", '2', filename); + cfg->vk_coin = GetPrivateProfileIntW(L"io3", L"coin", '3', filename); +} diff --git a/carolio/config.h b/carolio/config.h new file mode 100644 index 0000000..953277f --- /dev/null +++ b/carolio/config.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +struct carol_io_config { + uint8_t vk_buttons[5]; + uint8_t vk_slider[8]; + uint8_t vk_test; + uint8_t vk_service; + uint8_t vk_coin; +}; + +void carol_io_config_load( + struct carol_io_config *cfg, + const wchar_t *filename); diff --git a/carolio/meson.build b/carolio/meson.build new file mode 100644 index 0000000..dcb81ab --- /dev/null +++ b/carolio/meson.build @@ -0,0 +1,13 @@ +carolio_lib = static_library( + 'carolio', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + sources : [ + 'carolio.c', + 'carolio.h', + 'config.c', + 'config.h', + ], +) diff --git a/dist/carol/segatools.ini b/dist/carol/segatools.ini new file mode 100644 index 0000000..d43cfe0 --- /dev/null +++ b/dist/carol/segatools.ini @@ -0,0 +1,48 @@ +[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 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= + +[dns] +; Insert the hostname or IP address of the server you wish to use here. +; Note that 127.0.0.1, localhost etc are specifically rejected. +default=127.0.0.1 + +[netenv] +; Simulate an ideal LAN environment. +enable=1 + +[gpio] +dipsw1=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.126.0 + +[gfx] +; Force the game to run windowed. +windowed=1 +; Add a frame to the game window if running windowed. +framed=1 +; Select the monitor to run the game on. (Fullscreen only, 0 =primary screen) +monitor=0 + +[aimeio] +; To use a custom card reader IO DLL enter its path here. +; Leave empty if you want to use Segatools built-in keyboard input. +path= + +[io3] +; 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 diff --git a/dist/carol/start.bat b/dist/carol/start.bat new file mode 100644 index 0000000..0fab950 --- /dev/null +++ b/dist/carol/start.bat @@ -0,0 +1,13 @@ +@echo off + +pushd %~dp0 + +taskkill /f /im aimeReaderHost.exe > nul 2>&1 + +start /min inject -d -k carolhook.dll aimeReaderHost.exe -p 10 +inject -d -k carolhook.dll carol_nu.exe +taskkill /f /im aimeReaderHost.exe > nul 2>&1 + +echo. +echo Game processes have terminated +pause \ No newline at end of file diff --git a/dist/diva/segatools.ini b/dist/diva/segatools.ini new file mode 100644 index 0000000..e2608c0 --- /dev/null +++ b/dist/diva/segatools.ini @@ -0,0 +1,54 @@ +[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 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= + +[dns] +; Insert the hostname or IP address of the server you wish to use here. +; Note that 127.0.0.1, localhost etc are specifically rejected. +default=127.0.0.1 + +[netenv] +; Simulate an ideal LAN environment. This may interfere with head-to-head play. +; Chunithm is extremely picky about its LAN environment, so leaving this +; setting enabled is strongly recommended. +enable=1 + +[gpio] +dipsw1=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.150.0 + +[slider] +cell1=0x51 +cell2=0x57 +cell3=0x45 +cell4=0x52 +cell5=0x55 +cell6=0x49 +cell7=0x4F +cell8=0x50 + +[buttons] +key1=0x27 +key2=0x28 +key3=0x25 +key4=0x26 +key5=0x20 + +; Sliders : <- QWER UIOP -> +; Triangle : Up arrow +; Square : Left Arrow +; Cross : Down Arrow +; Circle : Right arrow +; Enter : Space + diff --git a/dist/diva/start.bat b/dist/diva/start.bat new file mode 100644 index 0000000..72934f6 --- /dev/null +++ b/dist/diva/start.bat @@ -0,0 +1,9 @@ +@echo off + +pushd %~dp0 + +inject -d -k divahook.dll diva.exe + +echo. +echo Game processes have terminated +pause \ No newline at end of file diff --git a/meson.build b/meson.build index caca31f..acf85ef 100644 --- a/meson.build +++ b/meson.build @@ -55,12 +55,14 @@ subdir('gfxhook') subdir('aimeio') subdir('chuniio') subdir('divaio') +subdir('carolio') subdir('idzio') subdir('mu3io') subdir('mercuryio') subdir('chunihook') subdir('divahook') +subdir('carolhook') subdir('idzhook') subdir('minihook') subdir('mu3hook') diff --git a/platform/amvideo.c b/platform/amvideo.c index ccd818f..001a52a 100644 --- a/platform/amvideo.c +++ b/platform/amvideo.c @@ -28,6 +28,11 @@ static const struct reg_hook_val amvideo_reg_vals[] = { .name = L"name", .read = amvideo_reg_read_name, .type = REG_SZ, + }, + { + .name = L"name_x86", + .read = amvideo_reg_read_name, + .type = REG_SZ, } };