diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..a8eacdb --- /dev/null +++ b/.clang-format @@ -0,0 +1,4 @@ +--- +BasedOnStyle: Google +IndentWidth: 4 +--- diff --git a/.gitignore b/.gitignore index 31da392..f5d9008 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,26 @@ .*.swp -.vscode/ +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix # Suggested names for build dirs build/ # External dependencies subprojects/capnhook + +# For enabling debug logging on local builds +MesonLocalOptions.mk + +# Some meson cache thing +.meson-subproject-wrap-hash.txt diff --git a/.vscode/settings.json b/.vscode/settings.json index 49872e4..9eb12eb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { "editor.formatOnSave": false, + "mesonbuild.configureOnOpen": false, } diff --git a/Makefile b/Makefile index f39d291..0381552 100644 --- a/Makefile +++ b/Makefile @@ -5,15 +5,18 @@ V ?= @ BUILD_DIR := build BUILD_DIR_32 := $(BUILD_DIR)/build32 BUILD_DIR_64 := $(BUILD_DIR)/build64 -BUILD_DIR_DOCKER := $(BUILD_DIR)/docker +BUILD_DIR_GAMES_32 := $(BUILD_DIR_32)/games +BUILD_DIR_GAMES_64 := $(BUILD_DIR_64)/games BUILD_DIR_ZIP := $(BUILD_DIR)/zip DOC_DIR := doc DIST_DIR := dist -DOCKER_CONTAINER_NAME := "segatools-build" -DOCKER_IMAGE_NAME := "segatools:build" +# Add "-D[option]=[value]" here as necessary +MESON_OPTIONS := +# For options that shouldn't be committed +-include MesonLocalOptions.mk # ----------------------------------------------------------------------------- # Targets @@ -23,9 +26,9 @@ include Package.mk .PHONY: build # Build the project build: - $(V)meson --cross cross-mingw-32.txt $(BUILD_DIR_32) + $(V)meson setup $(MESON_OPTIONS) --cross cross-mingw-32.txt $(BUILD_DIR_32) $(V)ninja -C $(BUILD_DIR_32) - $(V)meson --cross cross-mingw-64.txt $(BUILD_DIR_64) + $(V)meson setup $(MESON_OPTIONS) --cross cross-mingw-64.txt $(BUILD_DIR_64) $(V)ninja -C $(BUILD_DIR_64) .PHONY: dist # Build and create a zip distribution package @@ -42,15 +45,6 @@ zip: $(BUILD_DIR_ZIP)/segatools.zip clean: $(V)rm -rf $(BUILD_DIR) subprojects/capnhook -.PHONY: build-docker # Build the project in a docker container -build-docker: - $(V)docker rm -f $(DOCKER_CONTAINER_NAME) 2> /dev/null || true - $(V)docker build -t $(DOCKER_IMAGE_NAME) -f Dockerfile . - $(V)docker create --name $(DOCKER_CONTAINER_NAME) $(DOCKER_IMAGE_NAME) - $(V)rm -rf $(BUILD_DIR_DOCKER) - $(V)mkdir -p $(BUILD_DIR_DOCKER) - $(V)docker cp $(DOCKER_CONTAINER_NAME):/segatools/$(BUILD_DIR_ZIP) $(BUILD_DIR_DOCKER) - # ----------------------------------------------------------------------------- # Utility, combo and alias targets # ----------------------------------------------------------------------------- diff --git a/Package.mk b/Package.mk index 850f9ee..ae87f73 100644 --- a/Package.mk +++ b/Package.mk @@ -3,9 +3,9 @@ $(BUILD_DIR_ZIP)/chuni.zip: $(V)mkdir -p $(BUILD_DIR_ZIP)/chuni $(V)mkdir -p $(BUILD_DIR_ZIP)/chuni/DEVICE $(V)cp $(BUILD_DIR_32)/subprojects/capnhook/inject/inject.exe \ - $(BUILD_DIR_32)/chunihook/chunihook.dll \ + $(BUILD_DIR_GAMES_32)/chunihook/chunihook.dll \ $(DIST_DIR)/chuni/segatools.ini \ - $(DIST_DIR)/chuni/start.bat \ + $(DIST_DIR)/chuni/launch.bat \ $(BUILD_DIR_ZIP)/chuni $(V)cp pki/billing.pub \ pki/ca.crt \ @@ -18,9 +18,9 @@ $(BUILD_DIR_ZIP)/cxb.zip: $(V)mkdir -p $(BUILD_DIR_ZIP)/cxb $(V)mkdir -p $(BUILD_DIR_ZIP)/cxb/DEVICE $(V)cp $(BUILD_DIR_32)/subprojects/capnhook/inject/inject.exe \ - $(BUILD_DIR_32)/cxbhook/cxbhook.dll \ + $(BUILD_DIR_GAMES_32)/cxbhook/cxbhook.dll \ $(DIST_DIR)/cxb/segatools.ini \ - $(DIST_DIR)/cxb/start.bat \ + $(DIST_DIR)/cxb/launch.bat \ $(BUILD_DIR_ZIP)/cxb $(V)cp pki/billing.pub \ pki/ca.crt \ @@ -33,9 +33,9 @@ $(BUILD_DIR_ZIP)/diva.zip: $(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 \ + $(BUILD_DIR_GAMES_64)/divahook/divahook.dll \ $(DIST_DIR)/diva/segatools.ini \ - $(DIST_DIR)/diva/start.bat \ + $(DIST_DIR)/diva/launch.bat \ $(BUILD_DIR_ZIP)/diva $(V)cp pki/billing.pub \ pki/ca.crt \ @@ -48,9 +48,9 @@ $(BUILD_DIR_ZIP)/carol.zip: $(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 \ + $(BUILD_DIR_GAMES_32)/carolhook/carolhook.dll \ $(DIST_DIR)/carol/segatools.ini \ - $(DIST_DIR)/carol/start.bat \ + $(DIST_DIR)/carol/launch.bat \ $(BUILD_DIR_ZIP)/carol $(V)cp pki/billing.pub \ pki/ca.crt \ @@ -63,9 +63,9 @@ $(BUILD_DIR_ZIP)/idz.zip: $(V)mkdir -p $(BUILD_DIR_ZIP)/idz $(V)mkdir -p $(BUILD_DIR_ZIP)/idz/DEVICE $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ - $(BUILD_DIR_64)/idzhook/idzhook.dll \ + $(BUILD_DIR_GAMES_64)/idzhook/idzhook.dll \ $(DIST_DIR)/idz/segatools.ini \ - $(DIST_DIR)/idz/start.bat \ + $(DIST_DIR)/idz/launch.bat \ $(BUILD_DIR_ZIP)/idz $(V)cp pki/billing.pub \ pki/ca.crt \ @@ -73,14 +73,61 @@ $(BUILD_DIR_ZIP)/idz.zip: $(V)strip $(BUILD_DIR_ZIP)/idz/*.{exe,dll} $(V)cd $(BUILD_DIR_ZIP)/idz ; zip -r ../idz.zip * +$(BUILD_DIR_ZIP)/fgo.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/fgo + $(V)mkdir -p $(BUILD_DIR_ZIP)/fgo/DEVICE + $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_GAMES_64)/fgohook/fgohook.dll \ + $(DIST_DIR)/fgo/segatools.ini \ + $(DIST_DIR)/fgo/launch.bat \ + $(BUILD_DIR_ZIP)/fgo + $(V)cp pki/billing.pub \ + pki/ca.crt \ + $(BUILD_DIR_ZIP)/fgo/DEVICE + $(V)strip $(BUILD_DIR_ZIP)/fgo/*.{exe,dll} + $(V)cd $(BUILD_DIR_ZIP)/fgo ; zip -r ../fgo.zip * + +$(BUILD_DIR_ZIP)/idac.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/idac + $(V)mkdir -p $(BUILD_DIR_ZIP)/idac/DEVICE + $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_GAMES_64)/idachook/idachook.dll \ + $(DIST_DIR)/idac/segatools.ini \ + $(DIST_DIR)/idac/config_hook.json \ + $(DIST_DIR)/idac/launch.bat \ + $(BUILD_DIR_ZIP)/idac + $(V)cp pki/billing.pub \ + pki/ca.crt \ + $(BUILD_DIR_ZIP)/idac/DEVICE + $(V)strip $(BUILD_DIR_ZIP)/idac/*.{exe,dll} + $(V)cd $(BUILD_DIR_ZIP)/idac ; zip -r ../idac.zip * + +$(BUILD_DIR_ZIP)/swdc.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/swdc + $(V)mkdir -p $(BUILD_DIR_ZIP)/swdc/DEVICE + $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_GAMES_64)/swdchook/swdchook.dll \ + $(DIST_DIR)/swdc/segatools.ini \ + $(DIST_DIR)/swdc/config_hook.json \ + $(DIST_DIR)/swdc/launch.bat \ + $(BUILD_DIR_ZIP)/swdc + $(V)cp pki/billing.pub \ + pki/ca.crt \ + $(BUILD_DIR_ZIP)/swdc/DEVICE + $(V)strip $(BUILD_DIR_ZIP)/swdc/*.{exe,dll} + $(V)cd $(BUILD_DIR_ZIP)/swdc ; zip -r ../swdc.zip * + $(BUILD_DIR_ZIP)/mercury.zip: $(V)echo ... $@ $(V)mkdir -p $(BUILD_DIR_ZIP)/mercury $(V)mkdir -p $(BUILD_DIR_ZIP)/mercury/DEVICE $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ - $(BUILD_DIR_64)/mercuryhook/mercuryhook.dll \ + $(BUILD_DIR_GAMES_64)/mercuryhook/mercuryhook.dll \ $(DIST_DIR)/mercury/segatools.ini \ - $(DIST_DIR)/mercury/start.bat \ + $(DIST_DIR)/mercury/launch.bat \ $(BUILD_DIR_ZIP)/mercury $(V)cp pki/billing.pub \ pki/ca.crt \ @@ -88,15 +135,36 @@ $(BUILD_DIR_ZIP)/mercury.zip: $(V)strip $(BUILD_DIR_ZIP)/mercury/*.{exe,dll} $(V)cd $(BUILD_DIR_ZIP)/mercury ; zip -r ../mercury.zip * +$(BUILD_DIR_ZIP)/chusan.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/chusan + $(V)mkdir -p $(BUILD_DIR_ZIP)/chusan/DEVICE + $(V)cp $(DIST_DIR)/chusan/segatools.ini \ + $(DIST_DIR)/chusan/config_hook.json \ + $(DIST_DIR)/chusan/launch.bat \ + $(BUILD_DIR_ZIP)/chusan + $(V)cp $(BUILD_DIR_GAMES_32)/chusanhook/chusanhook.dll \ + $(BUILD_DIR_ZIP)/chusan/chusanhook_x86.dll + $(V)cp $(BUILD_DIR_GAMES_64)/chusanhook/chusanhook.dll \ + $(BUILD_DIR_ZIP)/chusan/chusanhook_x64.dll + $(V)cp $(BUILD_DIR_32)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_ZIP)/chusan/inject_x86.exe + $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_ZIP)/chusan/inject_x64.exe + $(V)cp pki/billing.pub \ + pki/ca.crt \ + $(BUILD_DIR_ZIP)/chusan/DEVICE + for x in exe dll; do strip $(BUILD_DIR_ZIP)/chusan/*.$$x; done + $(V)cd $(BUILD_DIR_ZIP)/chusan ; zip -r ../chusan.zip * $(BUILD_DIR_ZIP)/mu3.zip: $(V)echo ... $@ $(V)mkdir -p $(BUILD_DIR_ZIP)/mu3 $(V)mkdir -p $(BUILD_DIR_ZIP)/mu3/DEVICE $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ - $(BUILD_DIR_64)/mu3hook/mu3hook.dll \ + $(BUILD_DIR_GAMES_64)/mu3hook/mu3hook.dll \ $(DIST_DIR)/mu3/segatools.ini \ - $(DIST_DIR)/mu3/start.bat \ + $(DIST_DIR)/mu3/launch.bat \ $(BUILD_DIR_ZIP)/mu3 $(V)cp pki/billing.pub \ pki/ca.crt \ @@ -104,6 +172,74 @@ $(BUILD_DIR_ZIP)/mu3.zip: $(V)strip $(BUILD_DIR_ZIP)/mu3/*.{exe,dll} $(V)cd $(BUILD_DIR_ZIP)/mu3 ; zip -r ../mu3.zip * +$(BUILD_DIR_ZIP)/mai2.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/mai2 + $(V)mkdir -p $(BUILD_DIR_ZIP)/mai2/DEVICE + $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_GAMES_64)/mai2hook/mai2hook.dll \ + $(DIST_DIR)/mai2/segatools.ini \ + $(DIST_DIR)/mai2/launch.bat \ + $(BUILD_DIR_ZIP)/mai2 + $(V)cp pki/billing.pub \ + pki/ca.crt \ + $(BUILD_DIR_ZIP)/mai2/DEVICE + $(V)strip $(BUILD_DIR_ZIP)/mai2/*.{exe,dll} + $(V)cd $(BUILD_DIR_ZIP)/mai2 ; zip -r ../mai2.zip * + +$(BUILD_DIR_ZIP)/cm.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/cm + $(V)mkdir -p $(BUILD_DIR_ZIP)/cm/DEVICE + $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_GAMES_64)/cmhook/cmhook.dll \ + $(DIST_DIR)/cm/config_hook.json \ + $(DIST_DIR)/cm/segatools.ini \ + $(DIST_DIR)/cm/launch.bat \ + $(BUILD_DIR_ZIP)/cm + $(V)cp pki/billing.pub \ + pki/ca.crt \ + $(BUILD_DIR_ZIP)/cm/DEVICE + $(V)strip $(BUILD_DIR_ZIP)/cm/*.{exe,dll} + $(V)cd $(BUILD_DIR_ZIP)/cm ; zip -r ../cm.zip * + +$(BUILD_DIR_ZIP)/tokyo.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/tokyo + $(V)mkdir -p $(BUILD_DIR_ZIP)/tokyo/DEVICE + $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_GAMES_64)/tokyohook/tokyohook.dll \ + $(DIST_DIR)/tokyo/config_hook.json \ + $(DIST_DIR)/tokyo/segatools.ini \ + $(DIST_DIR)/tokyo/launch.bat \ + $(BUILD_DIR_ZIP)/tokyo + $(V)cp pki/billing.pub \ + pki/ca.crt \ + $(BUILD_DIR_ZIP)/tokyo/DEVICE + $(V)strip $(BUILD_DIR_ZIP)/tokyo/*.{exe,dll} + $(V)cd $(BUILD_DIR_ZIP)/tokyo ; zip -r ../tokyo.zip * + +$(BUILD_DIR_ZIP)/kemono.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/kemono + $(V)mkdir -p $(BUILD_DIR_ZIP)/kemono/DEVICE + $(V)cp $(DIST_DIR)/kemono/segatools.ini \ + $(DIST_DIR)/kemono/launch.bat \ + $(BUILD_DIR_ZIP)/kemono + $(V)cp $(BUILD_DIR_GAMES_32)/kemonohook/kemonohook.dll \ + $(BUILD_DIR_ZIP)/kemono/kemonohook_x86.dll + $(V)cp $(BUILD_DIR_GAMES_64)/kemonohook/kemonohook.dll \ + $(BUILD_DIR_ZIP)/kemono/kemonohook_x64.dll + $(V)cp $(BUILD_DIR_32)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_ZIP)/kemono/inject_x86.exe + $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_ZIP)/kemono/inject_x64.exe + $(V)cp pki/billing.pub \ + pki/ca.crt \ + $(BUILD_DIR_ZIP)/kemono/DEVICE + for x in exe dll; do strip $(BUILD_DIR_ZIP)/kemono/*.$$x; done + $(V)cd $(BUILD_DIR_ZIP)/kemono ; zip -r ../kemono.zip * + $(BUILD_DIR_ZIP)/doc.zip: \ $(DOC_DIR)/config \ $(DOC_DIR)/chunihook.md \ @@ -119,8 +255,16 @@ $(BUILD_DIR_ZIP)/segatools.zip: \ $(BUILD_DIR_ZIP)/diva.zip \ $(BUILD_DIR_ZIP)/doc.zip \ $(BUILD_DIR_ZIP)/idz.zip \ + $(BUILD_DIR_ZIP)/idac.zip \ + $(BUILD_DIR_ZIP)/swdc.zip \ $(BUILD_DIR_ZIP)/mercury.zip \ + $(BUILD_DIR_ZIP)/chusan.zip \ $(BUILD_DIR_ZIP)/mu3.zip \ + $(BUILD_DIR_ZIP)/mai2.zip \ + $(BUILD_DIR_ZIP)/cm.zip \ + $(BUILD_DIR_ZIP)/tokyo.zip \ + $(BUILD_DIR_ZIP)/fgo.zip \ + $(BUILD_DIR_ZIP)/kemono.zip \ CHANGELOG.md \ README.md \ diff --git a/README.md b/README.md index a9c53d7..6f8211c 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,37 @@ # Segatools -Version: `v005` +Version: `2024-09-30` Loaders and hardware emulators for SEGA games that run on the Nu and ALLS platforms. ## List of supported games -* Chunithm - * [Chunithm (Plus)](doc/chunihook.md) - * [Chunithm Air (Plus)](doc/chunihook.md) - * [Chunithm Star (Plus)](doc/chunihook.md) - * [Chunithm Amazon (Plus)](doc/chunihook.md) - * [Chunithm Crystal (Plus)](doc/chunihook.md) +* Card Maker + * starting from Card Maker +* CHUNITHM + * up to [CHUNITHM PARADISE LOST](doc/chunihook.md) + * starting from CHUNITHM NEW!! +* crossbeats REV. + * up to crossbeats REV. SUNRISE +* Fate/Grand Order + * Fate/Grand Order Arcade +* Hatsune Miku: Project DIVA Arcade + * up to Future Tone * Initial D * [Initial D Arcade Stage Zero](doc/idzhook.md) -* Wacca - * Wacca Lilly R (WIP) + * Initial D THE ARCADE +* maimai DX + * starting from maimai DX +* Mario & Sonic + * Mario & Sonic at the Tokyo 2020 Olympics Arcade +* O.N.G.E.K.I. + * starting from O.N.G.E.K.I. +* SEGA World Drivers Championship + * SEGA World Drivers Championship 2019 +* WACCA + * starting from WACCA +* Kemono Friends + * Kemono Friends 3: Planet Tours ## End-users diff --git a/board/config.c b/board/config.c deleted file mode 100644 index 191425a..0000000 --- a/board/config.c +++ /dev/null @@ -1,41 +0,0 @@ -#include - -#include -#include -#include -#include - -#include "board/aime-dll.h" -#include "board/config.h" -#include "board/sg-reader.h" - -static void aime_dll_config_load(struct aime_dll_config *cfg, const wchar_t *filename) -{ - assert(cfg != NULL); - assert(filename != NULL); - - GetPrivateProfileStringW( - L"aimeio", - L"path", - L"", - cfg->path, - _countof(cfg->path), - filename); -} - -void aime_config_load(struct aime_config *cfg, const wchar_t *filename) -{ - assert(cfg != NULL); - assert(filename != NULL); - - aime_dll_config_load(&cfg->dll, filename); - cfg->enable = GetPrivateProfileIntW(L"aime", L"enable", 1, filename); -} - -void io4_config_load(struct io4_config *cfg, const wchar_t *filename) -{ - assert(cfg != NULL); - assert(filename != NULL); - - cfg->enable = GetPrivateProfileIntW(L"io4", L"enable", 1, filename); -} diff --git a/board/vfd.c b/board/vfd.c deleted file mode 100644 index 081a8d8..0000000 --- a/board/vfd.c +++ /dev/null @@ -1,62 +0,0 @@ -/* This is some sort of LCD display found on various cabinets. It is driven - directly by amdaemon, and it has something to do with displaying the status - of electronic payments. - - Part number in schematics is "VFD GP1232A02A FUTABA". - - Little else about this board is known. Black-holing the RS232 comms that it - receives seems to be sufficient for the time being. */ - -#include - -#include -#include - -#include "board/vfd.h" - -#include "hook/iohook.h" - -#include "hooklib/uart.h" - -#include "util/dprintf.h" -#include "util/dump.h" - -static HRESULT vfd_handle_irp(struct irp *irp); - -static struct uart vfd_uart; -static uint8_t vfd_written[512]; -static uint8_t vfd_readable[512]; - -HRESULT vfd_hook_init(unsigned int port_no) -{ - uart_init(&vfd_uart, port_no); - vfd_uart.written.bytes = vfd_written; - vfd_uart.written.nbytes = sizeof(vfd_written); - vfd_uart.readable.bytes = vfd_readable; - vfd_uart.readable.nbytes = sizeof(vfd_readable); - - return iohook_push_handler(vfd_handle_irp); -} - -static HRESULT vfd_handle_irp(struct irp *irp) -{ - HRESULT hr; - - assert(irp != NULL); - - if (!uart_match_irp(&vfd_uart, irp)) { - return iohook_invoke_next(irp); - } - - hr = uart_handle_irp(&vfd_uart, irp); - - if (FAILED(hr) || irp->op != IRP_OP_WRITE) { - return hr; - } - - dprintf("VFD TX:\n"); - dump_iobuf(&vfd_uart.written); - vfd_uart.written.pos = 0; - - return hr; -} diff --git a/board/vfd.h b/board/vfd.h deleted file mode 100644 index 01cd82e..0000000 --- a/board/vfd.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include - -HRESULT vfd_hook_init(unsigned int port_no); diff --git a/carolhook/controlbd.c b/carolhook/controlbd.c deleted file mode 100644 index 44fa5a6..0000000 --- a/carolhook/controlbd.c +++ /dev/null @@ -1,178 +0,0 @@ -#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 deleted file mode 100644 index 500efd7..0000000 --- a/carolhook/controlbd.h +++ /dev/null @@ -1,21 +0,0 @@ -#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/serial.c b/carolhook/serial.c deleted file mode 100644 index 3a50907..0000000 --- a/carolhook/serial.c +++ /dev/null @@ -1,39 +0,0 @@ -#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 deleted file mode 100644 index 8a682ac..0000000 --- a/carolhook/serial.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once -#include -#include - -void serial_init(); \ No newline at end of file diff --git a/carolhook/touch.c b/carolhook/touch.c deleted file mode 100644 index 8cbb8ca..0000000 --- a/carolhook/touch.c +++ /dev/null @@ -1,118 +0,0 @@ -#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 deleted file mode 100644 index 6881b8f..0000000 --- a/carolhook/touch.h +++ /dev/null @@ -1,17 +0,0 @@ -#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 deleted file mode 100644 index 1ae4817..0000000 --- a/carolio/carolio.c +++ /dev/null @@ -1,79 +0,0 @@ -#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/chunihook/config.c b/chunihook/config.c deleted file mode 100644 index 0f049b2..0000000 --- a/chunihook/config.c +++ /dev/null @@ -1,61 +0,0 @@ -#include - -#include -#include -#include - -#include "amex/amex.h" -#include "amex/config.h" - -#include "board/config.h" -#include "board/sg-reader.h" - -#include "chunihook/config.h" - -#include "gfxhook/config.h" - -#include "hooklib/config.h" - -#include "platform/config.h" -#include "platform/platform.h" - -void chuni_dll_config_load( - struct chuni_dll_config *cfg, - const wchar_t *filename) -{ - assert(cfg != NULL); - assert(filename != NULL); - - GetPrivateProfileStringW( - L"chuniio", - L"path", - L"", - cfg->path, - _countof(cfg->path), - filename); -} - -void slider_config_load(struct slider_config *cfg, const wchar_t *filename) -{ - assert(cfg != NULL); - assert(filename != NULL); - - cfg->enable = GetPrivateProfileIntW(L"slider", L"enable", 1, filename); -} - -void chuni_hook_config_load( - struct chuni_hook_config *cfg, - const wchar_t *filename) -{ - assert(cfg != NULL); - assert(filename != NULL); - - memset(cfg, 0, sizeof(*cfg)); - - platform_config_load(&cfg->platform, filename); - amex_config_load(&cfg->amex, filename); - aime_config_load(&cfg->aime, filename); - gfx_config_load(&cfg->gfx, filename); - chuni_dll_config_load(&cfg->dll, filename); - slider_config_load(&cfg->slider, filename); -} diff --git a/chuniio/chuniio.c b/chuniio/chuniio.c deleted file mode 100644 index 4fffe69..0000000 --- a/chuniio/chuniio.c +++ /dev/null @@ -1,138 +0,0 @@ -#include - -#include -#include -#include - -#include "chuniio/chuniio.h" -#include "chuniio/config.h" - -static unsigned int __stdcall chuni_io_slider_thread_proc(void *ctx); - -static bool chuni_io_coin; -static uint16_t chuni_io_coins; -static uint8_t chuni_io_hand_pos; -static HANDLE chuni_io_slider_thread; -static bool chuni_io_slider_stop_flag; -static struct chuni_io_config chuni_io_cfg; - -uint16_t chuni_io_get_api_version(void) -{ - return 0x0101; -} - -HRESULT chuni_io_jvs_init(void) -{ - chuni_io_config_load(&chuni_io_cfg, L".\\segatools.ini"); - - return S_OK; -} - -void chuni_io_jvs_read_coin_counter(uint16_t *out) -{ - if (out == NULL) { - return; - } - - if (GetAsyncKeyState(chuni_io_cfg.vk_coin)) { - if (!chuni_io_coin) { - chuni_io_coin = true; - chuni_io_coins++; - } - } else { - chuni_io_coin = false; - } - - *out = chuni_io_coins; -} - -void chuni_io_jvs_poll(uint8_t *opbtn, uint8_t *beams) -{ - size_t i; - - if (GetAsyncKeyState(chuni_io_cfg.vk_test)) { - *opbtn |= 0x01; /* Test */ - } - - if (GetAsyncKeyState(chuni_io_cfg.vk_service)) { - *opbtn |= 0x02; /* Service */ - } - - if (GetAsyncKeyState(chuni_io_cfg.vk_ir)) { - if (chuni_io_hand_pos < 6) { - chuni_io_hand_pos++; - } - } else { - if (chuni_io_hand_pos > 0) { - chuni_io_hand_pos--; - } - } - - for (i = 0 ; i < 6 ; i++) { - if (chuni_io_hand_pos > i) { - *beams |= (1 << i); - } - } -} - -HRESULT chuni_io_slider_init(void) -{ - return S_OK; -} - -void chuni_io_slider_start(chuni_io_slider_callback_t callback) -{ - if (chuni_io_slider_thread != NULL) { - return; - } - - chuni_io_slider_thread = (HANDLE) _beginthreadex( - NULL, - 0, - chuni_io_slider_thread_proc, - callback, - 0, - NULL); -} - -void chuni_io_slider_stop(void) -{ - if (chuni_io_slider_thread == NULL) { - return; - } - - chuni_io_slider_stop_flag = true; - - WaitForSingleObject(chuni_io_slider_thread, INFINITE); - CloseHandle(chuni_io_slider_thread); - chuni_io_slider_thread = NULL; - chuni_io_slider_stop_flag = false; -} - -void chuni_io_slider_set_leds(const uint8_t *rgb) -{ -} - -static unsigned int __stdcall chuni_io_slider_thread_proc(void *ctx) -{ - chuni_io_slider_callback_t callback; - uint8_t pressure[32]; - size_t i; - - callback = ctx; - - while (!chuni_io_slider_stop_flag) { - for (i = 0 ; i < _countof(pressure) ; i++) { - if (GetAsyncKeyState(chuni_io_cfg.vk_cell[i]) & 0x8000) { - pressure[i] = 128; - } else { - pressure[i] = 0; - } - } - - callback(pressure); - Sleep(1); - } - - return 0; -} diff --git a/chuniio/config.c b/chuniio/config.c deleted file mode 100644 index 7365f2c..0000000 --- a/chuniio/config.c +++ /dev/null @@ -1,43 +0,0 @@ -#include - -#include -#include -#include - -#include "chuniio/config.h" - -static const int chuni_io_default_cells[] = { - 'L', 'L', 'L', 'L', - 'K', 'K', 'K', 'K', - 'J', 'J', 'J', 'J', - 'H', 'H', 'H', 'H', - 'G', 'G', 'G', 'G', - 'F', 'F', 'F', 'F', - 'D', 'D', 'D', 'D', - 'S', 'S', 'S', 'S', -}; - -void chuni_io_config_load( - struct chuni_io_config *cfg, - const wchar_t *filename) -{ - wchar_t key[16]; - int i; - - 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); - cfg->vk_ir = GetPrivateProfileIntW(L"io3", L"ir", VK_SPACE, filename); - - for (i = 0 ; i < 32 ; i++) { - swprintf_s(key, _countof(key), L"cell%i", i + 1); - cfg->vk_cell[i] = GetPrivateProfileIntW( - L"slider", - key, - chuni_io_default_cells[i], - filename); - } -} diff --git a/aimeio/aimeio.c b/common/aimeio/aimeio.c similarity index 76% rename from aimeio/aimeio.c rename to common/aimeio/aimeio.c index f505aac..8192bae 100644 --- a/aimeio/aimeio.c +++ b/common/aimeio/aimeio.c @@ -12,11 +12,13 @@ #include "util/crc.h" #include "util/dprintf.h" +#include "util/env.h" struct aime_io_config { wchar_t aime_path[MAX_PATH]; wchar_t felica_path[MAX_PATH]; bool felica_gen; + bool aime_gen; uint8_t vk_scan; }; @@ -40,6 +42,11 @@ static HRESULT aime_io_generate_felica( uint8_t *bytes, size_t nbytes); +static HRESULT aime_io_generate_aime( + const wchar_t *path, + uint8_t *bytes, + size_t nbytes); + static void aime_io_config_read( struct aime_io_config *cfg, const wchar_t *filename) @@ -62,11 +69,16 @@ static void aime_io_config_read( cfg->felica_path, _countof(cfg->felica_path), filename); - dprintf("NFC: felicaPath GetLastError %lx\n", GetLastError()); cfg->felica_gen = GetPrivateProfileIntW( L"aime", L"felicaGen", + 0, + filename); + + cfg->aime_gen = GetPrivateProfileIntW( + L"aime", + L"aimeGen", 1, filename); @@ -136,7 +148,7 @@ static HRESULT aime_io_generate_felica( srand(time(NULL)); - for (i = 0 ; i < nbytes ; i++) { + for (i = 0; i < nbytes; i++) { bytes[i] = rand(); } @@ -151,7 +163,7 @@ static HRESULT aime_io_generate_felica( return E_FAIL; } - for (i = 0 ; i < nbytes ; i++) { + for (i = 0; i < nbytes; i++) { fprintf(f, "%02X", bytes[i]); } @@ -163,6 +175,47 @@ static HRESULT aime_io_generate_felica( return S_OK; } +static HRESULT aime_io_generate_aime( + const wchar_t *path, + uint8_t *bytes, + size_t nbytes) +{ + size_t i; + FILE *f; + + assert(path != NULL); + assert(bytes != NULL); + assert(nbytes > 0); + + srand(time(NULL)); + + /* AiMe IDs should not start with 3, due to a missing check for BananaPass IDs */ + do { + for (i = 0; i < nbytes; i++) { + bytes[i] = rand() % 10 << 4 | rand() % 10; + } + } while (bytes[0] >> 4 == 3); + + f = _wfopen(path, L"w"); + + if (f == NULL) { + dprintf("AimeIO DLL: %S: fopen failed: %i\n", path, (int) errno); + + return E_FAIL; + } + + for (i = 0; i < nbytes; i++) { + fprintf(f, "%02x", bytes[i]); + } + + fprintf(f, "\n"); + fclose(f); + + dprintf("AimeIO DLL: Generated random AiMe ID\n"); + + return S_OK; +} + uint16_t aime_io_get_api_version(void) { return 0x0100; @@ -170,7 +223,7 @@ uint16_t aime_io_get_api_version(void) HRESULT aime_io_init(void) { - aime_io_config_read(&aime_io_cfg, L".\\segatools.ini"); + aime_io_config_read(&aime_io_cfg, get_config_path()); return S_OK; } @@ -210,6 +263,22 @@ HRESULT aime_io_nfc_poll(uint8_t unit_no) return S_OK; } + /* Try generating AiMe IC (if enabled) */ + + if (aime_io_cfg.aime_gen) { + hr = aime_io_generate_aime( + aime_io_cfg.aime_path, + aime_io_aime_id, + sizeof(aime_io_aime_id)); + + if (FAILED(hr)) { + return hr; + } + + aime_io_aime_id_present = true; + return S_OK; + } + /* Try FeliCa IC */ hr = aime_io_read_id_file( diff --git a/aimeio/aimeio.h b/common/aimeio/aimeio.h similarity index 100% rename from aimeio/aimeio.h rename to common/aimeio/aimeio.h diff --git a/aimeio/meson.build b/common/aimeio/meson.build similarity index 87% rename from aimeio/meson.build rename to common/aimeio/meson.build index ddbb66a..46b0beb 100644 --- a/aimeio/meson.build +++ b/common/aimeio/meson.build @@ -3,7 +3,6 @@ aimeio_lib = static_library( name_prefix : '', include_directories: inc, implicit_include_directories : false, - c_pch : '../precompiled.h', link_with : [ util_lib, ], diff --git a/amex/amex.c b/common/amex/amex.c similarity index 100% rename from amex/amex.c rename to common/amex/amex.c diff --git a/amex/amex.h b/common/amex/amex.h similarity index 100% rename from amex/amex.h rename to common/amex/amex.h diff --git a/amex/config.c b/common/amex/config.c similarity index 100% rename from amex/config.c rename to common/amex/config.c diff --git a/amex/config.h b/common/amex/config.h similarity index 100% rename from amex/config.h rename to common/amex/config.h diff --git a/amex/ds.c b/common/amex/ds.c similarity index 93% rename from amex/ds.c rename to common/amex/ds.c index c0f357f..0989ac4 100644 --- a/amex/ds.c +++ b/common/amex/ds.c @@ -1,10 +1,11 @@ #include #include -#include +#include #include #include #include +#include #include #include "amex/ds.h" @@ -19,13 +20,11 @@ #include "util/dprintf.h" #include "util/str.h" -#pragma pack(push, 1) +#define DS_IOCTL_GET_ABI_VERSION CTL_CODE(0x8000, 0x800, METHOD_BUFFERED, FILE_READ_ACCESS) +#define DS_IOCTL_SETUP CTL_CODE(0x8000, 0x801, METHOD_BUFFERED, FILE_READ_ACCESS) +#define DS_IOCTL_READ_SECTOR CTL_CODE(0x8000, 0x804, METHOD_BUFFERED, FILE_READ_ACCESS) -enum { - DS_IOCTL_GET_ABI_VERSION = 0x80006000, - DS_IOCTL_SETUP = 0x80006004, - DS_IOCTL_READ_SECTOR = 0x80006010, -}; +#pragma pack(push, 1) struct ds_eeprom { uint32_t crc32; diff --git a/amex/ds.h b/common/amex/ds.h similarity index 100% rename from amex/ds.h rename to common/amex/ds.h diff --git a/amex/eeprom.c b/common/amex/eeprom.c similarity index 97% rename from amex/eeprom.c rename to common/amex/eeprom.c index 84e4701..b06c4bd 100644 --- a/amex/eeprom.c +++ b/common/amex/eeprom.c @@ -6,7 +6,7 @@ #include #endif #include -#include +#include #include @@ -20,9 +20,7 @@ #include "util/dprintf.h" #include "util/str.h" -enum { - EEPROM_IOCTL_GET_ABI_VERSION = 0x80006000, -}; +#define EEPROM_IOCTL_GET_ABI_VERSION CTL_CODE(0x8000, 0x800, METHOD_BUFFERED, FILE_READ_ACCESS) static HRESULT eeprom_handle_irp(struct irp *irp); static HRESULT eeprom_handle_open(struct irp *irp); diff --git a/amex/eeprom.h b/common/amex/eeprom.h similarity index 100% rename from amex/eeprom.h rename to common/amex/eeprom.h diff --git a/amex/gpio.c b/common/amex/gpio.c similarity index 92% rename from amex/gpio.c rename to common/amex/gpio.c index 4e18630..5d6ead1 100644 --- a/amex/gpio.c +++ b/common/amex/gpio.c @@ -1,5 +1,5 @@ #include -#include +#include #include #include @@ -13,12 +13,10 @@ #include "util/dprintf.h" #include "util/str.h" -enum { - GPIO_IOCTL_SET_LEDS = 0x8000A004, - GPIO_IOCTL_GET_PSW = 0x80006008, - GPIO_IOCTL_GET_DIPSW = 0x8000600C, - GPIO_IOCTL_DESCRIBE = 0x80006014, -}; +#define GPIO_IOCTL_SET_LEDS CTL_CODE(0x8000, 0x801, METHOD_BUFFERED, FILE_WRITE_ACCESS) +#define GPIO_IOCTL_GET_PSW CTL_CODE(0x8000, 0x802, METHOD_BUFFERED, FILE_READ_ACCESS) +#define GPIO_IOCTL_GET_DIPSW CTL_CODE(0x8000, 0x803, METHOD_BUFFERED, FILE_READ_ACCESS) +#define GPIO_IOCTL_DESCRIBE CTL_CODE(0x8000, 0x805, METHOD_BUFFERED, FILE_READ_ACCESS) enum { GPIO_TYPE_NONE = 0, diff --git a/amex/gpio.h b/common/amex/gpio.h similarity index 100% rename from amex/gpio.h rename to common/amex/gpio.h diff --git a/amex/guid.c b/common/amex/guid.c similarity index 100% rename from amex/guid.c rename to common/amex/guid.c diff --git a/amex/jvs.c b/common/amex/jvs.c similarity index 93% rename from amex/jvs.c rename to common/amex/jvs.c index 40d9673..0c2dfc7 100644 --- a/amex/jvs.c +++ b/common/amex/jvs.c @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -21,11 +22,9 @@ #include "util/dump.h" #include "util/str.h" -enum { - JVS_IOCTL_HELLO = 0x80006004, - JVS_IOCTL_SENSE = 0x8000600C, - JVS_IOCTL_TRANSACT = 0x8000E008, -}; +#define JVS_IOCTL_HELLO CTL_CODE(0x8000, 0x801, METHOD_BUFFERED, FILE_READ_ACCESS) +#define JVS_IOCTL_TRANSACT CTL_CODE(0x8000, 0x802, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define JVS_IOCTL_SENSE CTL_CODE(0x8000, 0x803, METHOD_BUFFERED, FILE_READ_ACCESS) static HRESULT jvs_handle_irp(struct irp *irp); static HRESULT jvs_handle_open(struct irp *irp); @@ -185,14 +184,14 @@ static HRESULT jvs_ioctl_sense(struct irp *irp) static HRESULT jvs_ioctl_transact(struct irp *irp) { -#if 0 +#if defined(LOG_JVS) dprintf("\nJVS Port: Outbound frame:\n"); dump_const_iobuf(&irp->write); #endif jvs_bus_transact(jvs_root, irp->write.bytes, irp->write.nbytes, &irp->read); -#if 0 +#if defined(LOG_JVS) dprintf("JVS Port: Inbound frame:\n"); dump_iobuf(&irp->read); dprintf("\n"); diff --git a/amex/jvs.h b/common/amex/jvs.h similarity index 100% rename from amex/jvs.h rename to common/amex/jvs.h diff --git a/amex/meson.build b/common/amex/meson.build similarity index 94% rename from amex/meson.build rename to common/amex/meson.build index 0f4d61e..7476aa4 100644 --- a/amex/meson.build +++ b/common/amex/meson.build @@ -2,7 +2,6 @@ amex_lib = static_library( 'amex', include_directories : inc, implicit_include_directories : false, - c_pch : '../precompiled.h', dependencies : [ capnhook.get_variable('hook_dep'), ], diff --git a/amex/nvram.c b/common/amex/nvram.c similarity index 100% rename from amex/nvram.c rename to common/amex/nvram.c diff --git a/amex/nvram.h b/common/amex/nvram.h similarity index 100% rename from amex/nvram.h rename to common/amex/nvram.h diff --git a/amex/sram.c b/common/amex/sram.c similarity index 96% rename from amex/sram.c rename to common/amex/sram.c index c5a195a..fa4f005 100644 --- a/amex/sram.c +++ b/common/amex/sram.c @@ -6,7 +6,7 @@ #include #endif #include -#include +#include #include @@ -20,9 +20,7 @@ #include "util/dprintf.h" #include "util/str.h" -enum { - SRAM_IOCTL_GET_ABI_VERSION = 0x80006000, -}; +#define SRAM_IOCTL_GET_ABI_VERSION CTL_CODE(0x8000, 0x800, METHOD_BUFFERED, FILE_READ_ACCESS) static HRESULT sram_handle_irp(struct irp *irp); static HRESULT sram_handle_open(struct irp *irp); diff --git a/amex/sram.h b/common/amex/sram.h similarity index 100% rename from amex/sram.h rename to common/amex/sram.h diff --git a/board/aime-dll.c b/common/board/aime-dll.c similarity index 100% rename from board/aime-dll.c rename to common/board/aime-dll.c diff --git a/board/aime-dll.h b/common/board/aime-dll.h similarity index 94% rename from board/aime-dll.h rename to common/board/aime-dll.h index 354516b..25c52f0 100644 --- a/board/aime-dll.h +++ b/common/board/aime-dll.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include "aimeio/aimeio.h" @@ -18,6 +19,7 @@ struct aime_dll { struct aime_dll_config { wchar_t path[MAX_PATH]; + bool path64; }; extern struct aime_dll aime_dll; diff --git a/common/board/config.c b/common/board/config.c new file mode 100644 index 0000000..96c4146 --- /dev/null +++ b/common/board/config.c @@ -0,0 +1,113 @@ +#include + +#include +#include +#include +#include + +#include "board/aime-dll.h" +#include "board/config.h" +#include "board/sg-reader.h" +#include "board/vfd.h" + +#include "util/dprintf.h" + +// Check windows +#if _WIN32 || _WIN64 + #if _WIN64 + #define ENV64BIT + #else + #define ENV32BIT + #endif +#endif + +// Check GCC +#if __GNUC__ + #if __x86_64__ || __ppc64__ + #define ENV64BIT + #else + #define ENV32BIT + #endif +#endif + +static void aime_dll_config_load(struct aime_dll_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + // Workaround for x64/x86 external IO dlls + // path32 for 32bit, path64 for 64bit + // for else.. is that possible? idk + + if (cfg->path64) { + #if defined(ENV32BIT) + // Always empty, due to amdaemon being 64 bit in 32 bit mode + memset(cfg->path, 0, sizeof(cfg->path)); + #elif defined(ENV64BIT) + GetPrivateProfileStringW( + L"aimeio", + L"path", + L"", + cfg->path, + _countof(cfg->path), + filename); + #else + #error "Unknown environment" + #endif + } else { + GetPrivateProfileStringW( + L"aimeio", + L"path", + L"", + cfg->path, + _countof(cfg->path), + filename); + } +} + +void aime_config_load(struct aime_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + aime_dll_config_load(&cfg->dll, filename); + cfg->enable = GetPrivateProfileIntW(L"aime", L"enable", 1, filename); + cfg->port_no = GetPrivateProfileIntW(L"aime", L"portNo", 0, filename); + cfg->high_baudrate = GetPrivateProfileIntW(L"aime", L"highBaud", 1, filename); + cfg->gen = GetPrivateProfileIntW(L"aime", L"gen", 0, filename); + cfg->proxy_flag = GetPrivateProfileIntW(L"aime", L"proxyFlag", 2, filename); + + GetPrivateProfileStringW( + L"aime", + L"authdataPath", + L"DEVICE\\authdata.bin", + cfg->authdata_path, + _countof(cfg->authdata_path), + filename); +} + +void io4_config_load(struct io4_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"io4", L"enable", 1, filename); +} + +void vfd_config_load(struct vfd_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"vfd", L"enable", 1, filename); + cfg->port_no = GetPrivateProfileIntW(L"vfd", L"portNo", 0, filename); + cfg->utf_conversion = GetPrivateProfileIntW(L"vfd", L"utfConversion", 0, filename); +} + +void ffb_config_load(struct ffb_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"ffb", L"enable", 1, filename); +} diff --git a/board/config.h b/common/board/config.h similarity index 57% rename from board/config.h rename to common/board/config.h index f436a82..34977dc 100644 --- a/board/config.h +++ b/common/board/config.h @@ -5,6 +5,10 @@ #include "board/io4.h" #include "board/sg-reader.h" +#include "board/vfd.h" +#include "board/ffb.h" void aime_config_load(struct aime_config *cfg, const wchar_t *filename); void io4_config_load(struct io4_config *cfg, const wchar_t *filename); +void vfd_config_load(struct vfd_config *cfg, const wchar_t *filename); +void ffb_config_load(struct ffb_config *cfg, const wchar_t *filename); diff --git a/common/board/ffb.c b/common/board/ffb.c new file mode 100644 index 0000000..e7a11f7 --- /dev/null +++ b/common/board/ffb.c @@ -0,0 +1,235 @@ +/* + Force Feedback Board (FFB) + + This board is used by many SEGA games to provide force feedback to the player. + It is driven by the game software over a serial connection and is used by many + games such as SEGA World Drivers Championship, Initial D Arcade, ... + + Part number in schematics is "838-15069 MOTOR DRIVE BD RS232/422 Board". + + Some observations: + The maximal strength for any effect is 127, except Damper which maxes out at 40. + The period for rumble effects is in the range 0-40. +*/ + +#include "board/ffb.h" + +#include +#include +#include + +#include "hook/iohook.h" +#include "hooklib/uart.h" +#include "util/dprintf.h" +#include "util/dump.h" + + +// request format: +// 0x?? - sync + command +// 0x?? - direction/additional command +// 0x?? - strength +// 0x?? - checksum (sum of everything except the sync byte) + +enum { + FFB_CMD_TOGGLE = 0x80, + FFB_CMD_CONSTANT_FORCE = 0x84, + FFB_CMD_RUMBLE = 0x85, + FFB_CMD_DAMPER = 0x86, +}; + +struct ffb_hdr { + uint8_t cmd; +}; + +union ffb_req_any { + struct ffb_hdr hdr; + uint8_t bytes[3]; +}; + +static HRESULT ffb_handle_irp(struct irp *irp); + +static HRESULT ffb_req_dispatch(const union ffb_req_any *req); +static HRESULT ffb_req_toggle(const uint8_t *bytes); +static HRESULT ffb_req_constant_force(const uint8_t *bytes); +static HRESULT ffb_req_rumble(const uint8_t *bytes); +static HRESULT ffb_req_damper(const uint8_t *bytes); + +static const struct ffb_ops *ffb_ops; +static struct uart ffb_uart; + +static bool ffb_started; +static HRESULT ffb_start_hr; +static uint8_t ffb_written[4]; +static uint8_t ffb_readable[4]; + +/* Static variables to store maximum strength values */ +static uint8_t max_constant_force = 0; +static uint8_t max_rumble = 0; +static uint8_t max_period = 0; +static uint8_t max_damper = 0; + +HRESULT ffb_hook_init( + const struct ffb_config *cfg, + const struct ffb_ops *ops, + unsigned int port_no) +{ + assert(cfg != NULL); + assert(ops != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + ffb_ops = ops; + + uart_init(&ffb_uart, port_no); + ffb_uart.written.bytes = ffb_written; + ffb_uart.written.nbytes = sizeof(ffb_written); + ffb_uart.readable.bytes = ffb_readable; + ffb_uart.readable.nbytes = sizeof(ffb_readable); + + dprintf("FFB: hook enabled.\n"); + + return iohook_push_handler(ffb_handle_irp); +} + +static HRESULT ffb_handle_irp(struct irp *irp) +{ + HRESULT hr; + + assert(irp != NULL); + + if (!uart_match_irp(&ffb_uart, irp)) { + return iohook_invoke_next(irp); + } + + hr = uart_handle_irp(&ffb_uart, irp); + + if (FAILED(hr) || irp->op != IRP_OP_WRITE) { + return hr; + } + + assert(&ffb_uart.written != NULL); + assert(ffb_uart.written.bytes != NULL || ffb_uart.written.nbytes == 0); + assert(ffb_uart.written.pos <= ffb_uart.written.nbytes); + + // dprintf("FFB TX:\n"); + + hr = ffb_req_dispatch((const union ffb_req_any *) ffb_uart.written.bytes); + + if (FAILED(hr)) { + dprintf("FFB: Processing error: %x\n", (int)hr); + } + + // dump_iobuf(&ffb_uart.written); + ffb_uart.written.pos = 0; + + return hr; +} + +static HRESULT ffb_req_dispatch(const union ffb_req_any *req) +{ + switch (req->hdr.cmd) { + case FFB_CMD_TOGGLE: + return ffb_req_toggle(req->bytes); + case FFB_CMD_CONSTANT_FORCE: + return ffb_req_constant_force(req->bytes); + case FFB_CMD_RUMBLE: + return ffb_req_rumble(req->bytes); + case FFB_CMD_DAMPER: + return ffb_req_damper(req->bytes); + + /* There are some test mode specfic commands which doesn't seem to be used in + game at all. The same is true for the initialization phase. */ + + default: + dprintf("FFB: Unhandled command %02x\n", req->hdr.cmd); + + return S_OK; + } +} + +static HRESULT ffb_req_toggle(const uint8_t *bytes) +{ + uint8_t activate = bytes[2]; + + if (activate == 0x01) { + dprintf("FFB: Activated\n"); + } else { + dprintf("FFB: Deactivated\n"); + } + + if (ffb_ops->toggle != NULL) { + ffb_ops->toggle(activate == 0x01); + } + + return S_OK; +} + +static HRESULT ffb_req_constant_force(const uint8_t *bytes) +{ + // dprintf("FFB: Constant force\n"); + + uint8_t direction = bytes[1]; + uint8_t force = bytes[2]; + + if (direction == 0x0) { + // Right + force = 128 - force; + } + + // Update max strength if the current force is greater + if (force > max_constant_force) { + max_constant_force = force; + } + + // dprintf("FFB: Constant Force Strength: %d (Max: %d)\n", force, max_constant_force); + if (ffb_ops->constant_force != NULL) { + ffb_ops->constant_force(direction, force); + } + + return S_OK; +} + +static HRESULT ffb_req_rumble(const uint8_t *bytes) +{ + // dprintf("FFB: Rumble\n"); + + uint8_t force = bytes[1]; + uint8_t period = bytes[2]; + + // Update max strength if the current force is greater + if (force > max_rumble) { + max_rumble = force; + } + + if (period > max_period) { + max_period = period; + } + + // dprintf("FFB: Rumble Period: %d (Max %d), Strength: %d (Max: %d)\n", period, max_period, force, max_rumble); + if (ffb_ops->rumble != NULL) { + ffb_ops->rumble(force, period); + } + + return S_OK; +} + +static HRESULT ffb_req_damper(const uint8_t *bytes) +{ + // dprintf("FFB: Damper\n"); + + uint8_t force = bytes[2]; + + // Update max strength if the current force is greater + if (force > max_damper) { + max_damper = force; + } + + // dprintf("FFB: Damper Strength: %d (Max: %d)\n", force, max_damper); + if (ffb_ops->damper != NULL) { + ffb_ops->damper(force); + } + + return S_OK; +} diff --git a/common/board/ffb.h b/common/board/ffb.h new file mode 100644 index 0000000..4dbc057 --- /dev/null +++ b/common/board/ffb.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include +#include + +struct ffb_config { + bool enable; +}; + +struct ffb_ops { + void (*toggle)(bool active); + void (*constant_force)(uint8_t direction, uint8_t force); + void (*rumble)(uint8_t force, uint8_t period); + void (*damper)(uint8_t force); +}; + +HRESULT ffb_hook_init( + const struct ffb_config *cfg, + const struct ffb_ops *ops, + unsigned int port_no); diff --git a/board/guid.c b/common/board/guid.c similarity index 100% rename from board/guid.c rename to common/board/guid.c diff --git a/board/guid.h b/common/board/guid.h similarity index 100% rename from board/guid.h rename to common/board/guid.h diff --git a/board/io3.c b/common/board/io3.c similarity index 90% rename from board/io3.c rename to common/board/io3.c index 369714f..d61cfdc 100644 --- a/board/io3.c +++ b/common/board/io3.c @@ -16,6 +16,7 @@ #include #include #include +#include #include "board/io3.h" @@ -79,6 +80,11 @@ static HRESULT io3_cmd_read_analogs( struct const_iobuf *req_buf, struct iobuf *resp_buf); +static HRESULT io3_cmd_read_rotarys( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf); + static HRESULT io3_cmd_write_gpio( struct io3 *io3, struct const_iobuf *req_buf, @@ -116,6 +122,13 @@ static uint8_t io3_features[] = { 0x03, 8, 10, 0, + /* Feature : 0x04 : Rotary inputs + Param1 : 4 : Number of rotary channels + Param2 : 0 : N/A + Param3 : 0 : N/A */ + + 0x04, 4, 0, 0, + /* Feature : 0x12 : GPIO outputs Param1 : 3 : Number of ports (8 bits per port) Param2 : 0 : N/A @@ -219,6 +232,9 @@ static HRESULT io3_cmd( case JVS_CMD_READ_ANALOGS: return io3_cmd_read_analogs(io3, req, resp); + case JVS_CMD_READ_ROTARYS: + return io3_cmd_read_rotarys(io3, req, resp); + case JVS_CMD_WRITE_GPIO: return io3_cmd_write_gpio(io3, req, resp); @@ -375,7 +391,7 @@ static HRESULT io3_cmd_read_switches( return hr; } -#if 0 +#if defined(LOG_IO3) dprintf("JVS I/O: Read switches, np=%i, bpp=%i\n", req.num_players, req.bytes_per_player); @@ -536,6 +552,60 @@ static HRESULT io3_cmd_read_analogs( } +static HRESULT io3_cmd_read_rotarys( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf) +{ + struct jvs_req_read_rotarys req; + uint16_t rotarys[4]; + uint8_t i; + HRESULT hr; + + /* Read req */ + + hr = iobuf_read(req_buf, &req, sizeof(req)); + + if (FAILED(hr)) { + return hr; + } + + if (req.nrotarys > _countof(rotarys)) { + dprintf("JVS I/O: Invalid analog count %i\n", req.nrotarys); + + return E_FAIL; + } + + //dprintf("JVS I/O: Read rotarys, nrotarys=%i\n", req.nrotarys); + + /* Write report byte */ + + hr = iobuf_write_8(resp_buf, 0x01); + + if (FAILED(hr)) { + return hr; + } + + /* Write analogs */ + + memset(rotarys, 0, sizeof(rotarys)); + + if (io3->ops->read_rotarys != NULL) { + io3->ops->read_rotarys(io3->ops_ctx, rotarys, req.nrotarys); + } + + for (i = 0 ; i < req.nrotarys ; i++) { + hr = iobuf_write_be16(resp_buf, rotarys[i]); + + if (FAILED(hr)) { + return hr; + } + } + + return hr; + +} + static HRESULT io3_cmd_write_gpio( struct io3 *io3, struct const_iobuf *req_buf, diff --git a/board/io3.h b/common/board/io3.h similarity index 91% rename from board/io3.h rename to common/board/io3.h index a094a2a..78fdca8 100644 --- a/board/io3.h +++ b/common/board/io3.h @@ -18,6 +18,7 @@ struct io3_ops { void (*write_gpio)(void *ctx, uint32_t state); void (*read_switches)(void *ctx, struct io3_switch_state *out); void (*read_analogs)(void *ctx, uint16_t *analogs, uint8_t nanalogs); + void (*read_rotarys)(void *ctx, uint16_t *rotaries, uint8_t nrotaries); void (*read_coin_counter)(void *ctx, uint8_t slot_no, uint16_t *out); }; diff --git a/board/io4.c b/common/board/io4.c similarity index 95% rename from board/io4.c rename to common/board/io4.c index e7fe9e3..e1def19 100644 --- a/board/io4.c +++ b/common/board/io4.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "board/config.h" #include "board/guid.h" @@ -28,7 +29,7 @@ enum { IO4_CMD_CLEAR_BOARD_STATUS = 0x03, IO4_CMD_SET_GENERAL_OUTPUT = 0x04, IO4_CMD_SET_PWM_OUTPUT = 0x05, - IO4_CMD_UNIMPLEMENTED = 0x41, + IO4_CMD_SET_UNIQUE_OUTPUT = 0x41, IO4_CMD_UPDATE_FIRMWARE = 0x85, }; @@ -40,7 +41,7 @@ struct io4_report_in { uint16_t buttons[2]; uint8_t system_status; uint8_t usb_status; - uint8_t unknown[29]; + uint8_t unique_input[29]; }; static_assert(sizeof(struct io4_report_in) == 0x40, "IO4 IN report size"); @@ -48,7 +49,7 @@ static_assert(sizeof(struct io4_report_in) == 0x40, "IO4 IN report size"); struct io4_report_out { uint8_t report_id; uint8_t cmd; - uint8_t payload[62]; + uint8_t payload[IO4_REPORT_OUT_PAYLOAD_LEN]; }; static_assert(sizeof(struct io4_report_out) == 0x40, "IO4 OUT report size"); @@ -223,7 +224,11 @@ static HRESULT io4_handle_write(struct irp *irp) return S_OK; case IO4_CMD_SET_GENERAL_OUTPUT: - dprintf("USB I/O: GPIO Out\n"); + // dprintf("USB I/O: GPIO Out\n"); + + if (io4_ops->write_gpio != NULL) { + return io4_ops->write_gpio(out.payload, IO4_REPORT_OUT_PAYLOAD_LEN); + } return S_OK; @@ -232,15 +237,15 @@ static HRESULT io4_handle_write(struct irp *irp) return S_OK; + case IO4_CMD_SET_UNIQUE_OUTPUT: + // dprintf("USB I/O: Unique Out\n"); + + return S_OK; + case IO4_CMD_UPDATE_FIRMWARE: dprintf("USB I/O: Update firmware..?\n"); - return E_FAIL; - - case IO4_CMD_UNIMPLEMENTED: - //dprintf("USB I/O: Unimplemented cmd 41\n"); - - return S_OK; + return E_FAIL; default: dprintf("USB I/O: Unknown command %02x\n", out.cmd); @@ -316,7 +321,7 @@ static HRESULT io4_async_poll(void *ctx, struct irp *irp) /* Delay long enough for the instigating thread in amdaemon to be satisfied that all queued-up reports have been drained. */ - Sleep(1); + // Sleep(1); /* Call into ops to poll the underlying inputs */ diff --git a/board/io4.h b/common/board/io4.h similarity index 81% rename from board/io4.h rename to common/board/io4.h index 1a6cc05..09a921b 100644 --- a/board/io4.h +++ b/common/board/io4.h @@ -3,6 +3,9 @@ #include #include +#include + +#define IO4_REPORT_OUT_PAYLOAD_LEN 62 enum { /* System buttons in button[0] */ @@ -24,6 +27,7 @@ struct io4_state { struct io4_ops { HRESULT (*poll)(void *ctx, struct io4_state *state); + HRESULT (*write_gpio)(uint8_t* payload, size_t len); }; HRESULT io4_hook_init( diff --git a/common/board/led15070-cmd.h b/common/board/led15070-cmd.h new file mode 100644 index 0000000..d5d739a --- /dev/null +++ b/common/board/led15070-cmd.h @@ -0,0 +1,81 @@ +#pragma once + +#include "board/led15070-frame.h" + +/* Command IDs */ + +enum { + LED_15070_CMD_RESET = 0x10, + LED_15070_CMD_SET_INPUT = 0x28, // No known use case + LED_15070_CMD_SET_NORMAL_12BIT = 0x30, // TODO + LED_15070_CMD_SET_NORMAL_8BIT = 0x31, + LED_15070_CMD_SET_MULTI_FLASH_8BIT = 0x32, + LED_15070_CMD_SET_MULTI_FADE_8BIT = 0x33, + LED_15070_CMD_SET_PALETTE_7_NORMAL_LED = 0x34, // No known use case + LED_15070_CMD_SET_PALETTE_6_FLASH_LED = 0x35, // No known use case + LED_15070_CMD_SET_15DC_OUT = 0x36, // No known use case + LED_15070_CMD_SET_15GS_OUT = 0x37, // No known use case + LED_15070_CMD_SET_PSC_MAX = 0x38, // No known use case + LED_15070_CMD_SET_FET_OUTPUT = 0x39, + LED_15070_CMD_SET_GS_PALETTE = 0x3A, + LED_15070_CMD_DC_UPDATE = 0x3B, + LED_15070_CMD_GS_UPDATE = 0x3C, + LED_15070_CMD_ROTATE = 0x3E, // No known use case, wtf is this? + LED_15070_CMD_SET_DC_DATA = 0x3F, + LED_15070_CMD_EEPROM_WRITE = 0x7B, + LED_15070_CMD_EEPROM_READ = 0x7C, + LED_15070_CMD_ACK_ON = 0x7D, + LED_15070_CMD_ACK_OFF = 0x7E, + LED_15070_CMD_BOARD_INFO = 0xF0, + LED_15070_CMD_BOARD_STATUS = 0xF1, + LED_15070_CMD_FW_SUM = 0xF2, + LED_15070_CMD_PROTOCOL_VER = 0xF3, + LED_15070_CMD_TO_BOOT_MODE = 0xFD, + LED_15070_CMD_FW_UPDATE = 0xFE, +}; + +/* Response codes */ + +enum { + LED_15070_STATUS_OK = 0x01, + LED_15070_STATUS_SUM_ERR = 0x02, + LED_15070_STATUS_PARITY_ERR = 0x03, + LED_15070_STATUS_FRAMING_ERR = 0x04, + LED_15070_STATUS_OVERRUN_ERR = 0x05, + LED_15070_STATUS_BUFFER_OVERFLOW = 0x06, +}; + +enum { + LED_15070_REPORT_OK = 0x01, + LED_15070_REPORT_WAIT = 0x02, + LED_15070_REPORT_ERR1 = 0x03, + LED_15070_REPORT_ERR2 = 0x04, +}; + +/* Request data structures */ + +struct led15070_req_any { + struct led15070_hdr hdr; + uint8_t cmd; + uint8_t payload[256]; +}; + +/* Response data structures */ + +struct led15070_resp_any { + struct led15070_hdr hdr; + uint8_t status; + uint8_t cmd; + uint8_t report; + uint8_t data[32]; +}; + +struct led15070_resp_board_info { + struct led15070_hdr hdr; + uint8_t status; + uint8_t cmd; + uint8_t report; + char board_num[8]; + uint8_t endcode; // Always 0xFF + uint8_t fw_ver; +}; diff --git a/common/board/led15070-frame.c b/common/board/led15070-frame.c new file mode 100644 index 0000000..f550fbd --- /dev/null +++ b/common/board/led15070-frame.c @@ -0,0 +1,194 @@ +#include + +#include +#include +#include +#include + +#include "board/led15070-frame.h" + +#include "hook/iobuf.h" + +static void led15070_frame_sync(struct iobuf *src); +static HRESULT led15070_frame_accept(const struct iobuf *dest); +static HRESULT led15070_frame_encode_byte(struct iobuf *dest, uint8_t byte); + +/* Frame structure: + + [0] Sync byte (0xE0) + [1] Destination address + [2] Source Address + [3] Length of data/payload + [4] Data/payload + For requests (host to board): + [0] Command + ... Payload + For responses (board to host): + [0] Status + [1] Command + [2] Report + ... Payload + [n] Checksum: Sum of all prior bytes (excluding sync byte) + + Byte stuffing: + + 0xD0 is an escape byte. Un-escape the subsequent byte by adding 1. */ + +static void led15070_frame_sync(struct iobuf *src) +{ + size_t i; + + for (i = 0 ; i < src->pos && src->bytes[i] != 0xE0 ; i++); + + src->pos -= i; + memmove(&src->bytes[0], &src->bytes[i], i); +} + +static HRESULT led15070_frame_accept(const struct iobuf *dest) +{ + uint8_t checksum; + size_t i; + + if (dest->pos < 3 || dest->pos != dest->bytes[3] + 5) { + return S_FALSE; + } + + checksum = 0; + + for (i = 1 ; i < dest->pos - 1 ; i++) { + checksum += dest->bytes[i]; + } + + //dprintf("LED checksum %02x, expected %02x\n", checksum, dest->bytes[dest->pos - 1]); + + if (checksum != dest->bytes[dest->pos - 1]) { + return HRESULT_FROM_WIN32(ERROR_CRC); + } + + return S_OK; +} + +HRESULT led15070_frame_decode(struct iobuf *dest, struct iobuf *src) +{ + uint8_t byte; + bool escape; + size_t i; + HRESULT hr; + + assert(dest != NULL); + assert(dest->bytes != NULL || dest->nbytes == 0); + assert(dest->pos <= dest->nbytes); + assert(src != NULL); + assert(src->bytes != NULL || src->nbytes == 0); + assert(src->pos <= src->nbytes); + + led15070_frame_sync(src); + + dest->pos = 0; + escape = false; + + for (i = 0, hr = S_FALSE ; i < src->pos && hr == S_FALSE ; i++) { + /* Step the FSM to unstuff another byte */ + + byte = src->bytes[i]; + + if (dest->pos >= dest->nbytes) { + hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } else if (i == 0) { + dest->bytes[dest->pos++] = byte; + } else if (byte == 0xE0) { + hr = E_FAIL; + } else if (byte == 0xD0) { + if (escape) { + hr = E_FAIL; + } + + escape = true; + } else if (escape) { + dest->bytes[dest->pos++] = byte + 1; + escape = false; + } else { + dest->bytes[dest->pos++] = byte; + } + + /* Try to accept the packet we've built up so far */ + + if (SUCCEEDED(hr)) { + hr = led15070_frame_accept(dest); + } + } + + /* Handle FSM terminal state */ + + if (hr != S_FALSE) { + /* Frame was either accepted or rejected, remove it from src */ + memmove(&src->bytes[0], &src->bytes[i], src->pos - i); + src->pos -= i; + } + + return hr; +} + +HRESULT led15070_frame_encode( + struct iobuf *dest, + const void *ptr, + size_t nbytes) +{ + const uint8_t *src; + uint8_t checksum; + uint8_t byte; + size_t i; + HRESULT hr; + + assert(dest != NULL); + assert(dest->bytes != NULL || dest->nbytes == 0); + assert(dest->pos <= dest->nbytes); + assert(ptr != NULL); + + src = ptr; + + assert(nbytes >= 3 && src[0] == 0xE0 && src[3] + 4 == nbytes); + + if (dest->pos >= dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = 0xE0; + checksum = 0; + // dprintf("%02x ", 0xe0); + + for (i = 1 ; i < nbytes ; i++) { + byte = src[i]; + checksum += byte; + // dprintf("%02x ", byte); + + hr = led15070_frame_encode_byte(dest, byte); + + if (FAILED(hr)) { + return hr; + } + } + // dprintf("%02x \n", checksum); + + return led15070_frame_encode_byte(dest, checksum); +} + +static HRESULT led15070_frame_encode_byte(struct iobuf *dest, uint8_t byte) +{ + if (byte == 0xE0 || byte == 0xD0) { + if (dest->pos + 2 > dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = 0xD0; + dest->bytes[dest->pos++] = byte - 1; + } else { + if (dest->pos + 1 > dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = byte; + } + + return S_OK; +} diff --git a/common/board/led15070-frame.h b/common/board/led15070-frame.h new file mode 100644 index 0000000..078dd32 --- /dev/null +++ b/common/board/led15070-frame.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include +#include + +#include "hook/iobuf.h" + +enum { + LED_15070_FRAME_SYNC = 0xE0, +}; + +struct led15070_hdr { + uint8_t sync; + uint8_t dest_adr; + uint8_t src_adr; + uint8_t nbytes; +}; + +HRESULT led15070_frame_decode(struct iobuf *dest, struct iobuf *src); + +HRESULT led15070_frame_encode( + struct iobuf *dest, + const void *ptr, + size_t nbytes); diff --git a/common/board/led15070.c b/common/board/led15070.c new file mode 100644 index 0000000..43a015d --- /dev/null +++ b/common/board/led15070.c @@ -0,0 +1,1300 @@ +/* + SEGA 837-15070-0X LED Controller Board Emulator + + Credits: + 837-15070-04 LED Controller Board Emulator (emihiok) + 837-15093-06 LED Controller Board Emulator (somewhatlurker, skogaby) + (a/o June 2023) +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "board/led15070-cmd.h" +#include "board/led15070-frame.h" + +#include "board/led15070.h" + +#include "hook/iobuf.h" +#include "hook/iohook.h" + +#include "hooklib/uart.h" + +#include "util/dprintf.h" +#include "util/dump.h" + +static HRESULT led15070_handle_irp(struct irp *irp); +static HRESULT led15070_handle_irp_locked(int board, struct irp *irp); + +static HRESULT led15070_req_dispatch(int board, const struct led15070_req_any *req); +static HRESULT led15070_req_reset(int board, const struct led15070_req_any *req); +static HRESULT led15070_req_set_input(int board, const struct led15070_req_any *req); +static HRESULT led15070_req_set_normal_12bit(int board, const struct led15070_req_any *req); +static HRESULT led15070_req_set_normal_8bit(int board, const struct led15070_req_any *req); +static HRESULT led15070_req_set_multi_flash_8bit(int board, const struct led15070_req_any *req); +static HRESULT led15070_req_set_multi_fade_8bit(int board, const struct led15070_req_any *req); +static HRESULT led15070_req_set_palette_7_normal_led(int board, const struct led15070_req_any *req); +static HRESULT led15070_req_set_palette_6_flash_led(int board, const struct led15070_req_any *req); +static HRESULT led15070_req_set_15dc_out(int board, const struct led15070_req_any *req); +static HRESULT led15070_req_set_15gs_out(int board, const struct led15070_req_any *req); +static HRESULT led15070_req_set_psc_max(int board, const struct led15070_req_any *req); +static HRESULT led15070_req_set_fet_output(int board, const struct led15070_req_any *req); +static HRESULT led15070_req_set_gs_palette(int board, const struct led15070_req_any *req); +static HRESULT led15070_req_dc_update(int board, const struct led15070_req_any *req); +static HRESULT led15070_req_gs_update(int board, const struct led15070_req_any *req); +static HRESULT led15070_req_rotate(int board, const struct led15070_req_any *req); +static HRESULT led15070_req_set_dc_data(int board, const struct led15070_req_any *req); +static HRESULT led15070_req_eeprom_write(int board, const struct led15070_req_any *req); +static HRESULT led15070_req_eeprom_read(int board, const struct led15070_req_any *req); +static HRESULT led15070_req_ack_on(int board, const struct led15070_req_any *req); +static HRESULT led15070_req_ack_off(int board, const struct led15070_req_any *req); +static HRESULT led15070_req_board_info(int board); +static HRESULT led15070_req_board_status(int board); +static HRESULT led15070_req_fw_sum(int board); +static HRESULT led15070_req_protocol_ver(int board); +static HRESULT led15070_req_to_boot_mode(int board); +static HRESULT led15070_req_fw_update(int board, const struct led15070_req_any *req); + +static HRESULT led15070_eeprom_open(int board, wchar_t *path, HANDLE *handle); +static HRESULT led15070_eeprom_close(int board, wchar_t *path, HANDLE *handle); + +static char led15070_board_num[8]; +static uint8_t led15070_fw_ver; +static uint16_t led15070_fw_sum; +static uint8_t led15070_host_adr = 0x01; + +#define led15070_nboards 2 + +typedef struct { + CRITICAL_SECTION lock; + bool started; + HRESULT start_hr; + struct uart boarduart; + uint8_t written_bytes[520]; + uint8_t readable_bytes[520]; + uint8_t gs[32][4]; + uint8_t dc[32][3]; + uint8_t fet[3]; + uint8_t gs_palette[8][3]; + wchar_t eeprom_path[MAX_PATH]; + HANDLE eeprom_handle; + uint8_t boardadr; + bool enable_bootloader; + bool enable_response; +} _led15070_per_board_vars; + +_led15070_per_board_vars led15070_per_board_vars[led15070_nboards]; + +static io_led_init_t led_init; +static io_led_set_fet_output_t led_set_fet_output; +static io_led_dc_update_t led_dc_update; +static io_led_gs_update_t led_gs_update; + +HRESULT led15070_hook_init( + const struct led15070_config *cfg, + io_led_init_t _led_init, + io_led_set_fet_output_t _led_set_fet_output, + io_led_dc_update_t _led_dc_update, + io_led_gs_update_t _led_gs_update, + unsigned int port_no[2]) +{ + unsigned int num_boards = 0; + + assert(cfg != NULL); + assert(_led_init != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + for (int i = 0; i < led15070_nboards; i++) + { + if (cfg->port_no[i] != 0) { + port_no[i] = cfg->port_no[i]; + } + + if (port_no[i] != 0) { + num_boards++; + } + } + + assert(num_boards != 0); + + led_init = _led_init; + led_set_fet_output = _led_set_fet_output; + led_dc_update = _led_dc_update; + led_gs_update = _led_gs_update; + + memcpy(led15070_board_num, cfg->board_number, sizeof(led15070_board_num)); + led15070_fw_ver = cfg->fw_ver; + led15070_fw_sum = cfg->fw_sum; + + for (int i = 0; i < num_boards; i++) + { + _led15070_per_board_vars *v = &led15070_per_board_vars[i]; + + InitializeCriticalSection(&v->lock); + + uart_init(&v->boarduart, port_no[i]); + v->boarduart.baud.BaudRate = 115200; + v->boarduart.written.bytes = v->written_bytes; + v->boarduart.written.nbytes = sizeof(v->written_bytes); + v->boarduart.readable.bytes = v->readable_bytes; + v->boarduart.readable.nbytes = sizeof(v->readable_bytes); + + memset(v->gs, 0, sizeof(v->gs)); + memset(v->dc, 0, sizeof(v->dc)); + memset(v->fet, 0, sizeof(v->fet)); + memset(v->gs_palette, 0, sizeof(v->gs_palette)); + memset(v->eeprom_path, 0, sizeof(v->eeprom_path)); + v->eeprom_handle = NULL; + + swprintf_s + ( + v->eeprom_path, MAX_PATH, + L"%s\\led15070_eeprom_%d.bin", + cfg->eeprom_path, i + ); + + /* Generate board EEPROM file if it doesn't already exist */ + led15070_eeprom_open(i, v->eeprom_path, &(v->eeprom_handle)); + led15070_eeprom_close(i, v->eeprom_path, &(v->eeprom_handle)); + + v->boardadr = 0x11; + v->enable_bootloader = false; + v->enable_response = false; + } + + dprintf("LED 15070: hook enabled.\n"); + + return iohook_push_handler(led15070_handle_irp); +} + +static HRESULT led15070_handle_irp(struct irp *irp) +{ + HRESULT hr; + + assert(irp != NULL); + + for (int i = 0; i < led15070_nboards; i++) + { + _led15070_per_board_vars *v = &led15070_per_board_vars[i]; + struct uart *boarduart = &v->boarduart; + + if (uart_match_irp(boarduart, irp)) + { + CRITICAL_SECTION lock = v->lock; + + EnterCriticalSection(&lock); + hr = led15070_handle_irp_locked(i, irp); + LeaveCriticalSection(&lock); + + return hr; + } + } + + return iohook_invoke_next(irp); +} + +static HRESULT led15070_handle_irp_locked(int board, struct irp *irp) +{ + struct led15070_req_any req; + struct iobuf req_iobuf; + HRESULT hr; + + _led15070_per_board_vars *v = &led15070_per_board_vars[board]; + struct uart *boarduart = &led15070_per_board_vars[board].boarduart; + + if (irp->op == IRP_OP_OPEN) { + // Unfortunately the LED board UART gets opened and closed + // repeatedly + + if (!v->started) { + dprintf("LED 15070: Starting LED backend\n"); + hr = led_init(); + + v->started = true; + v->start_hr = hr; + + if (FAILED(hr)) { + dprintf("LED 15070: Backend error, LED controller " + "disconnected: %x\n", + (int) hr); + + return hr; + } + } else { + hr = v->start_hr; + + if (FAILED(hr)) { + return hr; + } + } + } + + hr = uart_handle_irp(boarduart, irp); + + if (FAILED(hr) || irp->op != IRP_OP_WRITE) { + return hr; + } + + for (;;) { +#if defined(LOG_LED15070) + dprintf("TX Buffer:\n"); + dump_iobuf(&boarduart->written); +#endif + + req_iobuf.bytes = (uint8_t*)&req; + req_iobuf.nbytes = sizeof(req.hdr) + sizeof(req.cmd) + sizeof(req.payload); + req_iobuf.pos = 0; + + hr = led15070_frame_decode(&req_iobuf, &boarduart->written); + + if (hr != S_OK) { + if (FAILED(hr)) { + dprintf("LED 15070: Deframe error: %x\n", (int) hr); + } + + return hr; + } + +#if defined(LOG_LED15070) + dprintf("Deframe Buffer:\n"); + dump_iobuf(&req_iobuf); +#endif + + hr = led15070_req_dispatch(board, &req); + + if (FAILED(hr)) { + dprintf("LED 15070: Processing error: %x\n", (int) hr); + } + } +} + +static HRESULT led15070_req_dispatch(int board, const struct led15070_req_any *req) +{ + switch (req->cmd) { + case LED_15070_CMD_RESET: + return led15070_req_reset(board, req); + + case LED_15070_CMD_SET_INPUT: + return led15070_req_set_input(board, req); + + case LED_15070_CMD_SET_NORMAL_12BIT: + return led15070_req_set_normal_12bit(board, req); + + case LED_15070_CMD_SET_NORMAL_8BIT: + return led15070_req_set_normal_8bit(board, req); + + case LED_15070_CMD_SET_MULTI_FLASH_8BIT: + return led15070_req_set_multi_flash_8bit(board, req); + + case LED_15070_CMD_SET_MULTI_FADE_8BIT: + return led15070_req_set_multi_fade_8bit(board, req); + + case LED_15070_CMD_SET_PALETTE_7_NORMAL_LED: + return led15070_req_set_palette_7_normal_led(board, req); + + case LED_15070_CMD_SET_PALETTE_6_FLASH_LED: + return led15070_req_set_palette_6_flash_led(board, req); + + case LED_15070_CMD_SET_15DC_OUT: + return led15070_req_set_15dc_out(board, req); + + case LED_15070_CMD_SET_15GS_OUT: + return led15070_req_set_15gs_out(board, req); + + case LED_15070_CMD_SET_PSC_MAX: + return led15070_req_set_psc_max(board, req); + + case LED_15070_CMD_SET_FET_OUTPUT: + return led15070_req_set_fet_output(board, req); + + case LED_15070_CMD_SET_GS_PALETTE: + return led15070_req_set_gs_palette(board, req); + + case LED_15070_CMD_DC_UPDATE: + return led15070_req_dc_update(board, req); + + case LED_15070_CMD_GS_UPDATE: + return led15070_req_gs_update(board, req); + + case LED_15070_CMD_ROTATE: + return led15070_req_rotate(board, req); + + case LED_15070_CMD_SET_DC_DATA: + return led15070_req_set_dc_data(board, req); + + case LED_15070_CMD_EEPROM_WRITE: + return led15070_req_eeprom_write(board, req); + + case LED_15070_CMD_EEPROM_READ: + return led15070_req_eeprom_read(board, req); + + case LED_15070_CMD_ACK_ON: + return led15070_req_ack_on(board, req); + + case LED_15070_CMD_ACK_OFF: + return led15070_req_ack_off(board, req); + + case LED_15070_CMD_BOARD_INFO: + return led15070_req_board_info(board); + + case LED_15070_CMD_BOARD_STATUS: + return led15070_req_board_status(board); + + case LED_15070_CMD_FW_SUM: + return led15070_req_fw_sum(board); + + case LED_15070_CMD_PROTOCOL_VER: + return led15070_req_protocol_ver(board); + + case LED_15070_CMD_TO_BOOT_MODE: + return led15070_req_to_boot_mode(board); + + case LED_15070_CMD_FW_UPDATE: + return led15070_req_fw_update(board, req); + + default: + dprintf("LED 15070: Unhandled command %02x\n", req->cmd); + + return S_OK; + } +} + +static HRESULT led15070_req_reset(int board, const struct led15070_req_any *req) +{ + dprintf("LED 15070: Reset (board %u)\n", board); + + led15070_per_board_vars[board].enable_bootloader = false; + led15070_per_board_vars[board].enable_response = true; + + struct led15070_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15070_FRAME_SYNC; + resp.hdr.dest_adr = led15070_host_adr; + resp.hdr.src_adr = led15070_per_board_vars[board].boardadr; + resp.hdr.nbytes = 3; + + resp.status = LED_15070_STATUS_OK; + resp.cmd = LED_15070_CMD_RESET; + resp.report = LED_15070_REPORT_OK; + + return led15070_frame_encode(&led15070_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15070_req_set_input(int board, const struct led15070_req_any *req) +{ +#if defined(LOG_LED15070) + dprintf("LED 15070: Set input (board %u)\n", board); +#endif + + if (!led15070_per_board_vars[board].enable_response) + return S_OK; + + struct led15070_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15070_FRAME_SYNC; + resp.hdr.dest_adr = led15070_host_adr; + resp.hdr.src_adr = led15070_per_board_vars[board].boardadr; + resp.hdr.nbytes = 3; + + resp.status = LED_15070_STATUS_OK; + resp.cmd = LED_15070_CMD_SET_INPUT; + resp.report = LED_15070_REPORT_OK; + + return led15070_frame_encode(&led15070_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15070_req_set_normal_12bit(int board, const struct led15070_req_any *req) +{ + uint8_t idx = req->payload[0]; +#if defined(LOG_LED15070) + dprintf("LED 15070: Set LED - Normal 12bit (board %u, index %u)\n", + board, idx); +#endif + + // TODO: Data for this command. Seen with Carol + + if (!led15070_per_board_vars[board].enable_response) + return S_OK; + + struct led15070_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15070_FRAME_SYNC; + resp.hdr.dest_adr = led15070_host_adr; + resp.hdr.src_adr = led15070_per_board_vars[board].boardadr; + resp.hdr.nbytes = 3; + + resp.status = LED_15070_STATUS_OK; + resp.cmd = LED_15070_CMD_SET_NORMAL_12BIT; + resp.report = LED_15070_REPORT_OK; + + return led15070_frame_encode(&led15070_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15070_req_set_normal_8bit(int board, const struct led15070_req_any *req) +{ + uint8_t idx = req->payload[0]; +#if defined(LOG_LED15070) + dprintf("LED 15070: Set LED - Normal 8bit (board %u, index %u)\n", + board, idx); +#endif + + led15070_per_board_vars[board].gs[idx][0] = req->payload[1]; // R + led15070_per_board_vars[board].gs[idx][1] = req->payload[2]; // G + led15070_per_board_vars[board].gs[idx][2] = req->payload[3]; // B + + if (!led15070_per_board_vars[board].enable_response) + return S_OK; + + struct led15070_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15070_FRAME_SYNC; + resp.hdr.dest_adr = led15070_host_adr; + resp.hdr.src_adr = led15070_per_board_vars[board].boardadr; + resp.hdr.nbytes = 3; + + resp.status = LED_15070_STATUS_OK; + resp.cmd = LED_15070_CMD_SET_NORMAL_8BIT; + resp.report = LED_15070_REPORT_OK; + + return led15070_frame_encode(&led15070_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15070_req_set_multi_flash_8bit(int board, const struct led15070_req_any *req) +{ + uint8_t idx_start = req->payload[0]; + uint8_t idx_end = req->payload[1]; + uint8_t idx_skip = req->payload[2]; + + // TODO: useful? +#if defined(LOG_LED15070) + dprintf("LED 15070: Set LED - Multi flash 8bit (board %u, start %u, end %u, skip %u)\n", + board, idx_start, idx_end, idx_skip); +#endif + + if (idx_skip > 0 && idx_skip <= (idx_end - idx_start + 1)) { + idx_start += idx_skip; + } + + int i = idx_start; + do { + led15070_per_board_vars[board].gs[i][0] = req->payload[3]; // R + led15070_per_board_vars[board].gs[i][1] = req->payload[4]; // G + led15070_per_board_vars[board].gs[i][2] = req->payload[5]; // B + /* Always 0, tells the controller to immediately change to this color */ + led15070_per_board_vars[board].gs[i][3] = req->payload[6]; // Speed + i++; + } while (i < idx_end); + + if (!led15070_per_board_vars[board].enable_response) + return S_OK; + + struct led15070_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15070_FRAME_SYNC; + resp.hdr.dest_adr = led15070_host_adr; + resp.hdr.src_adr = led15070_per_board_vars[board].boardadr; + resp.hdr.nbytes = 3; + + resp.status = LED_15070_STATUS_OK; + resp.cmd = LED_15070_CMD_SET_MULTI_FLASH_8BIT; + resp.report = LED_15070_REPORT_OK; + + return led15070_frame_encode(&led15070_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15070_req_set_multi_fade_8bit(int board, const struct led15070_req_any *req) +{ + uint8_t idx_start = req->payload[0]; + uint8_t idx_end = req->payload[1]; + uint8_t idx_skip = req->payload[2]; +#if defined(LOG_LED15070) + dprintf("LED 15070: Set LED - Multi fade 8bit (board %u, start %u, end %u, skip %u)\n", + board, idx_start, idx_end, idx_skip); +#endif + + if (idx_skip > 0 && idx_skip <= (idx_end - idx_start + 1)) { + idx_start += idx_skip; + } + + int i = idx_start; + do { + led15070_per_board_vars[board].gs[i][0] = req->payload[3]; // R + led15070_per_board_vars[board].gs[i][1] = req->payload[4]; // G + led15070_per_board_vars[board].gs[i][2] = req->payload[5]; // B + led15070_per_board_vars[board].gs[i][3] = req->payload[6]; // Speed + i++; + } while (i < idx_end); + + if (!led15070_per_board_vars[board].enable_response) + return S_OK; + + struct led15070_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15070_FRAME_SYNC; + resp.hdr.dest_adr = led15070_host_adr; + resp.hdr.src_adr = led15070_per_board_vars[board].boardadr; + resp.hdr.nbytes = 3; + + resp.status = LED_15070_STATUS_OK; + resp.cmd = LED_15070_CMD_SET_MULTI_FADE_8BIT; + resp.report = LED_15070_REPORT_OK; + + return led15070_frame_encode(&led15070_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15070_req_set_palette_7_normal_led(int board, const struct led15070_req_any *req) +{ +#if defined(LOG_LED15070) + dprintf("LED 15070: Set palette - 7 Normal LED (board %u)\n", board); +#endif + + if (!led15070_per_board_vars[board].enable_response) + return S_OK; + + struct led15070_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15070_FRAME_SYNC; + resp.hdr.dest_adr = led15070_host_adr; + resp.hdr.src_adr = led15070_per_board_vars[board].boardadr; + resp.hdr.nbytes = 3; + + resp.status = LED_15070_STATUS_OK; + resp.cmd = LED_15070_CMD_SET_PALETTE_7_NORMAL_LED; + resp.report = LED_15070_REPORT_OK; + + return led15070_frame_encode(&led15070_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15070_req_set_palette_6_flash_led(int board, const struct led15070_req_any *req) +{ +#if defined(LOG_LED15070) + dprintf("LED 15070: Set palette - 6 Flash LED (board %u)\n", board); +#endif + + if (!led15070_per_board_vars[board].enable_response) + return S_OK; + + struct led15070_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15070_FRAME_SYNC; + resp.hdr.dest_adr = led15070_host_adr; + resp.hdr.src_adr = led15070_per_board_vars[board].boardadr; + resp.hdr.nbytes = 3; + + resp.status = LED_15070_STATUS_OK; + resp.cmd = LED_15070_CMD_SET_PALETTE_6_FLASH_LED; + resp.report = LED_15070_REPORT_OK; + + return led15070_frame_encode(&led15070_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15070_req_set_15dc_out(int board, const struct led15070_req_any *req) +{ +#if defined(LOG_LED15070) + dprintf("LED 15070: Set 15DC out (board %u)\n", board); +#endif + + if (!led15070_per_board_vars[board].enable_response) + return S_OK; + + struct led15070_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15070_FRAME_SYNC; + resp.hdr.dest_adr = led15070_host_adr; + resp.hdr.src_adr = led15070_per_board_vars[board].boardadr; + resp.hdr.nbytes = 3; + + resp.status = LED_15070_STATUS_OK; + resp.cmd = LED_15070_CMD_SET_15DC_OUT; + resp.report = LED_15070_REPORT_OK; + + return led15070_frame_encode(&led15070_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15070_req_set_15gs_out(int board, const struct led15070_req_any *req) +{ +#if defined(LOG_LED15070) + dprintf("LED 15070: Set 15GS out (board %u)\n", board); +#endif + + if (!led15070_per_board_vars[board].enable_response) + return S_OK; + + struct led15070_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15070_FRAME_SYNC; + resp.hdr.dest_adr = led15070_host_adr; + resp.hdr.src_adr = led15070_per_board_vars[board].boardadr; + resp.hdr.nbytes = 3; + + resp.status = LED_15070_STATUS_OK; + resp.cmd = LED_15070_CMD_SET_15GS_OUT; + resp.report = LED_15070_REPORT_OK; + + return led15070_frame_encode(&led15070_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15070_req_set_psc_max(int board, const struct led15070_req_any *req) +{ +#if defined(LOG_LED15070) + dprintf("LED 15070: Set PSC max (board %u)\n", board); +#endif + + if (!led15070_per_board_vars[board].enable_response) + return S_OK; + + struct led15070_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15070_FRAME_SYNC; + resp.hdr.dest_adr = led15070_host_adr; + resp.hdr.src_adr = led15070_per_board_vars[board].boardadr; + resp.hdr.nbytes = 3; + + resp.status = LED_15070_STATUS_OK; + resp.cmd = LED_15070_CMD_SET_PSC_MAX; + resp.report = LED_15070_REPORT_OK; + + return led15070_frame_encode(&led15070_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15070_req_set_fet_output(int board, const struct led15070_req_any *req) +{ +#if defined(LOG_LED15070) + dprintf("LED 15070: Set FET output (board %u)\n", board); +#endif + + led15070_per_board_vars[board].fet[0] = req->payload[0]; // R or FET0 intensity + led15070_per_board_vars[board].fet[1] = req->payload[1]; // G or FET1 intensity + led15070_per_board_vars[board].fet[2] = req->payload[2]; // B or FET2 intensity + + if (led_set_fet_output) + led_set_fet_output(board, (const uint8_t*)led15070_per_board_vars[board].fet); + + if (!led15070_per_board_vars[board].enable_response) + return S_OK; + + struct led15070_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15070_FRAME_SYNC; + resp.hdr.dest_adr = led15070_host_adr; + resp.hdr.src_adr = led15070_per_board_vars[board].boardadr; + resp.hdr.nbytes = 3; + + resp.status = LED_15070_STATUS_OK; + resp.cmd = LED_15070_CMD_SET_FET_OUTPUT; + resp.report = LED_15070_REPORT_OK; + + return led15070_frame_encode(&led15070_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15070_req_set_gs_palette(int board, const struct led15070_req_any *req) +{ + uint8_t idx = req->payload[0]; +#if defined(LOG_LED15070) + dprintf("LED 15070: Set GS palette (board %u, index %u)\n", board, idx); +#endif + + led15070_per_board_vars[board].gs_palette[idx][0] = req->payload[1]; // R + led15070_per_board_vars[board].gs_palette[idx][1] = req->payload[2]; // G + led15070_per_board_vars[board].gs_palette[idx][2] = req->payload[3]; // B + + if (!led15070_per_board_vars[board].enable_response) + return S_OK; + + struct led15070_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15070_FRAME_SYNC; + resp.hdr.dest_adr = led15070_host_adr; + resp.hdr.src_adr = led15070_per_board_vars[board].boardadr; + resp.hdr.nbytes = 3; + + resp.status = LED_15070_STATUS_OK; + resp.cmd = LED_15070_CMD_SET_GS_PALETTE; + resp.report = LED_15070_REPORT_OK; + + return led15070_frame_encode(&led15070_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15070_req_dc_update(int board, const struct led15070_req_any *req) +{ +#if defined(LOG_LED15070) + dprintf("LED 15070: DC update (board %u)\n", board); +#endif + + if (led_dc_update) + led_dc_update(board, (const uint8_t*)led15070_per_board_vars[board].dc); + + if (!led15070_per_board_vars[board].enable_response) + return S_OK; + + struct led15070_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15070_FRAME_SYNC; + resp.hdr.dest_adr = led15070_host_adr; + resp.hdr.src_adr = led15070_per_board_vars[board].boardadr; + resp.hdr.nbytes = 3; + + resp.status = LED_15070_STATUS_OK; + resp.cmd = LED_15070_CMD_DC_UPDATE; + resp.report = LED_15070_REPORT_OK; + + return led15070_frame_encode(&led15070_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15070_req_gs_update(int board, const struct led15070_req_any *req) +{ +#if defined(LOG_LED15070) + dprintf("LED 15070: GS update (board %u)\n", board); +#endif + + if (led_gs_update) + led_gs_update(board, (const uint8_t*)led15070_per_board_vars[board].gs); + + if (!led15070_per_board_vars[board].enable_response) + return S_OK; + + struct led15070_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15070_FRAME_SYNC; + resp.hdr.dest_adr = led15070_host_adr; + resp.hdr.src_adr = led15070_per_board_vars[board].boardadr; + resp.hdr.nbytes = 3; + + resp.status = LED_15070_STATUS_OK; + resp.cmd = LED_15070_CMD_GS_UPDATE; + resp.report = LED_15070_REPORT_OK; + + return led15070_frame_encode(&led15070_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15070_req_rotate(int board, const struct led15070_req_any *req) +{ +#if defined(LOG_LED15070) + dprintf("LED 15070: Rotate (board %u)\n", board); +#endif + + if (!led15070_per_board_vars[board].enable_response) + return S_OK; + + struct led15070_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15070_FRAME_SYNC; + resp.hdr.dest_adr = led15070_host_adr; + resp.hdr.src_adr = led15070_per_board_vars[board].boardadr; + resp.hdr.nbytes = 3; + + resp.status = LED_15070_STATUS_OK; + resp.cmd = LED_15070_CMD_ROTATE; + resp.report = LED_15070_REPORT_OK; + + return led15070_frame_encode(&led15070_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15070_req_set_dc_data(int board, const struct led15070_req_any *req) +{ + uint8_t idx_start = req->payload[0]; + uint8_t idx_end = req->payload[1]; + uint8_t idx_skip = req->payload[2]; +#if defined(LOG_LED15070) + dprintf("LED 15070: Set DC data (board %u, start %u, end %u, skip %u)\n", + board, idx_start, idx_end, idx_skip); +#endif + + if (idx_skip > 0 && idx_skip <= (idx_end - idx_start + 1)) { + idx_start += idx_skip; + } + + int i = idx_start; + do { + led15070_per_board_vars[board].dc[i][0] = req->payload[3]; // R + led15070_per_board_vars[board].dc[i][1] = req->payload[4]; // G + led15070_per_board_vars[board].dc[i][2] = req->payload[5]; // B + i++; + } while (i < idx_end); + + if (!led15070_per_board_vars[board].enable_response) + return S_OK; + + struct led15070_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15070_FRAME_SYNC; + resp.hdr.dest_adr = led15070_host_adr; + resp.hdr.src_adr = led15070_per_board_vars[board].boardadr; + resp.hdr.nbytes = 3; + + resp.status = LED_15070_STATUS_OK; + resp.cmd = LED_15070_CMD_SET_DC_DATA; + resp.report = LED_15070_REPORT_OK; + + return led15070_frame_encode(&led15070_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15070_req_eeprom_write(int board, const struct led15070_req_any *req) +{ + DWORD eeprom_bytes_written = 0; + HRESULT hr; + BOOL ok; + + uint8_t addr = req->payload[0]; + uint8_t data = req->payload[1]; +#if defined(LOG_LED15070) + dprintf("LED 15070: EEPROM write (board %u, address %02x, data %02x)\n", + board, addr, data); +#endif + + if (addr > 0x07) { + dprintf("LED 15070: Error -- Invalid EEPROM write address %02x\n", + addr); + + return E_INVALIDARG; + } + + hr = led15070_eeprom_open( + board, + led15070_per_board_vars[board].eeprom_path, + &(led15070_per_board_vars[board].eeprom_handle)); + + if (FAILED(hr)) { + return hr; + } + + hr = SetFilePointer( + led15070_per_board_vars[board].eeprom_handle, + addr, + NULL, + FILE_BEGIN); + + if (hr == INVALID_SET_FILE_POINTER) { + dprintf("LED 10570: Error -- Failed to set pointer to EEPROM file " + "(board %u)\n", board); + led15070_eeprom_close( + board, + led15070_per_board_vars[board].eeprom_path, + &(led15070_per_board_vars[board].eeprom_handle)); + + return hr; + } + + ok = WriteFile( + led15070_per_board_vars[board].eeprom_handle, + &data, + sizeof(data), + &eeprom_bytes_written, NULL); + + if (!ok || eeprom_bytes_written == 0) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("LED 15070: Error -- Failed to write to EEPROM file: %x " + "(board %u)\n", (int) hr, board); + led15070_eeprom_close( + board, + led15070_per_board_vars[board].eeprom_path, + &(led15070_per_board_vars[board].eeprom_handle)); + + return hr; + } + + hr = led15070_eeprom_close( + board, + led15070_per_board_vars[board].eeprom_path, + &(led15070_per_board_vars[board].eeprom_handle)); + + if (FAILED(hr)) { + return hr; + } + + struct led15070_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15070_FRAME_SYNC; + resp.hdr.dest_adr = led15070_host_adr; + resp.hdr.src_adr = led15070_per_board_vars[board].boardadr; + resp.hdr.nbytes = 2 + 3; + + resp.status = LED_15070_STATUS_OK; + resp.cmd = LED_15070_CMD_EEPROM_WRITE; + resp.report = LED_15070_REPORT_OK; + + resp.data[0] = addr; + resp.data[1] = data; + + return led15070_frame_encode(&led15070_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15070_req_eeprom_read(int board, const struct led15070_req_any *req) +{ + DWORD eeprom_bytes_read = 0; + HRESULT hr; + BOOL ok; + + uint8_t addr = req->payload[0]; + uint8_t data = 0; +#if defined(LOG_LED15070) + dprintf("LED 15070: EEPROM read (board %u, address %02x)\n", board, addr); +#endif + + if (addr > 0x07) { + dprintf("LED 15070: Error -- Invalid EEPROM read address %02x\n", + addr); + + return E_INVALIDARG; + } + + hr = led15070_eeprom_open( + board, + led15070_per_board_vars[board].eeprom_path, + &(led15070_per_board_vars[board].eeprom_handle)); + + if (FAILED(hr)) { + return hr; + } + + hr = SetFilePointer( + led15070_per_board_vars[board].eeprom_handle, + addr, + NULL, + FILE_BEGIN); + + if (hr == INVALID_SET_FILE_POINTER) { + dprintf("LED 10570: Error -- Failed to set pointer to EEPROM file " + "(board %u)\n", board); + led15070_eeprom_close( + board, + led15070_per_board_vars[board].eeprom_path, + &(led15070_per_board_vars[board].eeprom_handle)); + + return hr; + } + + ok = ReadFile( + led15070_per_board_vars[board].eeprom_handle, + &data, + sizeof(data), + &eeprom_bytes_read, + NULL); + + if (!ok || eeprom_bytes_read == 0) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("LED 15070: Error -- Failed to read from EEPROM file: %x " + "(board %u)\n", (int) hr, board); + led15070_eeprom_close( + board, + led15070_per_board_vars[board].eeprom_path, + &(led15070_per_board_vars[board].eeprom_handle)); + + return hr; + } + + hr = led15070_eeprom_close( + board, + led15070_per_board_vars[board].eeprom_path, + &(led15070_per_board_vars[board].eeprom_handle)); + + if (FAILED(hr)) { + return hr; + } + + struct led15070_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15070_FRAME_SYNC; + resp.hdr.dest_adr = led15070_host_adr; + resp.hdr.src_adr = led15070_per_board_vars[board].boardadr; + resp.hdr.nbytes = 1 + 3; + + resp.status = LED_15070_STATUS_OK; + resp.cmd = LED_15070_CMD_EEPROM_READ; + resp.report = LED_15070_REPORT_OK; + + resp.data[0] = data; + + return led15070_frame_encode(&led15070_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15070_req_ack_on(int board, const struct led15070_req_any *req) +{ +#if defined(LOG_LED15070) + dprintf("LED 15070: Acknowledge commands ON (board %u)\n", board); +#endif + + led15070_per_board_vars[board].enable_response = true; + + struct led15070_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15070_FRAME_SYNC; + resp.hdr.dest_adr = led15070_host_adr; + resp.hdr.src_adr = led15070_per_board_vars[board].boardadr; + resp.hdr.nbytes = 3; + + resp.status = LED_15070_STATUS_OK; + resp.cmd = LED_15070_CMD_ACK_ON; + resp.report = LED_15070_REPORT_OK; + + return led15070_frame_encode(&led15070_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15070_req_ack_off(int board, const struct led15070_req_any *req) +{ +#if defined(LOG_LED15070) + dprintf("LED 15070: Acknowledge commands OFF (board %u)\n", board); +#endif + + led15070_per_board_vars[board].enable_response = false; + + struct led15070_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15070_FRAME_SYNC; + resp.hdr.dest_adr = led15070_host_adr; + resp.hdr.src_adr = led15070_per_board_vars[board].boardadr; + resp.hdr.nbytes = 3; + + resp.status = LED_15070_STATUS_OK; + resp.cmd = LED_15070_CMD_ACK_OFF; + resp.report = LED_15070_REPORT_OK; + + return led15070_frame_encode(&led15070_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15070_req_board_info(int board) +{ +#if defined(LOG_LED15070) + dprintf("LED 15070: Get board info (board %u)\n", board); +#endif + + struct led15070_resp_board_info resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15070_FRAME_SYNC; + resp.hdr.dest_adr = led15070_host_adr; + resp.hdr.src_adr = led15070_per_board_vars[board].boardadr; + resp.hdr.nbytes = 10 + 3; + + resp.status = LED_15070_STATUS_OK; + resp.cmd = LED_15070_CMD_BOARD_INFO; + resp.report = LED_15070_REPORT_OK; + + memcpy(resp.board_num, led15070_board_num, sizeof(resp.board_num)); + resp.endcode = 0xff; + resp.fw_ver = led15070_fw_ver; + + return led15070_frame_encode(&led15070_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15070_req_board_status(int board) +{ +#if defined(LOG_LED15070) + dprintf("LED 15070: Get board status (board %u)\n", board); +#endif + + struct led15070_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15070_FRAME_SYNC; + resp.hdr.dest_adr = led15070_host_adr; + resp.hdr.src_adr = led15070_per_board_vars[board].boardadr; + resp.hdr.nbytes = 4 + 3; + + resp.status = LED_15070_STATUS_OK; + resp.cmd = LED_15070_CMD_BOARD_STATUS; + resp.report = LED_15070_REPORT_OK; + + resp.data[0] = 0; // Timeout status + resp.data[1] = 0; // Timeout sec + resp.data[2] = 0; // PWM IO + resp.data[3] = 0; // FET timeout + + return led15070_frame_encode(&led15070_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15070_req_fw_sum(int board) +{ +#if defined(LOG_LED15070) + dprintf("LED 15070: Get firmware checksum (board %u)\n", board); +#endif + + struct led15070_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15070_FRAME_SYNC; + resp.hdr.dest_adr = led15070_host_adr; + resp.hdr.src_adr = led15070_per_board_vars[board].boardadr; + resp.hdr.nbytes = 2 + 3; + + resp.status = LED_15070_STATUS_OK; + resp.cmd = LED_15070_CMD_FW_SUM; + resp.report = LED_15070_REPORT_OK; + + resp.data[0] = (led15070_fw_sum >> 8) & 0xff; + resp.data[1] = led15070_fw_sum & 0xff; + + return led15070_frame_encode(&led15070_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15070_req_protocol_ver(int board) +{ +#if defined(LOG_LED15070) + dprintf("LED 15070: Get protocol version (board %u)\n", board); +#endif + + struct led15070_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15070_FRAME_SYNC; + resp.hdr.dest_adr = led15070_host_adr; + resp.hdr.src_adr = led15070_per_board_vars[board].boardadr; + resp.hdr.nbytes = 3 + 3; + + resp.status = LED_15070_STATUS_OK; + resp.cmd = LED_15070_CMD_PROTOCOL_VER; + resp.report = LED_15070_REPORT_OK; + + if (led15070_per_board_vars[board].enable_bootloader) { + resp.data[0] = 0; // BOOT mode + resp.data[1] = 1; // Version major + resp.data[2] = 1; // Version minor + } else { + resp.data[0] = 1; // APPLI mode + resp.data[1] = 1; // Version major + resp.data[2] = 4; // Version minor + } + + return led15070_frame_encode(&led15070_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15070_req_to_boot_mode(int board) +{ + dprintf("LED 15070: To boot mode (board %u)\n", board); + + led15070_per_board_vars[board].enable_bootloader = true; + + struct led15070_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15070_FRAME_SYNC; + resp.hdr.dest_adr = led15070_host_adr; + resp.hdr.src_adr = led15070_per_board_vars[board].boardadr; + resp.hdr.nbytes = 3; + + resp.status = LED_15070_STATUS_OK; + resp.cmd = LED_15070_CMD_TO_BOOT_MODE; + resp.report = LED_15070_REPORT_OK; + + return led15070_frame_encode(&led15070_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15070_req_fw_update(int board, const struct led15070_req_any *req) +{ + dprintf("LED 15070: Firmware update (UNIMPLEMENTED) (board %u)\n", board); + + struct led15070_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15070_FRAME_SYNC; + resp.hdr.dest_adr = led15070_host_adr; + resp.hdr.src_adr = led15070_per_board_vars[board].boardadr; + resp.hdr.nbytes = 3; + + resp.status = LED_15070_STATUS_OK; + resp.cmd = LED_15070_CMD_FW_UPDATE; + resp.report = LED_15070_REPORT_OK; + + return led15070_frame_encode(&led15070_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15070_eeprom_open(int board, wchar_t *path, HANDLE *handle) +{ + DWORD eeprom_bytes_written; + BYTE null_bytes[8] = { 0x00 }; + HRESULT hr; + BOOL ok; + +#if defined(LOG_LED15070) + dprintf("LED 15070: Opening EEPROM file '%S' handle (board %u)\n", path, board); +#endif + + *handle = CreateFileW( + path, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ, + NULL, + OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (*handle == INVALID_HANDLE_VALUE) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("LED 15070: Failed to open EEPROM file '%S' handle: %x " + "(board %u)\n", path, (int) hr, board); + + return hr; + } + + if (GetLastError() == ERROR_ALREADY_EXISTS) return S_OK; + + ok = WriteFile( + *handle, + null_bytes, + sizeof(null_bytes), + &eeprom_bytes_written, + NULL); + + if (!ok || eeprom_bytes_written != sizeof(null_bytes)) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("LED 15070: Failed to initialize empty EEPROM file '%S': %x " + "(board %u)\n", path, (int) hr, board); + + return hr; + } + + return S_OK; +} + +static HRESULT led15070_eeprom_close(int board, wchar_t *path, HANDLE *handle) +{ + HRESULT hr; + BOOL ok; + +#if defined(LOG_LED15070) + dprintf("LED 15070: Closing EEPROM file '%S' handle (board %u)\n", path, board); +#endif + + ok = CloseHandle(*handle); + + if (!ok) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("LED 15070: Failed to close EEPROM file '%S' handle: %x " + "(board %u)\n", path, (int) hr, board); + + return hr; + } + + return S_OK; +} diff --git a/common/board/led15070.h b/common/board/led15070.h new file mode 100644 index 0000000..ae237b4 --- /dev/null +++ b/common/board/led15070.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include +#include + +struct led15070_config { + bool enable; + unsigned int port_no[2]; + char board_number[8]; + uint8_t fw_ver; + uint16_t fw_sum; + wchar_t eeprom_path[MAX_PATH]; +}; + +typedef HRESULT (*io_led_init_t)(void); +typedef void (*io_led_set_fet_output_t)(uint8_t board, const uint8_t *rgb); +typedef void (*io_led_dc_update_t)(uint8_t board, const uint8_t *rgb); +typedef void (*io_led_gs_update_t)(uint8_t board, const uint8_t *rgb); + +HRESULT led15070_hook_init( + const struct led15070_config *cfg, + io_led_init_t _led_init, + io_led_set_fet_output_t _led_set_fet_output, + io_led_dc_update_t _led_dc_update, + io_led_gs_update_t _led_gs_update, + unsigned int port_no[2]); diff --git a/common/board/led15093-cmd.h b/common/board/led15093-cmd.h new file mode 100644 index 0000000..44bddf9 --- /dev/null +++ b/common/board/led15093-cmd.h @@ -0,0 +1,222 @@ +#pragma once + +#include "board/led15093-frame.h" + +/* Command IDs */ + +enum { + LED_15093_CMD_RESET = 0x10, + LED_15093_CMD_SET_TIMEOUT = 0x11, + LED_15093_CMD_UNK1 = 0x12, + LED_15093_CMD_SET_DISABLE_RESPONSE = 0x14, + LED_15093_CMD_SET_ID = 0x18, + LED_15093_CMD_CLEAR_ID = 0x19, + LED_15093_CMD_SET_MAX_BRIGHT = 0x3F, // TODO + LED_15093_CMD_UPDATE_LED = 0x80, + LED_15093_CMD_SET_LED = 0x81, + LED_15093_CMD_SET_IMM_LED = 0x82, + LED_15093_CMD_SET_FADE_LED = 0x83, + LED_15093_CMD_SET_FADE_LEVEL = 0x84, + LED_15093_CMD_SET_FADE_SHIFT = 0x85, + LED_15093_CMD_SET_AUTO_SHIFT = 0x86, + LED_15093_CMD_GET_BOARD_INFO = 0xF0, + LED_15093_CMD_GET_BOARD_STATUS = 0xF1, + LED_15093_CMD_GET_FW_SUM = 0xF2, + LED_15093_CMD_GET_PROTOCOL_VER = 0xF3, + LED_15093_CMD_SET_BOOTMODE = 0xFD, + LED_15093_CMD_FW_UPDATE = 0xFE, +}; + +/* Response codes */ + +enum { + LED_15093_STATUS_OK = 0x01, + LED_15093_STATUS_ERR_SUM = 0x02, + LED_15093_STATUS_ERR_PARITY = 0x03, + LED_15093_STATUS_ERR_FRAMING = 0x04, + LED_15093_STATUS_ERR_OVERRUN = 0x05, + LED_15093_STATUS_ERR_BUFFER_OVERFLOW = 0x06, +}; + +enum { + LED_15093_REPORT_OK = 0x01, + LED_15093_REPORT_WAIT = 0x02, + LED_15093_REPORT_ERR1 = 0x03, + LED_15093_REPORT_ERR2 = 0x04, +}; + +/* Status bitmasks */ + +enum { + LED_15093_STATUS_UART_ERR_SUM = 0x01, + LED_15093_STATUS_UART_ERR_PARITY = 0x02, + LED_15093_STATUS_UART_ERR_FRAMING = 0x04, + LED_15093_STATUS_UART_ERR_OVERRUN = 0x08, + LED_15093_STATUS_UART_ERR_BUFFER_OVERFLOW = 0x10, +}; + +enum { + LED_15093_STATUS_BOARD_ERR_WDT = 0x01, + LED_15093_STATUS_BOARD_ERR_TIMEOUT = 0x02, + LED_15093_STATUS_BOARD_ERR_RESET = 0x04, + LED_15093_STATUS_BOARD_ERR_BOR = 0x08, +}; + +enum { + LED_15093_STATUS_CMD_ERR_BUSY = 0x01, + LED_15093_STATUS_CMD_ERR_UNKNOWN = 0x02, + LED_15093_STATUS_CMD_ERR_PARAM = 0x04, + LED_15093_STATUS_CMD_ERR_EXE = 0x08, +}; + +/* Status types for internal use */ + +enum { + LED_15093_STATUS_TYPE_BOARD = 1, + LED_15093_STATUS_TYPE_UART = 2, + LED_15093_STATUS_TYPE_CMD = 3, +}; + +/* Request data structures */ + +struct led15093_req_reset { + struct led15093_req_hdr hdr; + uint8_t cmd; + uint8_t r_type; +}; + +struct led15093_req_set_timeout { + struct led15093_req_hdr hdr; + uint8_t cmd; + uint16_t count; +}; + +struct led15093_req_set_disable_response { + struct led15093_req_hdr hdr; + uint8_t cmd; + bool sw; +}; + +struct led15093_req_set_id { + struct led15093_req_hdr hdr; + uint8_t cmd; + uint8_t id; +}; + +struct led15093_req_set_led { + struct led15093_req_hdr hdr; + uint8_t cmd; + uint8_t data[198]; +}; + +struct led15093_req_set_fade_level { + struct led15093_req_hdr hdr; + uint8_t cmd; + uint8_t depth; + uint8_t cycle; +}; + +struct led15093_req_set_fade_shift { + struct led15093_req_hdr hdr; + uint8_t cmd; + uint8_t target; +}; + +struct led15093_req_set_auto_shift { + struct led15093_req_hdr hdr; + uint8_t cmd; + uint8_t count; + uint8_t target; +}; + +struct led15093_req_get_board_status { + struct led15093_req_hdr hdr; + uint8_t cmd; + bool clear; +}; + +union led15093_req_any { + struct led15093_req_hdr hdr; + struct led15093_req_reset reset; + struct led15093_req_set_timeout set_timeout; + struct led15093_req_set_disable_response set_disable_response; + struct led15093_req_set_id set_id; + struct led15093_req_set_led set_led; + struct led15093_req_set_fade_level set_fade_level; + struct led15093_req_set_fade_shift set_fade_shift; + struct led15093_req_set_auto_shift set_auto_shift; + struct led15093_req_get_board_status get_board_status; + uint8_t payload[256]; +}; + +/* Response data structures */ + +struct led15093_resp_any { + struct led15093_resp_hdr hdr; + uint8_t status; + uint8_t cmd; + uint8_t report; + uint8_t data[32]; +}; + +struct led15093_resp_timeout { + struct led15093_resp_hdr hdr; + uint8_t status; + uint8_t cmd; + uint8_t report; + uint8_t count_upper; + uint8_t count_lower; +}; + +struct led15093_resp_fw_sum { + struct led15093_resp_hdr hdr; + uint8_t status; + uint8_t cmd; + uint8_t report; + uint8_t sum_upper; + uint8_t sum_lower; +}; + +struct led15093_resp_board_info_legacy { + struct led15093_resp_hdr hdr; + uint8_t status; + uint8_t cmd; + uint8_t report; + char board_num[8]; + uint8_t lf; // 0x0A (ASCII LF) + char chip_num[5]; + uint8_t endcode; // Always 0xFF + uint8_t fw_ver; +}; + +struct led15093_resp_board_info { + struct led15093_resp_hdr hdr; + uint8_t status; + uint8_t cmd; + uint8_t report; + char board_num[8]; + uint8_t lf; // 0x0A (ASCII LF) + char chip_num[5]; + uint8_t endcode; // Always 0xFF + uint8_t fw_ver; + uint16_t rx_buf; +}; + +struct led15093_resp_protocol_ver { + struct led15093_resp_hdr hdr; + uint8_t status; + uint8_t cmd; + uint8_t report; + uint8_t mode; + uint8_t major_ver; + uint8_t minor_ver; +}; + +struct led15093_resp_set_auto_shift { + struct led15093_resp_hdr hdr; + uint8_t status; + uint8_t cmd; + uint8_t report; + uint8_t count; + uint8_t target; +}; diff --git a/common/board/led15093-frame.c b/common/board/led15093-frame.c new file mode 100644 index 0000000..844f4bf --- /dev/null +++ b/common/board/led15093-frame.c @@ -0,0 +1,196 @@ +#include + +#include +#include +#include +#include + +#include "board/led15093-frame.h" + +#include "hook/iobuf.h" + +static void led15093_frame_sync(struct iobuf *src); +static HRESULT led15093_frame_accept(const struct iobuf *dest); +static HRESULT led15093_frame_encode_byte(struct iobuf *dest, uint8_t byte); + +/* Frame structure: + + [0] Sync byte (0xE0) + [1] Destination address + [2] Source Address + [3] Length of data/payload + [4] Data/payload + For requests (host to board): + [0] Command + ... Payload + For responses (board to host): + [0] Status + [1] Command + [2] Report + ... Payload + [n] Checksum: Sum of all prior bytes (excluding sync byte) + + Byte stuffing: + + 0xD0 is an escape byte. Un-escape the subsequent byte by adding 1. */ + +static void led15093_frame_sync(struct iobuf *src) +{ + size_t i; + + for (i = 0 ; i < src->pos && src->bytes[i] != LED_15093_FRAME_SYNC ; i++); + + src->pos -= i; + memmove(&src->bytes[0], &src->bytes[i], i); +} + +static HRESULT led15093_frame_accept(const struct iobuf *dest) +{ + uint8_t checksum; + size_t i; + + if (dest->pos < 3 || dest->pos != dest->bytes[3] + 5) { + return S_FALSE; + } + + checksum = 0; + + for (i = 1 ; i < dest->pos - 1 ; i++) { + checksum += dest->bytes[i]; + } + + // dprintf("LED checksum %02x, expected %02x\n", checksum, dest->bytes[dest->pos - 1]); + + if (checksum != dest->bytes[dest->pos - 1]) { + return HRESULT_FROM_WIN32(ERROR_CRC); + } + + return S_OK; +} + +HRESULT led15093_frame_decode(struct iobuf *dest, struct iobuf *src) +{ + uint8_t byte; + bool escape; + size_t i; + HRESULT hr; + + assert(dest != NULL); + assert(dest->bytes != NULL || dest->nbytes == 0); + assert(dest->pos <= dest->nbytes); + assert(src != NULL); + assert(src->bytes != NULL || src->nbytes == 0); + assert(src->pos <= src->nbytes); + + led15093_frame_sync(src); + + dest->pos = 0; + escape = false; + + for (i = 0, hr = S_FALSE ; i < src->pos && hr == S_FALSE ; i++) { + /* Step the FSM to unstuff another byte */ + + byte = src->bytes[i]; + + if (dest->pos >= dest->nbytes) { + hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } else if (i == 0) { + dest->bytes[dest->pos++] = byte; + } else if (byte == LED_15093_FRAME_SYNC) { + hr = E_FAIL; + } else if (byte == LED_15093_FRAME_ESC) { + if (escape) { + hr = E_FAIL; + } + + escape = true; + } else if (escape) { + dest->bytes[dest->pos++] = byte + 1; + escape = false; + } else { + dest->bytes[dest->pos++] = byte; + } + + /* Try to accept the packet we've built up so far */ + + if (SUCCEEDED(hr)) { + hr = led15093_frame_accept(dest); + } + } + + /* Handle FSM terminal state */ + + if (hr != S_FALSE) { + /* Frame was either accepted or rejected, remove it from src */ + memmove(&src->bytes[0], &src->bytes[i], src->pos - i); + src->pos -= i; + } + + return hr; +} + +HRESULT led15093_frame_encode( + struct iobuf *dest, + const void *ptr, + size_t nbytes) +{ + const uint8_t *src; + uint8_t checksum; + uint8_t byte; + size_t i; + HRESULT hr; + + assert(dest != NULL); + assert(dest->bytes != NULL || dest->nbytes == 0); + assert(dest->pos <= dest->nbytes); + assert(ptr != NULL); + + src = ptr; + + assert(nbytes >= 3 && + src[0] == LED_15093_FRAME_SYNC && + src[3] + 4 == nbytes); + + if (dest->pos >= dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = LED_15093_FRAME_SYNC; + checksum = 0; + // dprintf("%02x ", LED_15093_FRAME_SYNC); + + for (i = 1 ; i < nbytes ; i++) { + byte = src[i]; + checksum += byte; + // dprintf("%02x ", byte); + + hr = led15093_frame_encode_byte(dest, byte); + + if (FAILED(hr)) { + return hr; + } + } + // dprintf("%02x \n", checksum); + + return led15093_frame_encode_byte(dest, checksum); +} + +static HRESULT led15093_frame_encode_byte(struct iobuf *dest, uint8_t byte) +{ + if (byte == LED_15093_FRAME_SYNC || byte == LED_15093_FRAME_ESC) { + if (dest->pos + 2 > dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = LED_15093_FRAME_ESC; + dest->bytes[dest->pos++] = byte - 1; + } else { + if (dest->pos + 1 > dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = byte; + } + + return S_OK; +} diff --git a/common/board/led15093-frame.h b/common/board/led15093-frame.h new file mode 100644 index 0000000..2311eb6 --- /dev/null +++ b/common/board/led15093-frame.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include +#include + +#include "hook/iobuf.h" + +enum { + LED_15093_FRAME_SYNC = 0xE0, + LED_15093_FRAME_ESC = 0xD0, +}; + +struct led15093_req_hdr { + uint8_t sync; + uint8_t dest_adr; + uint8_t src_adr; + uint8_t nbytes; +}; + +struct led15093_resp_hdr { + uint8_t sync; + uint8_t dest_adr; + uint8_t src_adr; + uint8_t nbytes; +}; + +HRESULT led15093_frame_decode(struct iobuf *dest, struct iobuf *src); + +HRESULT led15093_frame_encode( + struct iobuf *dest, + const void *ptr, + size_t nbytes); diff --git a/common/board/led15093.c b/common/board/led15093.c new file mode 100644 index 0000000..31ccb4e --- /dev/null +++ b/common/board/led15093.c @@ -0,0 +1,1129 @@ +/* + SEGA 837-15093-XX LED Controller Board emulator + + Supported variants: + + 837-15093 + 837-15093-01 + 837-15093-04 + 837-15093-05 + 837-15093-06 + + Credits: + 837-15093-XX LED Controller Board emulator (emihiok) + 837-15093-06 LED Controller Board emulator (somewhatlurker, skogaby) +*/ + +#include + +#include +#include +#include +#include +#include +#include + +#include "board/led15093-cmd.h" +#include "board/led15093-frame.h" +#include "board/led15093.h" + +#include "hook/iobuf.h" +#include "hook/iohook.h" + +#include "hooklib/uart.h" + +#include "util/dprintf.h" +#include "util/str.h" +#include "util/dump.h" + +#define led15093_nboards 2 +#define led15093_nnodes 1 + +#define led15093_led_count_max 66 + +typedef struct { + uint8_t boardadr; + uint8_t boardstatus[4]; + uint8_t fade_depth; + uint8_t fade_cycle; + uint8_t led[led15093_led_count_max][3]; + uint8_t led_bright[led15093_led_count_max][3]; + uint8_t led_count; + uint8_t status_code; + uint8_t report_code; + bool enable_bootloader; + bool enable_response; +} _led15093_per_node_vars; + +typedef struct { + CRITICAL_SECTION lock; + bool started; + HRESULT start_hr; + struct uart boarduart; + uint8_t written_bytes[520]; + uint8_t readable_bytes[520]; + _led15093_per_node_vars per_node_vars[led15093_nnodes]; +} _led15093_per_board_vars; + +_led15093_per_board_vars led15093_per_board_vars[led15093_nboards]; + +static HRESULT led15093_handle_irp(struct irp *irp); +static HRESULT led15093_handle_irp_locked(int board, struct irp *irp); + +static HRESULT led15093_req_dispatch(int board, const union led15093_req_any *req); +static HRESULT led15093_req_reset(int board, const struct led15093_req_reset *req); +static HRESULT led15093_req_set_timeout(int board, const struct led15093_req_set_timeout *req); +static HRESULT led15093_req_set_response(int board, const struct led15093_req_set_disable_response *req); +static HRESULT led15093_req_set_id(int board, const struct led15093_req_set_id *req); +static HRESULT led15093_req_clear_id(int board, const union led15093_req_any *req); +static HRESULT led15093_req_set_max_bright(int board, const struct led15093_req_set_led *req); +static HRESULT led15093_req_update_led(int board, const union led15093_req_any *req); +static HRESULT led15093_req_set_led(int board, const struct led15093_req_set_led *req); +static HRESULT led15093_req_set_imm_led(int board, const struct led15093_req_set_led *req); +static HRESULT led15093_req_set_fade_led(int board, const struct led15093_req_set_led *req); +static HRESULT led15093_req_set_fade_level(int board, const struct led15093_req_set_fade_level *req); +static HRESULT led15093_req_set_fade_shift(int board, const struct led15093_req_set_fade_shift *req); +static HRESULT led15093_req_set_auto_shift(int board, const struct led15093_req_set_auto_shift *req); +static HRESULT led15093_req_get_board_info(int board, const union led15093_req_any *req); +static HRESULT led15093_req_get_board_status(int board, const struct led15093_req_get_board_status *req); +static HRESULT led15093_req_get_fw_sum(int board, const union led15093_req_any *req); +static HRESULT led15093_req_get_protocol_ver(int board, const union led15093_req_any *req); +static HRESULT led15093_req_set_bootmode(int board, const union led15093_req_any *req); +static HRESULT led15093_req_fw_update(int board, const union led15093_req_any *req); + +static void led15093_set_status(int board, int id, uint8_t type, uint8_t status); +static void led15093_set_report(int board, int id, uint8_t report); +static void led15093_clear_status(int board, int id); +static void led15093_clear_report(int board, int id); + +static char led15093_board_num[8]; +static char led15093_chip_num[5]; +static char led15093_boot_chip_num[5]; +static uint8_t led15093_fw_ver; +static uint16_t led15093_fw_sum; +static uint8_t led15093_board_adr = 1; +static uint8_t led15093_host_adr = 1; + +static io_led_init_t led_init; +static io_led_set_leds_t set_leds; + +HRESULT led15093_hook_init( + const struct led15093_config *cfg, + io_led_init_t _led_init, + io_led_set_leds_t _set_leds, + unsigned int port_no[2]) +{ + unsigned int num_boards = 0; + + assert(cfg != NULL); + assert(_led_init != NULL); + assert(_set_leds != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + for (int i = 0; i < led15093_nboards; i++) + { + if (cfg->port_no[i] != 0) { + port_no[i] = cfg->port_no[i]; + } + + if (port_no[i] != 0) { + num_boards++; + } + } + + assert(num_boards != 0); + + led15093_board_adr = num_boards; + led15093_host_adr = num_boards == 2 ? 1 : 2; + + led_init = _led_init; + set_leds = _set_leds; + + memcpy(led15093_board_num, cfg->board_number, sizeof(led15093_board_num)); + memcpy(led15093_chip_num, cfg->chip_number, sizeof(led15093_chip_num)); + memcpy(led15093_boot_chip_num, cfg->boot_chip_number, sizeof(led15093_boot_chip_num)); + led15093_fw_ver = cfg->fw_ver; + led15093_fw_sum = cfg->fw_sum; + + for (int i = 0; i < num_boards; i++) + { + _led15093_per_board_vars *vb = &led15093_per_board_vars[i]; + + InitializeCriticalSection(&vb->lock); + + uart_init(&vb->boarduart, port_no[i]); + if (cfg->high_baudrate) { + vb->boarduart.baud.BaudRate = 460800; + } else { + vb->boarduart.baud.BaudRate = 115200; + } + vb->boarduart.written.bytes = vb->written_bytes; + vb->boarduart.written.nbytes = sizeof(vb->written_bytes); + vb->boarduart.readable.bytes = vb->readable_bytes; + vb->boarduart.readable.nbytes = sizeof(vb->readable_bytes); + + for (int j = 0; j < led15093_nnodes; j++) + { + _led15093_per_node_vars *vn = &vb->per_node_vars[j]; + + memset(vn->led, 0, sizeof(vn->led)); + memset(vn->led_bright, 0x3F, sizeof(vn->led_bright)); + vn->led_count = led15093_led_count_max; + vn->fade_depth = 32; + vn->fade_cycle = 8; + led15093_clear_status(i, 1 + j); + led15093_clear_report(i, 1 + j); + vn->boardstatus[3] = 1; // DIPSW1 ON + vn->boardadr = led15093_board_adr + j; + vn->enable_bootloader = false; + vn->enable_response = true; + } + } + + dprintf("LED 15093: hook enabled.\n"); + + return iohook_push_handler(led15093_handle_irp); +} + +static HRESULT led15093_handle_irp(struct irp *irp) +{ + HRESULT hr; + + assert(irp != NULL); + + for (int i = 0; i < led15093_nboards; i++) + { + _led15093_per_board_vars *v = &led15093_per_board_vars[i]; + struct uart *boarduart = &v->boarduart; + + if (uart_match_irp(boarduart, irp)) + { + CRITICAL_SECTION lock = v->lock; + + EnterCriticalSection(&lock); + hr = led15093_handle_irp_locked(i, irp); + LeaveCriticalSection(&lock); + + return hr; + } + } + + return iohook_invoke_next(irp); +} + +static HRESULT led15093_handle_irp_locked(int board, struct irp *irp) +{ + union led15093_req_any req; + struct iobuf req_iobuf; + HRESULT hr; + + _led15093_per_board_vars *v = &led15093_per_board_vars[board]; + struct uart *boarduart = &led15093_per_board_vars[board].boarduart; + + if (irp->op == IRP_OP_OPEN) { + // Unfortunately the LED board UART gets opened and closed repeatedly + + if (!v->started) { + dprintf("LED 15093: Starting LED backend\n"); + hr = led_init(); + + // hr = S_OK; + v->started = true; + v->start_hr = hr; + + if (FAILED(hr)) { + dprintf("LED 15093: Backend error, LED board disconnected: " + "%x\n", + (int) hr); + + return hr; + } + } else { + hr = v->start_hr; + + if (FAILED(hr)) { + return hr; + } + } + } + + hr = uart_handle_irp(boarduart, irp); + + if (FAILED(hr) || irp->op != IRP_OP_WRITE) { + return hr; + } + + for (;;) { +#if defined(LOG_LED15093) + dprintf("TX Buffer:\n"); + dump_iobuf(&boarduart->written); +#endif + + req_iobuf.bytes = (uint8_t*)&req; + req_iobuf.nbytes = sizeof(req.hdr) + sizeof(req.payload); + req_iobuf.pos = 0; + + hr = led15093_frame_decode(&req_iobuf, &boarduart->written); + + if (hr != S_OK) { + if (hr == HRESULT_FROM_WIN32(ERROR_CRC)) { + led15093_set_status( + board, + 2, + LED_15093_STATUS_TYPE_UART, + LED_15093_STATUS_UART_ERR_SUM); + } + + if (FAILED(hr)) { + dprintf("LED 15093: Deframe error: %x\n", (int) hr); + } + + return hr; + } + +#if defined(LOG_LED15093) + dprintf("Deframe Buffer:\n"); + dump_iobuf(&req_iobuf); +#endif + + hr = led15093_req_dispatch(board, &req); + + if (FAILED(hr)) { + dprintf("LED 15093: Processing error: %x\n", (int) hr); + } + + /* TODO: We should wait for a get board status request with a clear flag, + instead of doing this here. */ + led15093_clear_status(board, req.hdr.dest_adr); + led15093_clear_report(board, req.hdr.dest_adr); + } +} + +static HRESULT led15093_req_dispatch(int board, const union led15093_req_any *req) +{ + switch (req->payload[4]) { + case LED_15093_CMD_RESET: + return led15093_req_reset(board, &req->reset); + + case LED_15093_CMD_SET_TIMEOUT: + return led15093_req_set_timeout(board, &req->set_timeout); + + case LED_15093_CMD_SET_DISABLE_RESPONSE: + return led15093_req_set_response(board, &req->set_disable_response); + + case LED_15093_CMD_SET_ID: + return led15093_req_set_id(board, &req->set_id); + + case LED_15093_CMD_CLEAR_ID: + return led15093_req_clear_id(board, req); + + case LED_15093_CMD_SET_MAX_BRIGHT: + return led15093_req_set_max_bright(board, &req->set_led); + + case LED_15093_CMD_UPDATE_LED: + return led15093_req_update_led(board, req); + + case LED_15093_CMD_SET_LED: + return led15093_req_set_led(board, &req->set_led); + + case LED_15093_CMD_SET_IMM_LED: + // case LED_15093_CMD_SET_IMM_LED_LEGACY: + return led15093_req_set_imm_led(board, &req->set_led); + + case LED_15093_CMD_SET_FADE_LED: + return led15093_req_set_fade_led(board, &req->set_led); + + case LED_15093_CMD_SET_FADE_LEVEL: + return led15093_req_set_fade_level(board, &req->set_fade_level); + + case LED_15093_CMD_SET_FADE_SHIFT: + return led15093_req_set_fade_shift(board, &req->set_fade_shift); + + case LED_15093_CMD_SET_AUTO_SHIFT: + return led15093_req_set_auto_shift(board, &req->set_auto_shift); + + case LED_15093_CMD_GET_BOARD_INFO: + return led15093_req_get_board_info(board, req); + + case LED_15093_CMD_GET_BOARD_STATUS: + return led15093_req_get_board_status(board, &req->get_board_status); + + case LED_15093_CMD_GET_FW_SUM: + return led15093_req_get_fw_sum(board, req); + + case LED_15093_CMD_GET_PROTOCOL_VER: + return led15093_req_get_protocol_ver(board, req); + + case LED_15093_CMD_SET_BOOTMODE: + return led15093_req_set_bootmode(board, req); + + case LED_15093_CMD_FW_UPDATE: + return led15093_req_fw_update(board, req); + + default: + dprintf("LED 15093: Unhandled command %02x\n", req->payload[4]); + led15093_set_report( + board, + req->hdr.dest_adr, + LED_15093_REPORT_ERR2); + + return S_OK; + } +} + +static HRESULT led15093_req_reset(int board, const struct led15093_req_reset *req) +{ + uint8_t id_idx = (req->hdr.dest_adr < 2) ? 0 : req->hdr.dest_adr - 2; + _led15093_per_node_vars *v = &led15093_per_board_vars[board].per_node_vars[id_idx]; + + dprintf("LED 15093: Reset (board %d, node %d, type %02x)\n", + board, req->hdr.dest_adr, req->r_type); + + if (req->r_type != 0xd9) + dprintf("LED 15093: Warning -- Unknown reset type %02x\n", req->r_type); + + /* A dest_adr of 0 means that this message is addressed to all nodes */ + if (req->hdr.dest_adr == 0) { + for (int i = 0; i < led15093_nnodes; i++) { + led15093_per_board_vars[board].per_node_vars[i].enable_bootloader = false; + led15093_per_board_vars[board].per_node_vars[i].enable_response = true; + } + } else { + v->enable_bootloader = false; + v->enable_response = true; + } + + struct led15093_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15093_FRAME_SYNC; + resp.hdr.dest_adr = led15093_host_adr; + resp.hdr.src_adr = v->boardadr; + resp.hdr.nbytes = 3; + + resp.status = v->status_code; + resp.cmd = LED_15093_CMD_RESET; + resp.report = v->report_code; + + return led15093_frame_encode(&led15093_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15093_req_set_timeout(int board, const struct led15093_req_set_timeout *req) +{ + uint8_t id_idx = (req->hdr.dest_adr < 2) ? 0 : req->hdr.dest_adr - 2; + _led15093_per_node_vars *v = &led15093_per_board_vars[board].per_node_vars[id_idx]; + + dprintf("LED 15093: Set timeout (board %u, count %u)\n", board, req->count); + + struct led15093_resp_timeout resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15093_FRAME_SYNC; + resp.hdr.dest_adr = led15093_host_adr; + resp.hdr.src_adr = v->boardadr; + resp.hdr.nbytes = 2 + 3; + + resp.status = v->status_code; + resp.cmd = LED_15093_CMD_SET_TIMEOUT; + resp.report = v->report_code; + + resp.count_upper = (req->count >> 8) & 0xff; + resp.count_lower = req->count & 0xff; + + return led15093_frame_encode(&led15093_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15093_req_set_response(int board, const struct led15093_req_set_disable_response *req) +{ + uint8_t id_idx = (req->hdr.dest_adr < 2) ? 0 : req->hdr.dest_adr - 2; + _led15093_per_node_vars *v = &led15093_per_board_vars[board].per_node_vars[id_idx]; + + if (req->hdr.nbytes > 1) { + dprintf("LED 15093: Set LED response setting (board %d, node %d, SW %d)\n", + board, req->hdr.dest_adr, req->sw); + v->enable_response = !req->sw; + } else { + dprintf("LED 15093: Check LED response setting (board %d, node %d)\n", + board, req->hdr.dest_adr); + } + + struct led15093_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15093_FRAME_SYNC; + resp.hdr.dest_adr = led15093_host_adr; + resp.hdr.src_adr = v->boardadr; + resp.hdr.nbytes = 1 + 3; + + resp.status = v->status_code; + resp.cmd = LED_15093_CMD_SET_DISABLE_RESPONSE; + resp.report = v->report_code; + + resp.data[0] = !v->enable_response; + + + return led15093_frame_encode(&led15093_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15093_req_set_id(int board, const struct led15093_req_set_id *req) +{ + uint8_t id_idx = (req->hdr.dest_adr < 2) ? 0 : req->hdr.dest_adr - 2; + _led15093_per_node_vars *v = &led15093_per_board_vars[board].per_node_vars[id_idx]; + + dprintf("LED 15093: Set ID (board %d, requested node ID %d)\n", + board, req->id); + + if (req->id == 0 || req->id > 8) { + dprintf("LED 15093: Error -- Invalid node ID requested: %d\n", + req->id); + led15093_set_status( + board, + v->boardadr, + LED_15093_STATUS_TYPE_CMD, + LED_15093_STATUS_CMD_ERR_PARAM); + + return HRESULT_FROM_WIN32(ERROR_BAD_UNIT); + } + + /* A dest_adr of 0 means that this message is addressed to any unassigned + node..? */ + if (req->hdr.dest_adr == 0) { + bool slot_found = false; + + for (int i = 0; i < led15093_nnodes && slot_found == false; i++) { + if (led15093_per_board_vars[board].per_node_vars[i].boardadr == 0) { + led15093_per_board_vars[board].per_node_vars[i].boardadr = req->id; + slot_found = true; + } + } + + if (!slot_found) { + dprintf("LED 15093: Warning -- Unable to assign requested node " + "%d, all slots full\n", req->hdr.dest_adr); + } + } else { + /* Overwrite current ID. Probably no use case for this but we do it + anyway */ + v->boardadr = req->id; + } + struct led15093_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15093_FRAME_SYNC; + resp.hdr.dest_adr = led15093_host_adr; + resp.hdr.src_adr = v->boardadr; + resp.hdr.nbytes = 3; + + resp.status = v->status_code; + resp.cmd = LED_15093_CMD_SET_ID; + resp.report = v->report_code; + + return led15093_frame_encode(&led15093_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15093_req_clear_id(int board, const union led15093_req_any *req) +{ + uint8_t id_idx = (req->hdr.dest_adr < 2) ? 0 : req->hdr.dest_adr - 2; + _led15093_per_node_vars *v = &led15093_per_board_vars[board].per_node_vars[id_idx]; + + dprintf("LED 15093: Clear ID (board %d, node %d)\n", + board, req->hdr.dest_adr); + + /* A dest_adr of 0 means that this message is addressed to all nodes */ + if (req->hdr.dest_adr == 0) { + for (int i = 0; i < led15093_nnodes; i++) { + led15093_per_board_vars[board].per_node_vars[i].boardadr = 0; + } + } else { + /* Overwrite current ID. Probably no use case for this but we do it + anyway */ + v->boardadr = 0; + } + + struct led15093_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15093_FRAME_SYNC; + resp.hdr.dest_adr = led15093_host_adr; + resp.hdr.src_adr = v->boardadr; + resp.hdr.nbytes = 3; + + resp.status = v->status_code; + resp.cmd = LED_15093_CMD_CLEAR_ID; + resp.report = v->report_code; + + return led15093_frame_encode(&led15093_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15093_req_set_max_bright(int board, const struct led15093_req_set_led *req) +{ + uint8_t id_idx = (req->hdr.dest_adr < 2) ? 0 : req->hdr.dest_adr - 2; + _led15093_per_node_vars *v = &led15093_per_board_vars[board].per_node_vars[id_idx]; + + dprintf("LED 15093: Set maximum LED brightness (board %d, node %d)\n", + board, req->hdr.dest_adr); + + if ((req->hdr.nbytes - 1) > (v->led_count * 3)) { + dprintf("LED 15093: Error -- Invalid LED count\n"); + led15093_set_report(board, req->hdr.dest_adr, LED_15093_REPORT_ERR1); + + return E_INVALIDARG; + } + + /* 15093 equivalent of 15070's "set DC data". 63 (0x3F) is, again, the + default. */ + + memcpy(v->led_bright, req->data, req->hdr.nbytes - 1); + + // fgo_dll.led_gr_set_max_bright((const uint8_t*)&v->led_bright); + + if (!v->enable_response) + return S_OK; + + struct led15093_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15093_FRAME_SYNC; + resp.hdr.dest_adr = led15093_host_adr; + resp.hdr.src_adr = v->boardadr; + resp.hdr.nbytes = 3; + + resp.status = v->status_code; + resp.cmd = LED_15093_CMD_SET_MAX_BRIGHT; + resp.report = v->report_code; + + return led15093_frame_encode(&led15093_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + + +static HRESULT led15093_req_update_led(int board, const union led15093_req_any *req) +{ + uint8_t id_idx = (req->hdr.dest_adr < 2) ? 0 : req->hdr.dest_adr - 2; + _led15093_per_node_vars *v = &led15093_per_board_vars[board].per_node_vars[id_idx]; + + // dprintf("LED 15093: Update LED (board %d, node %d)\n", + // board, req->hdr.dest_adr); + + // fgo_dll.led_gr_set_imm((const uint8_t*)&v->led); + + if (!v->enable_response) + return S_OK; + + struct led15093_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15093_FRAME_SYNC; + resp.hdr.dest_adr = led15093_host_adr; + resp.hdr.src_adr = v->boardadr; + resp.hdr.nbytes = 3; + + resp.status = v->status_code; + resp.cmd = LED_15093_CMD_UPDATE_LED; + resp.report = v->report_code; + + return led15093_frame_encode(&led15093_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15093_req_set_led(int board, const struct led15093_req_set_led *req) +{ + uint8_t id_idx = (req->hdr.dest_adr < 2) ? 0 : req->hdr.dest_adr - 2; + _led15093_per_node_vars *v = &led15093_per_board_vars[board].per_node_vars[id_idx]; + + // dprintf("LED 15093: Set LED (board %d, node %d)\n", + // board, req->hdr.dest_adr); + + if ((req->hdr.nbytes - 1) > (v->led_count * 3)) { + dprintf("LED 15093: Error -- Invalid LED count\n"); + led15093_set_report(board, req->hdr.dest_adr, LED_15093_REPORT_ERR1); + + return E_INVALIDARG; + } + + memcpy(v->led, req->data, req->hdr.nbytes - 1); + + if (!v->enable_response) + return S_OK; + + struct led15093_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15093_FRAME_SYNC; + resp.hdr.dest_adr = led15093_host_adr; + resp.hdr.src_adr = v->boardadr; + resp.hdr.nbytes = 3; + + resp.status = v->status_code; + resp.cmd = LED_15093_CMD_SET_LED; + resp.report = v->report_code; + + return led15093_frame_encode(&led15093_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15093_req_set_imm_led(int board, const struct led15093_req_set_led *req) +{ + uint8_t id_idx = (req->hdr.dest_adr < 2) ? 0 : req->hdr.dest_adr - 2; + _led15093_per_node_vars *v = &led15093_per_board_vars[board].per_node_vars[id_idx]; + + // dprintf("LED 15093: Set and immediately draw LED (board %d, node %d)\n", + // board, req->hdr.dest_adr); + + if ((req->hdr.nbytes - 1) > (v->led_count * 3)) { + dprintf("LED 15093: Error -- Invalid LED count\n"); + led15093_set_report(board, req->hdr.dest_adr, LED_15093_REPORT_ERR1); + + return E_INVALIDARG; + } + + // Return the current LED data, remove const qualifier + set_leds(board, (uint8_t *) req->data); + + memcpy(v->led, req->data, req->hdr.nbytes - 1); + // fgo_dll.led_gr_set_imm((const uint8_t*)&v->led); + + if (!v->enable_response) + return S_OK; + + struct led15093_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15093_FRAME_SYNC; + resp.hdr.dest_adr = led15093_host_adr; + resp.hdr.src_adr = v->boardadr; + resp.hdr.nbytes = 3; + + resp.status = v->status_code; + if (req->cmd == LED_15093_CMD_SET_IMM_LED) { + resp.cmd = LED_15093_CMD_SET_IMM_LED; + } + // else { + // resp.cmd = LED_15093_CMD_SET_IMM_LED_LEGACY; + // } + resp.report = v->report_code; + + return led15093_frame_encode(&led15093_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15093_req_set_fade_led(int board, const struct led15093_req_set_led *req) +{ + uint8_t id_idx = (req->hdr.dest_adr < 2) ? 0 : req->hdr.dest_adr - 2; + _led15093_per_node_vars *v = &led15093_per_board_vars[board].per_node_vars[id_idx]; + + // dprintf("LED 15093: Set and fade LED (board %d, node %d)\n", + // board, req->hdr.dest_adr); + + if ((req->hdr.nbytes - 1) > (v->led_count * 3)) { + dprintf("LED 15093: Error -- Invalid LED count\n"); + led15093_set_report(board, req->hdr.dest_adr, LED_15093_REPORT_ERR1); + + return E_INVALIDARG; + } + + memcpy(v->led, req->data, req->hdr.nbytes - 1); + + // fgo_dll.led_gr_set_fade( + // (const uint8_t*)v->led, + // v->fade_depth, + // v->fade_cycle); + + if (!v->enable_response) + return S_OK; + + struct led15093_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15093_FRAME_SYNC; + resp.hdr.dest_adr = led15093_host_adr; + resp.hdr.src_adr = v->boardadr; + resp.hdr.nbytes = 3; + + resp.status = v->status_code; + resp.cmd = LED_15093_CMD_SET_FADE_LED; + resp.report = v->report_code; + + return led15093_frame_encode(&led15093_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15093_req_set_fade_level(int board, const struct led15093_req_set_fade_level *req) +{ + uint8_t id_idx = (req->hdr.dest_adr < 2) ? 0 : req->hdr.dest_adr - 2; + _led15093_per_node_vars *v = &led15093_per_board_vars[board].per_node_vars[id_idx]; + + dprintf("LED 15093: Set fade level (board %d, node %d, depth %d, cycle %d)\n", + board, req->hdr.dest_adr, req->depth, req->cycle); + + v->fade_depth = req->depth; + v->fade_cycle = req->cycle; + + if (!v->enable_response) + return S_OK; + + struct led15093_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15093_FRAME_SYNC; + resp.hdr.dest_adr = led15093_host_adr; + resp.hdr.src_adr = v->boardadr; + resp.hdr.nbytes = 3; + + resp.status = v->status_code; + resp.cmd = LED_15093_CMD_SET_FADE_LEVEL; + resp.report = v->report_code; + + return led15093_frame_encode(&led15093_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15093_req_set_fade_shift(int board, const struct led15093_req_set_fade_shift *req) +{ + uint8_t id_idx = (req->hdr.dest_adr < 2) ? 0 : req->hdr.dest_adr - 2; + _led15093_per_node_vars *v = &led15093_per_board_vars[board].per_node_vars[id_idx]; + + // dprintf("LED 15093: Set fade shift (board %d, node %d)\n", + // board, req->hdr.dest_adr); + + if (!v->enable_response) + return S_OK; + + struct led15093_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15093_FRAME_SYNC; + resp.hdr.dest_adr = led15093_host_adr; + resp.hdr.src_adr = v->boardadr; + resp.hdr.nbytes = 3; + + resp.status = v->status_code; + resp.cmd = LED_15093_CMD_SET_FADE_SHIFT; + resp.report = v->report_code; + + return led15093_frame_encode(&led15093_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15093_req_set_auto_shift(int board, const struct led15093_req_set_auto_shift *req) +{ + uint8_t id_idx = (req->hdr.dest_adr < 2) ? 0 : req->hdr.dest_adr - 2; + _led15093_per_node_vars *v = &led15093_per_board_vars[board].per_node_vars[id_idx]; + + dprintf("LED 15093: Set auto shift (board %d, node %d)\n", + board, req->hdr.dest_adr); + + /* There's actually a lot of conflicting info about this command... It + seems they changed the arguments between the -04 and -05 board variants, + and also changed the command ID from 0x87 to 0x86. + + Fortunately, only the -05 variant actually uses this in any shipping + game, so we don't need to implement support for the legacy command + version. */ + + v->led_count = req->count; + + if (!v->enable_response) + return S_OK; + + struct led15093_resp_set_auto_shift resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15093_FRAME_SYNC; + resp.hdr.dest_adr = led15093_host_adr; + resp.hdr.src_adr = v->boardadr; + resp.hdr.nbytes = 1 + 3; + + resp.status = v->status_code; + resp.cmd = LED_15093_CMD_SET_AUTO_SHIFT; + resp.report = v->report_code; + + resp.count = v->led_count; + + return led15093_frame_encode(&led15093_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15093_req_get_board_info(int board, const union led15093_req_any *req) +{ + uint8_t id_idx = (req->hdr.dest_adr < 2) ? 0 : req->hdr.dest_adr - 2; + _led15093_per_node_vars *v = &led15093_per_board_vars[board].per_node_vars[id_idx]; + + dprintf("LED 15093: Get board info (board %d, node %d)\n", + board, req->hdr.dest_adr); + + if (str_eq(led15093_board_num, "15093")) { + struct led15093_resp_board_info_legacy resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15093_FRAME_SYNC; + resp.hdr.dest_adr = led15093_host_adr; + resp.hdr.src_adr = v->boardadr; + resp.hdr.nbytes = 7 + 3; + + resp.status = v->status_code; + resp.cmd = LED_15093_CMD_GET_BOARD_INFO; + resp.report = v->report_code; + + memcpy(resp.board_num, led15093_board_num, sizeof(resp.board_num)); + resp.endcode = 0xff; + resp.fw_ver = led15093_fw_ver; + + return led15093_frame_encode(&led15093_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); + } else { + struct led15093_resp_board_info resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15093_FRAME_SYNC; + resp.hdr.dest_adr = led15093_host_adr; + resp.hdr.src_adr = v->boardadr; + resp.hdr.nbytes = 18 + 3; + + resp.status = v->status_code; + resp.cmd = LED_15093_CMD_GET_BOARD_INFO; + resp.report = v->report_code; + + memcpy(resp.board_num, led15093_board_num, sizeof(resp.board_num)); + resp.lf = 0x0a; + if (v->enable_bootloader) { + memcpy(resp.chip_num, led15093_boot_chip_num, sizeof(resp.chip_num)); + } else { + memcpy(resp.chip_num, led15093_chip_num, sizeof(resp.chip_num)); + } + resp.endcode = 0xff; + resp.fw_ver = led15093_fw_ver; + resp.rx_buf = 204; // Must be 204 regardless of active LED count + + return led15093_frame_encode(&led15093_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); + } +} + +static HRESULT led15093_req_get_board_status(int board, const struct led15093_req_get_board_status *req) +{ + uint8_t id_idx = (req->hdr.dest_adr < 2) ? 0 : req->hdr.dest_adr - 2; + _led15093_per_node_vars *v = &led15093_per_board_vars[board].per_node_vars[id_idx]; + + dprintf("LED 15093: Get board status (board %d, node %d, clear %d)\n", + board, req->hdr.dest_adr, req->clear); + + if (req->clear) { + led15093_clear_status(board, req->hdr.dest_adr); + } + + struct led15093_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15093_FRAME_SYNC; + resp.hdr.dest_adr = led15093_host_adr; + resp.hdr.src_adr = v->boardadr; + resp.hdr.nbytes = (v->enable_bootloader) ? (3 + 3) : (4 + 3); + + resp.status = v->status_code; + resp.cmd = LED_15093_CMD_GET_BOARD_STATUS; + resp.report = v->report_code; + + if (v->enable_bootloader) { + resp.data[0] = v->boardstatus[0]; // Board flags + resp.data[1] = v->boardstatus[1]; // UART flags + resp.data[2] = v->boardstatus[2]; // Command flags + } else { + resp.data[0] = v->boardstatus[0]; // Board flags + resp.data[1] = v->boardstatus[1]; // UART flags + resp.data[2] = v->boardstatus[2]; // Command flags + resp.data[3] = v->boardstatus[3]; // DIPSW + } + + return led15093_frame_encode(&led15093_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15093_req_get_fw_sum(int board, const union led15093_req_any *req) +{ + uint8_t id_idx = (req->hdr.dest_adr < 2) ? 0 : req->hdr.dest_adr - 2; + _led15093_per_node_vars *v = &led15093_per_board_vars[board].per_node_vars[id_idx]; + + dprintf("LED 15093: Get firmware checksum (board %d, node %d)\n", + board, req->hdr.dest_adr); + + struct led15093_resp_fw_sum resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15093_FRAME_SYNC; + resp.hdr.dest_adr = led15093_host_adr; + resp.hdr.src_adr = v->boardadr; + resp.hdr.nbytes = 2 + 3; + + resp.status = v->status_code; + resp.cmd = LED_15093_CMD_GET_FW_SUM; + resp.report = v->report_code; + + resp.sum_upper = (led15093_fw_sum >> 8) & 0xff; + resp.sum_lower = led15093_fw_sum & 0xff; + + return led15093_frame_encode(&led15093_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15093_req_get_protocol_ver(int board, const union led15093_req_any *req) +{ + uint8_t id_idx = (req->hdr.dest_adr < 2) ? 0 : req->hdr.dest_adr - 2; + _led15093_per_node_vars *v = &led15093_per_board_vars[board].per_node_vars[id_idx]; + + dprintf("LED 15093: Get protocol version (board %d, node %d)\n", + board, req->hdr.dest_adr); + + struct led15093_resp_protocol_ver resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15093_FRAME_SYNC; + resp.hdr.dest_adr = led15093_host_adr; + resp.hdr.src_adr = v->boardadr; + resp.hdr.nbytes = 3 + 3; + + resp.status = v->status_code; + resp.cmd = LED_15093_CMD_GET_PROTOCOL_VER; + resp.report = v->report_code; + + if (v->enable_bootloader) { + resp.mode = 0; // BOOT mode + resp.major_ver = 1; + resp.minor_ver = 1; + } else { + resp.mode = 1; // APPLI mode + resp.major_ver = 1; + resp.minor_ver = 4; + } + + return led15093_frame_encode(&led15093_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15093_req_set_bootmode(int board, const union led15093_req_any *req) +{ + uint8_t id_idx = (req->hdr.dest_adr < 2) ? 0 : req->hdr.dest_adr - 2; + _led15093_per_node_vars *v = &led15093_per_board_vars[board].per_node_vars[id_idx]; + + dprintf("LED 15093: Set bootmode (board %d, node %d)\n", + board, req->hdr.dest_adr); + + v->enable_bootloader = true; + + struct led15093_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15093_FRAME_SYNC; + resp.hdr.dest_adr = led15093_host_adr; + resp.hdr.src_adr = v->boardadr; + resp.hdr.nbytes = 1 + 3; + + resp.status = v->status_code; + resp.cmd = LED_15093_CMD_SET_BOOTMODE; + resp.report = v->report_code; + + resp.data[0] = 1; // IDK but this seems to fix test mode????? + + return led15093_frame_encode(&led15093_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static HRESULT led15093_req_fw_update(int board, const union led15093_req_any *req) +{ + uint8_t id_idx = (req->hdr.dest_adr < 2) ? 0 : req->hdr.dest_adr - 2; + _led15093_per_node_vars *v = &led15093_per_board_vars[board].per_node_vars[id_idx]; + + dprintf("LED 15093: Firmware update (UNIMPLEMENTED) (board %d, node %d)\n", + board, req->hdr.dest_adr); + + struct led15093_resp_any resp; + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = LED_15093_FRAME_SYNC; + resp.hdr.dest_adr = led15093_host_adr; + resp.hdr.src_adr = v->boardadr; + resp.hdr.nbytes = 3; + + resp.status = v->status_code; + resp.cmd = LED_15093_CMD_FW_UPDATE; + resp.report = v->report_code; + + return led15093_frame_encode(&led15093_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes); +} + +static void led15093_set_status(int board, int id, uint8_t type, uint8_t status) +{ + uint8_t id_idx = (id < 2) ? 0 : id - 2; + _led15093_per_node_vars *v = &led15093_per_board_vars[board].per_node_vars[id_idx]; + + + if (type == LED_15093_STATUS_TYPE_BOARD) { + /* Set board flag */ + v->boardstatus[0] |= status; + } else if (type == LED_15093_STATUS_TYPE_UART) { + /* Set UART flag */ + v->boardstatus[1] |= status; + /* Set response status code */ + switch (status) { + case LED_15093_STATUS_UART_ERR_SUM: + v->status_code = LED_15093_STATUS_ERR_SUM; + break; + case LED_15093_STATUS_UART_ERR_PARITY: + v->status_code = LED_15093_STATUS_ERR_PARITY; + break; + case LED_15093_STATUS_UART_ERR_FRAMING: + v->status_code = LED_15093_STATUS_ERR_FRAMING; + break; + case LED_15093_STATUS_ERR_OVERRUN: + v->status_code = LED_15093_STATUS_ERR_OVERRUN; + break; + case LED_15093_STATUS_ERR_BUFFER_OVERFLOW: + v->status_code = LED_15093_STATUS_ERR_BUFFER_OVERFLOW; + break; + default: + break; + } + } else if (type == LED_15093_STATUS_TYPE_CMD) { + /* Set command flag */ + v->boardstatus[2] |= status; + } +} + +static void led15093_set_report(int board, int id, uint8_t report) +{ + uint8_t id_idx = (id < 2) ? 0 : id - 2; + _led15093_per_node_vars *v = &led15093_per_board_vars[board].per_node_vars[id_idx]; + + v->report_code = report; +} + +static void led15093_clear_status(int board, int id) +{ + uint8_t id_idx = (id < 2) ? 0 : id - 2; + _led15093_per_node_vars *v = &led15093_per_board_vars[board].per_node_vars[id_idx]; + + if (id == 0) + { + for (int i = 0; i < led15093_nnodes; i++) { + for (int j = 0; j < (_countof(v->boardstatus) - 1); j++) { + led15093_per_board_vars[board].per_node_vars[i].boardstatus[j] = 0; + } + + led15093_per_board_vars[board].per_node_vars[i].status_code = LED_15093_STATUS_OK; + } + } + else + { + v->boardstatus[0] = 0; // Board flags + v->boardstatus[1] = 0; // UART flags + v->boardstatus[2] = 0; // Command flags + + v->status_code = LED_15093_STATUS_OK; + } +} + +static void led15093_clear_report(int board, int id) +{ + if (id == 0) + { + for (int i = 0; i < led15093_nnodes; i++) { + led15093_per_board_vars[board].per_node_vars[i].report_code = LED_15093_REPORT_OK; + } + } + else + { + uint8_t id_idx = (id < 2) ? 0 : id - 2; + _led15093_per_node_vars *v = &led15093_per_board_vars[board].per_node_vars[id_idx]; + + v->report_code = LED_15093_STATUS_OK; + } +} diff --git a/common/board/led15093.h b/common/board/led15093.h new file mode 100644 index 0000000..5ff930b --- /dev/null +++ b/common/board/led15093.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include +#include + +struct led15093_config { + bool enable; + bool high_baudrate; + unsigned int port_no[2]; + char board_number[8]; + char chip_number[5]; + char boot_chip_number[5]; + uint8_t fw_ver; + uint16_t fw_sum; +}; + +typedef HRESULT (*io_led_init_t)(void); +typedef void (*io_led_set_leds_t)(uint8_t board, uint8_t *rgb); + +HRESULT led15093_hook_init(const struct led15093_config *cfg, io_led_init_t _led_init, + io_led_set_leds_t _set_leds, unsigned int port_no[2]); + diff --git a/board/meson.build b/common/board/meson.build similarity index 69% rename from board/meson.build rename to common/board/meson.build index d463d9a..a9c24ea 100644 --- a/board/meson.build +++ b/common/board/meson.build @@ -2,7 +2,6 @@ board_lib = static_library( 'board', include_directories : inc, implicit_include_directories : false, - c_pch : '../precompiled.h', dependencies : [ capnhook.get_variable('hook_dep'), ], @@ -20,6 +19,16 @@ board_lib = static_library( 'io3.h', 'io4.c', 'io4.h', + 'led15093-cmd.h', + 'led15093-frame.c', + 'led15093-frame.h', + 'led15093.c', + 'led15093.h', + 'led15070-cmd.h', + 'led15070-frame.c', + 'led15070-frame.h', + 'led15070.c', + 'led15070.h', 'sg-cmd.c', 'sg-cmd.h', 'sg-frame.c', @@ -37,5 +46,10 @@ board_lib = static_library( 'slider-frame.h', 'vfd.c', 'vfd.h', + 'vfd-cmd.h', + 'vfd-frame.c', + 'vfd-frame.h', + 'ffb.c', + 'ffb.h' ], ) diff --git a/board/sg-cmd.c b/common/board/sg-cmd.c similarity index 100% rename from board/sg-cmd.c rename to common/board/sg-cmd.c diff --git a/board/sg-cmd.h b/common/board/sg-cmd.h similarity index 82% rename from board/sg-cmd.h rename to common/board/sg-cmd.h index 685377f..73029ec 100644 --- a/board/sg-cmd.h +++ b/common/board/sg-cmd.h @@ -25,6 +25,13 @@ struct sg_res_header { uint8_t payload_len; }; +/* struct to save the version string with its length + to fix NUL terminator issues */ +struct version_info { + const char *version; + uint8_t length; +}; + typedef HRESULT (*sg_dispatch_fn_t)( void *ctx, const void *req, diff --git a/board/sg-frame.c b/common/board/sg-frame.c similarity index 100% rename from board/sg-frame.c rename to common/board/sg-frame.c diff --git a/board/sg-frame.h b/common/board/sg-frame.h similarity index 100% rename from board/sg-frame.h rename to common/board/sg-frame.h diff --git a/board/sg-led-cmd.h b/common/board/sg-led-cmd.h similarity index 96% rename from board/sg-led-cmd.h rename to common/board/sg-led-cmd.h index f74505b..cd4bcc6 100644 --- a/board/sg-led-cmd.h +++ b/common/board/sg-led-cmd.h @@ -17,7 +17,7 @@ struct sg_led_res_reset { struct sg_led_res_get_info { struct sg_res_header res; - uint8_t payload[9]; + char payload[12]; }; struct sg_led_req_set_color { diff --git a/board/sg-led.c b/common/board/sg-led.c similarity index 90% rename from board/sg-led.c rename to common/board/sg-led.c index 855e36b..47f97c2 100644 --- a/board/sg-led.c +++ b/common/board/sg-led.c @@ -27,14 +27,18 @@ static HRESULT sg_led_cmd_set_color( const struct sg_led *led, const struct sg_led_req_set_color *req); -static const uint8_t sg_led_info[] = { - '1', '5', '0', '8', '4', 0xFF, 0x10, 0x00, 0x12, +static const struct version_info led_version[] = { + {"15084\xFF\x10\x00\x12", 9}, + {"000-00000\xFF\x11\x40", 12}, + // maybe the same? + {"000-00000\xFF\x11\x40", 12} }; void sg_led_init( struct sg_led *led, uint8_t addr, const struct sg_led_ops *ops, + unsigned int gen, void *ctx) { assert(led != NULL); @@ -43,6 +47,7 @@ void sg_led_init( led->ops = ops; led->ops_ctx = ctx; led->addr = addr; + led->gen = gen; } void sg_led_transact( @@ -150,8 +155,11 @@ static HRESULT sg_led_cmd_get_info( struct sg_led_res_get_info *res) { sg_led_dprintf(led, "Get info\n"); - sg_res_init(&res->res, req, sizeof(res->payload)); - memcpy(res->payload, sg_led_info, sizeof(sg_led_info)); + + const struct version_info *fw = &led_version[led->gen - 1]; + + sg_res_init(&res->res, req, fw->length); + memcpy(res->payload, fw->version, fw->length); return S_OK; } diff --git a/board/sg-led.h b/common/board/sg-led.h similarity index 92% rename from board/sg-led.h rename to common/board/sg-led.h index de3caa6..eaa8d16 100644 --- a/board/sg-led.h +++ b/common/board/sg-led.h @@ -15,12 +15,14 @@ struct sg_led { const struct sg_led_ops *ops; void *ops_ctx; uint8_t addr; + unsigned int gen; }; void sg_led_init( struct sg_led *led, uint8_t addr, const struct sg_led_ops *ops, + unsigned int gen, void *ctx); void sg_led_transact( diff --git a/board/sg-nfc-cmd.h b/common/board/sg-nfc-cmd.h similarity index 73% rename from board/sg-nfc-cmd.h rename to common/board/sg-nfc-cmd.h index b6754c2..e54e0a7 100644 --- a/board/sg-nfc-cmd.h +++ b/common/board/sg-nfc-cmd.h @@ -5,18 +5,21 @@ #pragma pack(push, 1) enum { - SG_NFC_CMD_GET_FW_VERSION = 0x30, - SG_NFC_CMD_GET_HW_VERSION = 0x32, - SG_NFC_CMD_RADIO_ON = 0x40, - SG_NFC_CMD_RADIO_OFF = 0x41, - SG_NFC_CMD_POLL = 0x42, - SG_NFC_CMD_MIFARE_SELECT_TAG = 0x43, - SG_NFC_CMD_MIFARE_SET_KEY_BANA = 0x50, - SG_NFC_CMD_MIFARE_READ_BLOCK = 0x52, - SG_NFC_CMD_MIFARE_SET_KEY_AIME = 0x54, - SG_NFC_CMD_MIFARE_AUTHENTICATE = 0x55, /* guess based on time sent */ - SG_NFC_CMD_RESET = 0x62, - SG_NFC_CMD_FELICA_ENCAP = 0x71, + SG_NFC_CMD_GET_FW_VERSION = 0x30, + SG_NFC_CMD_GET_HW_VERSION = 0x32, + SG_NFC_CMD_RADIO_ON = 0x40, + SG_NFC_CMD_RADIO_OFF = 0x41, + SG_NFC_CMD_POLL = 0x42, + SG_NFC_CMD_MIFARE_SELECT_TAG = 0x43, + SG_NFC_CMD_MIFARE_SET_KEY_AIME = 0x50, + SG_NFC_CMD_MIFARE_AUTHENTICATE_AIME = 0x51, + SG_NFC_CMD_MIFARE_READ_BLOCK = 0x52, + SG_NFC_CMD_MIFARE_SET_KEY_BANA = 0x54, + SG_NFC_CMD_MIFARE_AUTHENTICATE_BANA = 0x55, + SG_NFC_CMD_TO_UPDATE_MODE = 0x60, + SG_NFC_CMD_SEND_HEX_DATA = 0x61, + SG_NFC_CMD_RESET = 0x62, + SG_NFC_CMD_FELICA_ENCAP = 0x71, }; struct sg_nfc_res_get_fw_version { @@ -31,7 +34,7 @@ struct sg_nfc_res_get_hw_version { struct sg_nfc_req_mifare_set_key { struct sg_req_header req; - uint8_t key_a[6]; + uint8_t key[6]; }; struct sg_nfc_req_mifare_50 { diff --git a/board/sg-nfc.c b/common/board/sg-nfc.c similarity index 73% rename from board/sg-nfc.c rename to common/board/sg-nfc.c index 2db684d..b103336 100644 --- a/board/sg-nfc.c +++ b/common/board/sg-nfc.c @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -16,6 +17,7 @@ #include "util/dprintf.h" #include "util/dump.h" +#include "util/slurp.h" static HRESULT sg_nfc_dispatch( void *ctx, @@ -60,15 +62,35 @@ static HRESULT sg_nfc_cmd_felica_encap( const struct sg_nfc_req_felica_encap *req, struct sg_nfc_res_felica_encap *res); +static HRESULT sg_nfc_cmd_send_hex_data( + struct sg_nfc *nfc, + const struct sg_req_header *req, + struct sg_res_header *res); + static HRESULT sg_nfc_cmd_dummy( struct sg_nfc *nfc, const struct sg_req_header *req, struct sg_res_header *res); +static const struct version_info hw_version[] = { + {"TN32MSEC003S H/W Ver3.0", 23}, + {"837-15286", 9}, + {"837-15396", 9} +}; + +static const struct version_info fw_version[] = { + {"TN32MSEC003S F/W Ver1.2", 23}, + {"\x94", 1}, + {"\x94", 1} +}; + void sg_nfc_init( struct sg_nfc *nfc, uint8_t addr, const struct sg_nfc_ops *ops, + unsigned int gen, + unsigned int proxy_flag, + const wchar_t* authdata_path, void *ops_ctx) { assert(nfc != NULL); @@ -77,6 +99,9 @@ void sg_nfc_init( nfc->ops = ops; nfc->ops_ctx = ops_ctx; nfc->addr = addr; + nfc->gen = gen; + nfc->proxy_flag = proxy_flag; + nfc->authdata_path = authdata_path; } #ifdef NDEBUG @@ -170,12 +195,17 @@ static HRESULT sg_nfc_dispatch( &req->felica_encap, &res->felica_encap); - case SG_NFC_CMD_MIFARE_AUTHENTICATE: + case SG_NFC_CMD_MIFARE_AUTHENTICATE_AIME: + case SG_NFC_CMD_MIFARE_AUTHENTICATE_BANA: + case SG_NFC_CMD_SEND_HEX_DATA: + return sg_nfc_cmd_send_hex_data(nfc, &req->simple, &res->simple); + case SG_NFC_CMD_MIFARE_SELECT_TAG: case SG_NFC_CMD_MIFARE_SET_KEY_AIME: case SG_NFC_CMD_MIFARE_SET_KEY_BANA: case SG_NFC_CMD_RADIO_ON: case SG_NFC_CMD_RADIO_OFF: + case SG_NFC_CMD_TO_UPDATE_MODE: return sg_nfc_cmd_dummy(nfc, &req->simple, &res->simple); default: @@ -202,9 +232,11 @@ static HRESULT sg_nfc_cmd_get_fw_version( const struct sg_req_header *req, struct sg_nfc_res_get_fw_version *res) { + const struct version_info *fw = &fw_version[nfc->gen - 1]; + /* Dest version is not NUL terminated, this is intentional */ - sg_res_init(&res->res, req, sizeof(res->version)); - memcpy(res->version, "TN32MSEC003S F/W Ver1.2E", sizeof(res->version)); + sg_res_init(&res->res, req, fw->length); + memcpy(res->version, fw->version, fw->length); return S_OK; } @@ -214,9 +246,11 @@ static HRESULT sg_nfc_cmd_get_hw_version( const struct sg_req_header *req, struct sg_nfc_res_get_hw_version *res) { + const struct version_info *hw = &hw_version[nfc->gen - 1]; + /* Dest version is not NUL terminated, this is intentional */ - sg_res_init(&res->res, req, sizeof(res->version)); - memcpy(res->version, "TN32MSEC003S H/W Ver3.0J", sizeof(res->version)); + sg_res_init(&res->res, req, hw->length); + memcpy(res->version, hw->version, hw->length); return S_OK; } @@ -287,6 +321,7 @@ static HRESULT sg_nfc_poll_aime( mifare->type = 0x10; mifare->id_len = sizeof(mifare->uid); + // mifare->uid = _byteswap_ulong(0x8FBECBFF); mifare->uid = _byteswap_ulong(0x01020304); /* Initialize MIFARE IC emulator */ @@ -326,13 +361,13 @@ static HRESULT sg_nfc_poll_felica( felica->type = 0x20; felica->id_len = sizeof(felica->IDm) + sizeof(felica->PMm); felica->IDm = _byteswap_uint64(IDm); - felica->PMm = _byteswap_uint64(felica_get_generic_PMm()); + felica->PMm = _byteswap_uint64(felica_get_amusement_ic_PMm()); /* Initialize FeliCa IC emulator */ nfc->felica.IDm = IDm; - nfc->felica.PMm = felica_get_generic_PMm(); - nfc->felica.system_code = 0x0000; + nfc->felica.PMm = felica_get_amusement_ic_PMm(); + nfc->felica.system_code = 0x88b4; return S_OK; } @@ -354,18 +389,62 @@ static HRESULT sg_nfc_cmd_mifare_read_block( sg_nfc_dprintf(nfc, "Read uid %08x block %i\n", uid, req->payload.block_no); - if (req->payload.block_no > 3) { + if (req->payload.block_no > 14) { sg_nfc_dprintf(nfc, "MIFARE block number out of range\n"); return E_FAIL; + } else if (req->payload.block_no >= 5){ // emoney auth encrypted + + sg_res_init(&res->res, &req->req, sizeof(res->block)); + + char* auth; + long size = wslurp(nfc->authdata_path, &auth, false); + if (size < 0){ + sg_nfc_dprintf(nfc, "Failed to read %ls: %lx!\n", nfc->authdata_path, GetLastError()); + return E_FAIL; + } + + int offset = 0; + if (req->payload.block_no == 6){ + offset = 16; + } else if (req->payload.block_no == 8){ + offset = 32; + } else if (req->payload.block_no == 9){ + offset = 48; + } else if (req->payload.block_no == 10){ + offset = 64; + } else if (req->payload.block_no == 12){ + offset = 82; + } else if (req->payload.block_no == 13){ + offset = 98; + } else if (req->payload.block_no == 14){ + offset = 114; + } + + for (int i = 0; i < 16 && offset + i < size; i++){ + res->block[i] = auth[offset + i]; + } + + free(auth); + + } else if (req->payload.block_no == 4){ // emoney auth plain + + sg_res_init(&res->res, &req->req, sizeof(res->block)); + + res->block[0] = 0x54; // header + res->block[1] = 0x43; + res->block[2] = nfc->proxy_flag; // 2 or 3 depending on game (useProxy in env.json) + res->block[3] = 0x01; // unknown flag + + } else { // read all other blocks normally + + sg_res_init(&res->res, &req->req, sizeof(res->block)); + + memcpy( res->block, + nfc->mifare.sectors[0].blocks[req->payload.block_no].bytes, + sizeof(res->block)); } - sg_res_init(&res->res, &req->req, sizeof(res->block)); - - memcpy( res->block, - nfc->mifare.sectors[0].blocks[req->payload.block_no].bytes, - sizeof(res->block)); - return S_OK; } @@ -401,7 +480,7 @@ static HRESULT sg_nfc_cmd_felica_encap( f_res.nbytes = sizeof(res->payload); f_res.pos = 1; -#if 0 +#if defined(LOG_NFC) dprintf("FELICA OUTBOUND:\n"); dump_const_iobuf(&f_req); #endif @@ -415,7 +494,7 @@ static HRESULT sg_nfc_cmd_felica_encap( sg_res_init(&res->res, &req->req, f_res.pos); res->payload[0] = f_res.pos; -#if 0 +#if defined(LOG_NFC) dprintf("FELICA INBOUND:\n"); dump_iobuf(&f_res); #endif @@ -423,6 +502,22 @@ static HRESULT sg_nfc_cmd_felica_encap( return S_OK; } +static HRESULT sg_nfc_cmd_send_hex_data( + struct sg_nfc *nfc, + const struct sg_req_header *req, + struct sg_res_header *res) +{ + sg_res_init(res, req, 0); + + /* Firmware checksum length? */ + if (req->payload_len == 0x2b) { + /* The firmware is identical flag? */ + res->status = 0x20; + } + + return S_OK; +} + static HRESULT sg_nfc_cmd_dummy( struct sg_nfc *nfc, const struct sg_req_header *req, diff --git a/board/sg-nfc.h b/common/board/sg-nfc.h similarity index 81% rename from board/sg-nfc.h rename to common/board/sg-nfc.h index 5562b2b..092b3e3 100644 --- a/board/sg-nfc.h +++ b/common/board/sg-nfc.h @@ -22,14 +22,20 @@ struct sg_nfc { const struct sg_nfc_ops *ops; void *ops_ctx; uint8_t addr; + unsigned int gen; + unsigned int proxy_flag; struct felica felica; struct mifare mifare; + const wchar_t* authdata_path; }; void sg_nfc_init( struct sg_nfc *nfc, uint8_t addr, const struct sg_nfc_ops *ops, + unsigned int gen, + unsigned int proxy_flag, + const wchar_t* authdata_path, void *ops_ctx); void sg_nfc_transact( diff --git a/board/sg-reader.c b/common/board/sg-reader.c similarity index 85% rename from board/sg-reader.c rename to common/board/sg-reader.c index dbf0392..2dc3c3d 100644 --- a/board/sg-reader.c +++ b/common/board/sg-reader.c @@ -47,7 +47,8 @@ static struct sg_led sg_reader_led; HRESULT sg_reader_hook_init( const struct aime_config *cfg, - unsigned int port_no, + unsigned int default_port_no, + unsigned int gen, HINSTANCE self) { HRESULT hr; @@ -65,11 +66,31 @@ HRESULT sg_reader_hook_init( return hr; } - sg_nfc_init(&sg_reader_nfc, 0x00, &sg_reader_nfc_ops, NULL); - sg_led_init(&sg_reader_led, 0x08, &sg_reader_led_ops, NULL); + unsigned int port_no = cfg->port_no; + if (port_no == 0){ + port_no = default_port_no; + } + + if (cfg->gen != 0) { + gen = cfg->gen; + } + + if (gen < 1 || gen > 3) { + dprintf("NFC Assembly: Invalid reader generation: %u\n", gen); + + return E_INVALIDARG; + } + + sg_nfc_init(&sg_reader_nfc, 0x00, &sg_reader_nfc_ops, gen, cfg->proxy_flag, cfg->authdata_path, NULL); + sg_led_init(&sg_reader_led, 0x08, &sg_reader_led_ops, gen, NULL); InitializeCriticalSection(&sg_reader_lock); + if (!cfg->high_baudrate) { + sg_reader_uart.baud.BaudRate = 38400; + } + + dprintf("NFC Assembly: enabling (port=%d)\n", port_no); uart_init(&sg_reader_uart, port_no); sg_reader_uart.written.bytes = sg_reader_written_bytes; sg_reader_uart.written.nbytes = sizeof(sg_reader_written_bytes); @@ -100,14 +121,14 @@ static HRESULT sg_reader_handle_irp_locked(struct irp *irp) { HRESULT hr; -#if 0 +#if defined(LOG_NFC) if (irp->op == IRP_OP_WRITE) { dprintf("WRITE:\n"); dump_const_iobuf(&irp->write); } #endif -#if 0 +#if defined(LOG_NFC) if (irp->op == IRP_OP_READ) { dprintf("READ:\n"); dump_iobuf(&sg_reader_uart.readable); diff --git a/board/sg-reader.h b/common/board/sg-reader.h similarity index 55% rename from board/sg-reader.h rename to common/board/sg-reader.h index 673a8bd..64de7e9 100644 --- a/board/sg-reader.h +++ b/common/board/sg-reader.h @@ -9,9 +9,15 @@ struct aime_config { struct aime_dll_config dll; bool enable; + unsigned int port_no; + bool high_baudrate; + unsigned int gen; + unsigned int proxy_flag; + wchar_t authdata_path[MAX_PATH]; }; HRESULT sg_reader_hook_init( const struct aime_config *cfg, - unsigned int port_no, + unsigned int default_port_no, + unsigned int gen, HINSTANCE self); diff --git a/board/slider-cmd.h b/common/board/slider-cmd.h similarity index 100% rename from board/slider-cmd.h rename to common/board/slider-cmd.h diff --git a/board/slider-frame.c b/common/board/slider-frame.c similarity index 100% rename from board/slider-frame.c rename to common/board/slider-frame.c diff --git a/board/slider-frame.h b/common/board/slider-frame.h similarity index 100% rename from board/slider-frame.h rename to common/board/slider-frame.h diff --git a/common/board/vfd-cmd.h b/common/board/vfd-cmd.h new file mode 100644 index 0000000..28bcb16 --- /dev/null +++ b/common/board/vfd-cmd.h @@ -0,0 +1,123 @@ +#pragma once + +#include "board/vfd-frame.h" + +enum { + VFD_CMD_GET_VERSION = 0x5B, + VFD_CMD_RESET = 0x0B, + VFD_CMD_CLEAR_SCREEN = 0x0C, + VFD_CMD_SET_BRIGHTNESS = 0x20, + VFD_CMD_SET_SCREEN_ON = 0x21, + VFD_CMD_SET_H_SCROLL = 0x22, + VFD_CMD_DRAW_IMAGE = 0x2E, + VFD_CMD_SET_CURSOR = 0x30, + VFD_CMD_SET_ENCODING = 0x32, + VFD_CMD_SET_TEXT_WND = 0x40, + VFD_CMD_SET_TEXT_SPEED = 0x41, + VFD_CMD_WRITE_TEXT = 0x50, + VFD_CMD_ENABLE_SCROLL = 0x51, + VFD_CMD_DISABLE_SCROLL = 0x52, + VFD_CMD_ROTATE = 0x5D, + VFD_CMD_CREATE_CHAR = 0xA3, + VFD_CMD_CREATE_CHAR2 = 0xA4, +}; + +enum { + VFD_ENC_GB2312 = 0, + VFD_ENC_BIG5 = 1, + VFD_ENC_SHIFT_JIS = 2, + VFD_ENC_KSC5601 = 3, + VFD_ENC_MAX = 3, +}; + +struct vfd_req_hdr { + uint8_t sync; + uint8_t cmd; +}; + +struct vfd_req_any { + struct vfd_req_hdr hdr; + uint8_t payload[2054]; +}; + +struct vfd_req_board_info { + struct vfd_req_hdr hdr; + uint8_t unk1; +}; + +struct vfd_resp_board_info { // \x0201.20\x03 + uint8_t unk1; + char version[5]; + uint8_t unk2; +}; + +struct vfd_req_reset { + struct vfd_req_hdr hdr; +}; + +struct vfd_req_cls { + struct vfd_req_hdr hdr; +}; + +struct vfd_req_brightness { + struct vfd_req_hdr hdr; + uint8_t brightness; +}; + +struct vfd_req_power { + struct vfd_req_hdr hdr; + uint8_t power_state; +}; + +struct vfd_req_hscroll { + struct vfd_req_hdr hdr; + uint8_t x_pos; +}; + +struct vfd_req_draw { + struct vfd_req_hdr hdr; + uint16_t x0; + uint8_t y0; + uint16_t x1; + uint8_t y1; + uint8_t image[2048]; +}; + +struct vfd_req_cursor { + struct vfd_req_hdr hdr; + uint16_t x; + uint8_t y; +}; + +struct vfd_req_encoding { + struct vfd_req_hdr hdr; + uint8_t encoding; +}; + +struct vfd_req_wnd { + struct vfd_req_hdr hdr; + uint16_t x0; + uint8_t y0; + uint16_t x1; + uint8_t y1; +}; + +struct vfd_req_speed { + struct vfd_req_hdr hdr; + uint8_t encoding; +}; + +struct vfd_req_scroll { + struct vfd_req_hdr hdr; +}; + +struct vfd_req_rotate { + struct vfd_req_hdr hdr; + uint8_t unk1; +}; + +struct vfd_req_create_char { + struct vfd_req_hdr hdr; + uint8_t type; + uint8_t pixels[32]; +}; diff --git a/common/board/vfd-frame.c b/common/board/vfd-frame.c new file mode 100644 index 0000000..05fe302 --- /dev/null +++ b/common/board/vfd-frame.c @@ -0,0 +1,88 @@ +#include + +#include +#include +#include +#include + +#define SUPER_VERBOSE 1 + +#include "board/vfd-frame.h" + +#include "hook/iobuf.h" + +#include "util/dprintf.h" + +static HRESULT vfd_frame_encode_byte(struct iobuf *dest, uint8_t byte); + +/* Frame structure: + + REQUEST: + [0] Sync byte (0x1A or 0x1B) + [1] Packet ID + [2...n-1] Data/payload + + --- OR --- + + if no sync byte is given, plain static text in the currently configured encoding is expected. + + RESPONSE: + This thing never responds, unless it's VFD_CMD_GET_VERSION + */ + +bool vfd_frame_sync(struct const_iobuf *src) { + return src->bytes[src->pos] == VFD_SYNC_BYTE || src->bytes[src->pos] == VFD_SYNC_BYTE2; +} + +HRESULT vfd_frame_encode( + struct iobuf *dest, + const void *ptr, + size_t nbytes) { + const uint8_t *src; + uint8_t byte; + size_t i; + HRESULT hr; + + assert(dest != NULL); + assert(dest->bytes != NULL || dest->nbytes == 0); + assert(dest->pos <= dest->nbytes); + assert(ptr != NULL); + + src = ptr; + + if (dest->pos >= dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + +#if SUPER_VERBOSE + dprintf("VFD: RX Buffer:\n"); +#endif + + for (i = 1; i < nbytes; i++) { + byte = src[i]; +#if SUPER_VERBOSE + dprintf("%02x ", byte); +#endif + + hr = vfd_frame_encode_byte(dest, byte); + + if (FAILED(hr)) { + return hr; + } + } +#if SUPER_VERBOSE + dprintf("\n"); +#endif + + return hr; +} + +static HRESULT vfd_frame_encode_byte(struct iobuf *dest, uint8_t byte) { + if (dest->pos + 1 > dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = byte; + + return S_OK; +} diff --git a/common/board/vfd-frame.h b/common/board/vfd-frame.h new file mode 100644 index 0000000..7055f5b --- /dev/null +++ b/common/board/vfd-frame.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include +#include + +#include "hook/iobuf.h" + +enum { + VFD_SYNC_BYTE = 0x1B, + VFD_SYNC_BYTE2 = 0x1A, +}; + +bool vfd_frame_sync(struct const_iobuf *src); + +HRESULT vfd_frame_encode( + struct iobuf *dest, + const void *ptr, + size_t nbytes); diff --git a/common/board/vfd.c b/common/board/vfd.c new file mode 100644 index 0000000..4cfb20b --- /dev/null +++ b/common/board/vfd.c @@ -0,0 +1,399 @@ +/* This is some sort of LCD display found on various cabinets. It is driven + directly by amdaemon, and it has something to do with displaying the status + of electronic payments. + + Part number in schematics is "VFD GP1232A02A FUTABA". */ + +#include + +#include +#include +#include + +#include "board/config.h" +#include "board/vfd.h" +#include "board/vfd-cmd.h" + +#include "hook/iohook.h" + +#include "hooklib/uart.h" + +#include "util/dprintf.h" +#include "util/dump.h" + +#define SUPER_VERBOSE 0 + +static HRESULT vfd_handle_irp(struct irp *irp); + +static struct uart vfd_uart; +static uint8_t vfd_written[4096]; +static uint8_t vfd_readable[4096]; + +static int encoding = VFD_ENC_SHIFT_JIS; + +HRESULT vfd_handle_get_version(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart); +HRESULT vfd_handle_reset(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart); +HRESULT vfd_handle_clear_screen(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart); +HRESULT vfd_handle_set_brightness(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart); +HRESULT vfd_handle_set_screen_on(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart); +HRESULT vfd_handle_set_h_scroll(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart); +HRESULT vfd_handle_draw_image(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart); +HRESULT vfd_handle_set_cursor(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart); +HRESULT vfd_handle_set_encoding(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart); +HRESULT vfd_handle_set_text_wnd(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart); +HRESULT vfd_handle_set_text_speed(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart); +HRESULT vfd_handle_write_text(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart); +HRESULT vfd_handle_enable_scroll(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart); +HRESULT vfd_handle_disable_scroll(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart); +HRESULT vfd_handle_rotate(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart); +HRESULT vfd_handle_create_char(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart); +HRESULT vfd_handle_create_char2(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart); + +static bool utf_enabled; + +HRESULT vfd_hook_init(struct vfd_config *cfg, unsigned int default_port_no) +{ + if (!cfg->enable){ + return S_FALSE; + } + + utf_enabled = cfg->utf_conversion; + + unsigned int port_no = cfg->port_no; + if (port_no == 0){ + port_no = default_port_no; + } + + dprintf("VFD: enabling (port=%d)\n", port_no); + uart_init(&vfd_uart, port_no); + vfd_uart.written.bytes = vfd_written; + vfd_uart.written.nbytes = sizeof(vfd_written); + vfd_uart.readable.bytes = vfd_readable; + vfd_uart.readable.nbytes = sizeof(vfd_readable); + + return iohook_push_handler(vfd_handle_irp); +} + + +const char* get_encoding_name(int b){ + switch (b){ + case 0: return "gb2312"; + case 1: return "big5"; + case 2: return "shift-jis"; + case 3: return "ks_c_5601-1987"; + default: return "unknown"; + } +} + +void print_vfd_text(const char* str, int len){ + + if (utf_enabled){ + + wchar_t encoded[1024]; + memset(encoded, 0, 1024 * sizeof(wchar_t)); + + int codepage = 0; + if (encoding == VFD_ENC_GB2312){ + codepage = 936; + } else if (encoding == VFD_ENC_BIG5){ + codepage = 950; + } else if (encoding == VFD_ENC_SHIFT_JIS){ + codepage = 932; + } else if (encoding == VFD_ENC_KSC5601) { + codepage = 949; + } + + if (!MultiByteToWideChar(codepage, MB_USEGLYPHCHARS, str, len, encoded, 1024)){ + dprintf("VFD: Text conversion failed: %ld", GetLastError()); + return; + } + + dprintf("VFD: Text: %ls\n", encoded); + } else { + + dprintf("VFD: Text: %s\n", str); + + } +} + +static HRESULT vfd_handle_irp(struct irp *irp) +{ + HRESULT hr; + + assert(irp != NULL); + + if (!uart_match_irp(&vfd_uart, irp)) { + return iohook_invoke_next(irp); + } + + if (irp->op == IRP_OP_OPEN){ + dprintf("VFD: Open\n"); + } else if (irp->op == IRP_OP_CLOSE){ + dprintf("VFD: Close\n"); + } + + hr = uart_handle_irp(&vfd_uart, irp); + + if (FAILED(hr) || irp->op != IRP_OP_WRITE) { + return hr; + } + +#if SUPER_VERBOSE + dprintf("VFD TX:\n"); + dump_iobuf(&vfd_uart.written); +#endif + + struct const_iobuf reader; + iobuf_flip(&reader, &vfd_uart.written); + + struct iobuf* writer = &vfd_uart.readable; + for (; reader.pos < reader.nbytes ; ){ + + if (vfd_frame_sync(&reader)) { + + reader.pos++; // get the sync byte out of the way + + uint8_t cmd; + iobuf_read_8(&reader, &cmd); + + if (cmd == VFD_CMD_GET_VERSION) { + hr = vfd_handle_get_version(&reader, writer, &vfd_uart); + } else if (cmd == VFD_CMD_RESET) { + hr = vfd_handle_reset(&reader, writer, &vfd_uart); + } else if (cmd == VFD_CMD_CLEAR_SCREEN) { + hr = vfd_handle_clear_screen(&reader, writer, &vfd_uart); + } else if (cmd == VFD_CMD_SET_BRIGHTNESS) { + hr = vfd_handle_set_brightness(&reader, writer, &vfd_uart); + } else if (cmd == VFD_CMD_SET_SCREEN_ON) { + hr = vfd_handle_set_screen_on(&reader, writer, &vfd_uart); + } else if (cmd == VFD_CMD_SET_H_SCROLL) { + hr = vfd_handle_set_h_scroll(&reader, writer, &vfd_uart); + } else if (cmd == VFD_CMD_DRAW_IMAGE) { + hr = vfd_handle_draw_image(&reader, writer, &vfd_uart); + } else if (cmd == VFD_CMD_SET_CURSOR) { + hr = vfd_handle_set_cursor(&reader, writer, &vfd_uart); + } else if (cmd == VFD_CMD_SET_ENCODING) { + hr = vfd_handle_set_encoding(&reader, writer, &vfd_uart); + } else if (cmd == VFD_CMD_SET_TEXT_WND) { + hr = vfd_handle_set_text_wnd(&reader, writer, &vfd_uart); + } else if (cmd == VFD_CMD_SET_TEXT_SPEED) { + hr = vfd_handle_set_text_speed(&reader, writer, &vfd_uart); + } else if (cmd == VFD_CMD_WRITE_TEXT) { + hr = vfd_handle_write_text(&reader, writer, &vfd_uart); + } else if (cmd == VFD_CMD_ENABLE_SCROLL) { + hr = vfd_handle_enable_scroll(&reader, writer, &vfd_uart); + } else if (cmd == VFD_CMD_DISABLE_SCROLL) { + hr = vfd_handle_disable_scroll(&reader, writer, &vfd_uart); + } else if (cmd == VFD_CMD_ROTATE) { + hr = vfd_handle_rotate(&reader, writer, &vfd_uart); + } else if (cmd == VFD_CMD_CREATE_CHAR) { + hr = vfd_handle_create_char(&reader, writer, &vfd_uart); + } else if (cmd == VFD_CMD_CREATE_CHAR2) { + hr = vfd_handle_create_char2(&reader, writer, &vfd_uart); + } else { + dprintf("VFD: Unknown command 0x%x\n", cmd); + dump_const_iobuf(&reader); + hr = S_FALSE; + } + } else { + + // if no sync byte is sent, we are just getting plain text... + + if (reader.pos < reader.nbytes){ + int len = 0; + + // read chars until we hit a new sync byte or the data ends + while (reader.pos + len + 1 < reader.nbytes && reader.bytes[reader.pos + len] != VFD_SYNC_BYTE && reader.bytes[reader.pos + len] != VFD_SYNC_BYTE2){ + len++; + } + + char* str = malloc(len); + memset(str, 0, len); + iobuf_read(&reader, str, len); + print_vfd_text(str, len); + free(str); + + reader.pos += len; + } + + } + + if (!SUCCEEDED(hr)){ + return hr; + } + + } + + vfd_uart.written.pos = 0; + + return hr; +} + +HRESULT vfd_handle_get_version(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart){ + dprintf("VFD: Get Version\n"); + + struct vfd_resp_board_info resp; + + memset(&resp, 0, sizeof(resp)); + resp.unk1 = 2; + strcpy(resp.version, "01.20"); + resp.unk2 = 1; + + return vfd_frame_encode(writer, &resp, sizeof(resp)); +} +HRESULT vfd_handle_reset(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart){ + dprintf("VFD: Reset\n"); + + encoding = VFD_ENC_SHIFT_JIS; + + return S_FALSE; +} +HRESULT vfd_handle_clear_screen(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart){ + dprintf("VFD: Clear Screen\n"); + + return S_FALSE; +} +HRESULT vfd_handle_set_brightness(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart){ + uint8_t b; + iobuf_read_8(reader, &b); + + if (b > 4){ + dprintf("VFD: Brightness, invalid argument\n"); + return E_FAIL; + } + + dprintf("VFD: Brightness, %d\n", b); + + return S_FALSE; +} +HRESULT vfd_handle_set_screen_on(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart){ + uint8_t b; + iobuf_read_8(reader, &b); + + if (b > 1){ + dprintf("VFD: Screen Power, invalid argument\n"); + return E_FAIL; + } + + dprintf("VFD: Screen Power, %d\n", b); + return S_FALSE; +} +HRESULT vfd_handle_set_h_scroll(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart){ + uint8_t x; + iobuf_read_8(reader, &x); + + dprintf("VFD: Horizontal Scroll, X=%d\n", x); + return S_FALSE; +} +HRESULT vfd_handle_draw_image(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart){ + int w, h; + uint16_t x0, x1; + uint8_t y0, y1; + uint8_t image[2048]; + + iobuf_read_be16(reader, &x0); + iobuf_read_8(reader, &y0); + iobuf_read_be16(reader, &x1); + iobuf_read_8(reader, &y1); + w = x1 - x0; + h = y1 - y0; + iobuf_read(reader, image, w*h); + + dprintf("VFD: Draw image, %dx%d\n", w, h); + return S_FALSE; +} + +HRESULT vfd_handle_set_cursor(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart){ + uint16_t x; + uint8_t y; + + iobuf_read_be16(reader, &x); + iobuf_read_8(reader, &y); + + dprintf("VFD: Set Cursor, x=%d,y=%d\n", x, y); + + return S_FALSE; +} + +HRESULT vfd_handle_set_encoding(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart){ + uint8_t b; + iobuf_read_8(reader, &b); + + dprintf("VFD: Set Encoding, %d (%s)\n", b, get_encoding_name(b)); + + if (b < 0 || b > VFD_ENC_MAX){ + dprintf("Invalid encoding specified\n"); + return E_FAIL; + } + + encoding = b; + + return S_FALSE; +} +HRESULT vfd_handle_set_text_wnd(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart){ + uint16_t x0, x1; + uint8_t y0, y1; + + iobuf_read_be16(reader, &x0); + iobuf_read_8(reader, &y0); + iobuf_read_be16(reader, &x1); + iobuf_read_8(reader, &y1); + + dprintf("VFD: Set Text Window, p0:%d,%d, p1:%d,%d\n", x0, y0, x1, y1); + return S_FALSE; +} +HRESULT vfd_handle_set_text_speed(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart){ + uint8_t b; + iobuf_read_8(reader, &b); + + dprintf("VFD: Set Text Speed, %d\n", b); + return S_FALSE; +} +HRESULT vfd_handle_write_text(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart){ + uint8_t len; + iobuf_read_8(reader, &len); + + char* str = malloc(len); + iobuf_read(reader, str, len); + + print_vfd_text(str, len); + free(str); + + return S_FALSE; +} +HRESULT vfd_handle_enable_scroll(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart){ + dprintf("VFD: Enable Scrolling\n"); + return S_FALSE; +} +HRESULT vfd_handle_disable_scroll(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart){ + dprintf("VFD: Disable Scrolling\n"); + return S_FALSE; +} +HRESULT vfd_handle_rotate(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart){ + uint8_t b; + iobuf_read_8(reader, &b); + + dprintf("VFD: Rotate, %d\n", b); + return S_FALSE; +} +HRESULT vfd_handle_create_char(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart){ + uint8_t b; + iobuf_read_8(reader, &b); + char buf[32]; + + iobuf_read(reader, buf, 32); + + dprintf("VFD: Create character, %d\n", b); + return S_FALSE; +} +HRESULT vfd_handle_create_char2(struct const_iobuf* reader, struct iobuf* writer, struct uart* vfd_uart){ + uint8_t b, b2; + iobuf_read_8(reader, &b); + iobuf_read_8(reader, &b2); + char buf[16]; + + iobuf_read(reader, buf, 16); + + dprintf("VFD: Create character, %d, %d\n", b, b2); + return S_FALSE; +} diff --git a/common/board/vfd.h b/common/board/vfd.h new file mode 100644 index 0000000..5d3664c --- /dev/null +++ b/common/board/vfd.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +struct vfd_config { + bool enable; + unsigned int port_no; + bool utf_conversion; +}; + + +HRESULT vfd_hook_init(struct vfd_config *cfg, unsigned int default_port_no); + diff --git a/gfxhook/config.c b/common/gfxhook/config.c similarity index 87% rename from gfxhook/config.c rename to common/gfxhook/config.c index 98db059..5c49625 100644 --- a/gfxhook/config.c +++ b/common/gfxhook/config.c @@ -14,4 +14,5 @@ void gfx_config_load(struct gfx_config *cfg, const wchar_t *filename) cfg->windowed = GetPrivateProfileIntW(L"gfx", L"windowed", 0, filename); cfg->framed = GetPrivateProfileIntW(L"gfx", L"framed", 1, filename); cfg->monitor = GetPrivateProfileIntW(L"gfx", L"monitor", 0, filename); + cfg->dpiAware = GetPrivateProfileIntW(L"gfx", L"dpiAware", 1, filename); } diff --git a/gfxhook/config.h b/common/gfxhook/config.h similarity index 100% rename from gfxhook/config.h rename to common/gfxhook/config.h diff --git a/gfxhook/d3d11.c b/common/gfxhook/d3d11.c similarity index 100% rename from gfxhook/d3d11.c rename to common/gfxhook/d3d11.c diff --git a/gfxhook/d3d11.h b/common/gfxhook/d3d11.h similarity index 100% rename from gfxhook/d3d11.h rename to common/gfxhook/d3d11.h diff --git a/gfxhook/d3d9.c b/common/gfxhook/d3d9.c similarity index 92% rename from gfxhook/d3d9.c rename to common/gfxhook/d3d9.c index 34a165d..7db36e5 100644 --- a/gfxhook/d3d9.c +++ b/common/gfxhook/d3d9.c @@ -224,9 +224,19 @@ static HRESULT STDMETHODCALLTYPE my_IDirect3D9_CreateDevice( gfx_util_frame_window(hwnd); } - dprintf("Gfx: Using adapter %d\n", gfx_config.monitor); + UINT max_adapter = IDirect3D9_GetAdapterCount(real); + adapter = gfx_config.monitor; + if (adapter >= max_adapter) { + dprintf( + "Gfx: Requested adapter %d but maximum is %d. Using primary monitor\n", + gfx_config.monitor, max_adapter - 1 + ); + adapter = D3DADAPTER_DEFAULT; + } else { + dprintf("Gfx: Using adapter %d\n", gfx_config.monitor); + } - return IDirect3D9_CreateDevice(real, gfx_config.monitor, type, hwnd, flags, pp, pdev); + return IDirect3D9_CreateDevice(real, adapter, type, hwnd, flags, pp, pdev); } static HRESULT STDMETHODCALLTYPE my_IDirect3D9Ex_CreateDevice( diff --git a/gfxhook/d3d9.h b/common/gfxhook/d3d9.h similarity index 100% rename from gfxhook/d3d9.h rename to common/gfxhook/d3d9.h diff --git a/gfxhook/dxgi.c b/common/gfxhook/dxgi.c similarity index 100% rename from gfxhook/dxgi.c rename to common/gfxhook/dxgi.c diff --git a/gfxhook/dxgi.h b/common/gfxhook/dxgi.h similarity index 100% rename from gfxhook/dxgi.h rename to common/gfxhook/dxgi.h diff --git a/common/gfxhook/gfx.c b/common/gfxhook/gfx.c new file mode 100644 index 0000000..5d7b8cd --- /dev/null +++ b/common/gfxhook/gfx.c @@ -0,0 +1,147 @@ +#include + +#include +#include + +#include "gfxhook/gfx.h" + +#include "hook/table.h" + +#include "util/dprintf.h" + +/* Hook functions */ + +static BOOL WINAPI hook_ShowWindow(HWND hWnd, int nCmdShow); +static HWND WINAPI hook_CreateWindowExA( + DWORD dwExStyle, + LPCSTR lpClassName, + LPCSTR lpWindowName, + DWORD dwStyle, + int X, + int Y, + int nWidth, + int nHeight, + HWND hWndParent, + HMENU hMenu, + HINSTANCE hInstance, + LPVOID lpParam +); + +/* Link pointers */ + +static BOOL (WINAPI *next_ShowWindow)(HWND hWnd, int nCmdShow); +static HWND (WINAPI *next_CreateWindowExA)( + DWORD dwExStyle, + LPCSTR lpClassName, + LPCSTR lpWindowName, + DWORD dwStyle, + int X, + int Y, + int nWidth, + int nHeight, + HWND hWndParent, + HMENU hMenu, + HINSTANCE hInstance, + LPVOID lpParam +); + +static struct gfx_config gfx_config; + +static const struct hook_symbol gfx_hooks[] = { + { + .name = "ShowWindow", + .patch = hook_ShowWindow, + .link = (void **) &next_ShowWindow, + }, { + .name = "CreateWindowExA", + .patch = hook_CreateWindowExA, + .link = (void **) &next_CreateWindowExA, + }, +}; + +void gfx_hook_init(const struct gfx_config *cfg) +{ + assert(cfg != NULL); + + if (!cfg->enable) { + return; + } + + if (cfg->dpiAware) { + if (SetProcessDPIAware()) { + dprintf("Gfx: Game process set to DPI aware.\n"); + } else { + dprintf("Gfx: Failed to set process DPI aware\n"); + } + } + + memcpy(&gfx_config, cfg, sizeof(*cfg)); + hook_table_apply(NULL, "user32.dll", gfx_hooks, _countof(gfx_hooks)); +} + +static BOOL WINAPI hook_ShowWindow(HWND hWnd, int nCmdShow) +{ + dprintf("Gfx: ShowWindow hook hit\n"); + + if (!gfx_config.framed && nCmdShow == SW_RESTORE) { + nCmdShow = SW_SHOW; + } + + return next_ShowWindow(hWnd, nCmdShow); +} + +static HWND WINAPI hook_CreateWindowExA( + DWORD dwExStyle, + LPCSTR lpClassName, + LPCSTR lpWindowName, + DWORD dwStyle, + int X, + int Y, + int nWidth, + int nHeight, + HWND hWndParent, + HMENU hMenu, + HINSTANCE hInstance, + LPVOID lpParam +) +{ + RECT rect; + + dprintf("Gfx: CreateWindowExA hook hit\n"); + + if (gfx_config.windowed) + { + if (gfx_config.framed) + dwStyle |= WS_BORDER | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU; + else + dwStyle = WS_POPUP; + + rect.left = ((X == CW_USEDEFAULT) ? 0 : X); + rect.top = ((Y == CW_USEDEFAULT) ? 0 : Y); + rect.right = rect.left + nWidth; + rect.bottom = rect.top + nHeight; + + // Don't care if it's ok or not, since we are creating window and we can't just return a NULL + AdjustWindowRect(&rect, dwStyle, !!hMenu); + + X = ((X == CW_USEDEFAULT) ? X : rect.left); + Y = ((Y == CW_USEDEFAULT) ? Y : rect.top); + nWidth = rect.right - rect.left; + nHeight = rect.bottom - rect.top; + } + + return next_CreateWindowExA( + dwExStyle, + lpClassName, + lpWindowName, + dwStyle, + X, + Y, + nWidth, + nHeight, + hWndParent, + hMenu, + hInstance, + lpParam + ); +} diff --git a/gfxhook/gfx.h b/common/gfxhook/gfx.h similarity index 90% rename from gfxhook/gfx.h rename to common/gfxhook/gfx.h index 9a7e27c..6f3c1b2 100644 --- a/gfxhook/gfx.h +++ b/common/gfxhook/gfx.h @@ -7,6 +7,7 @@ struct gfx_config { bool windowed; bool framed; int monitor; + bool dpiAware; }; void gfx_hook_init(const struct gfx_config *cfg); diff --git a/common/gfxhook/gl.c b/common/gfxhook/gl.c new file mode 100644 index 0000000..a3f0496 --- /dev/null +++ b/common/gfxhook/gl.c @@ -0,0 +1,77 @@ +#include + +#include +#include + +#include "gfxhook/gfx.h" +#include "gfxhook/gl.h" + +#include "hook/table.h" + +#include "hooklib/dll.h" + +#include "util/dprintf.h" + +/* Hook functions */ + +static void WINAPI hook_glutFullScreen(void); +static void WINAPI hook_glutInitDisplayMode(unsigned int mode); + +/* Link pointers */ + +static void (WINAPI *next_glutFullScreen)(void); +static void (WINAPI *next_glutInitDisplayMode)(unsigned int mode); + +static struct gfx_config gfx_config; + +static const struct hook_symbol glut_hooks[] = { + { + .name = "glutFullScreen", + .patch = hook_glutFullScreen, + .link = (void **) &next_glutFullScreen, + }, { + .name = "glutInitDisplayMode", + .patch = hook_glutInitDisplayMode, + .link = (void **) &next_glutInitDisplayMode, + }, +}; + +void gfx_gl_hook_init(const struct gfx_config *cfg, HINSTANCE self) +{ + assert(cfg != NULL); + + if (!cfg->enable) { + return; + } + + memcpy(&gfx_config, cfg, sizeof(*cfg)); + hook_table_apply(NULL, "glut32.dll", glut_hooks, _countof(glut_hooks)); + + if (self != NULL) { + dll_hook_push(self, L"glut32.dll"); + } +} + +static void WINAPI hook_glutFullScreen(void) +{ + dprintf("Gfx: glutFullScreen hook hit\n"); + + if (gfx_config.windowed) { + return; + } + + next_glutFullScreen(); +} + +static void WINAPI hook_glutInitDisplayMode(unsigned int mode) +{ + dprintf("Gfx: glutInitDisplayMode hook hit\n"); + + // GLUT adds a frame when going windowed + if (gfx_config.windowed && !gfx_config.framed) { + // GLUT_BORDERLESS + mode |= 0x0800; + } + + next_glutInitDisplayMode(mode); +} diff --git a/common/gfxhook/gl.h b/common/gfxhook/gl.h new file mode 100644 index 0000000..033840d --- /dev/null +++ b/common/gfxhook/gl.h @@ -0,0 +1,3 @@ +#pragma once + +void gfx_gl_hook_init(const struct gfx_config *cfg, HINSTANCE self); diff --git a/gfxhook/meson.build b/common/gfxhook/meson.build similarity index 94% rename from gfxhook/meson.build rename to common/gfxhook/meson.build index b973ddd..4f7b1a5 100644 --- a/gfxhook/meson.build +++ b/common/gfxhook/meson.build @@ -2,7 +2,6 @@ gfxhook_lib = static_library( 'gfxhook', include_directories : inc, implicit_include_directories : false, - c_pch : '../precompiled.h', dependencies : [ capnhook.get_variable('hook_dep'), dxguid_lib, @@ -22,6 +21,8 @@ gfxhook_lib = static_library( 'dxgi.h', 'gfx.c', 'gfx.h', + 'gl.c', + 'gl.h', 'util.c', 'util.h', ], diff --git a/gfxhook/util.c b/common/gfxhook/util.c similarity index 100% rename from gfxhook/util.c rename to common/gfxhook/util.c diff --git a/gfxhook/util.h b/common/gfxhook/util.h similarity index 100% rename from gfxhook/util.h rename to common/gfxhook/util.h diff --git a/common/hooklib/config.c b/common/hooklib/config.c new file mode 100644 index 0000000..b7a84cf --- /dev/null +++ b/common/hooklib/config.c @@ -0,0 +1,86 @@ +#include + +#include +#include +#include +#include + +#include "hooklib/config.h" +#include "hooklib/dvd.h" + +void dvd_config_load(struct dvd_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"dvd", L"enable", 1, filename); +} + +void touch_screen_config_load(struct touch_screen_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"touch", L"enable", 1, filename); + cfg->remap = GetPrivateProfileIntW(L"touch", L"remap", 1, filename); + cfg->cursor = GetPrivateProfileIntW(L"touch", L"cursor", 1, filename); +} + +void printer_config_load(struct printer_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + wchar_t tmpstr[16]; + + cfg->enable = GetPrivateProfileIntW(L"printer", L"enable", 0, filename); + cfg->rotate_180 = GetPrivateProfileIntW(L"printer", L"rotate180", 0, filename); + + GetPrivateProfileStringW( + L"printer", + L"serial_no", + L"5A-A123", + tmpstr, + _countof(tmpstr), + filename); + + size_t n = wcstombs(cfg->serial_no, tmpstr, sizeof(cfg->serial_no)); + for (int i = n; i < sizeof(cfg->serial_no); i++) + { + cfg->serial_no[i] = '\0'; + } + + GetPrivateProfileStringW( + L"printer", + L"mainFwPath", + L"DEVICE\\printer_main_fw.bin", + cfg->main_fw_path, + _countof(cfg->main_fw_path), + filename); + + GetPrivateProfileStringW( + L"printer", + L"dspFwPath", + L"DEVICE\\printer_dsp_fw.bin", + cfg->dsp_fw_path, + _countof(cfg->dsp_fw_path), + filename); + + GetPrivateProfileStringW( + L"printer", + L"paramFwPath", + L"DEVICE\\printer_param_fw.bin", + cfg->param_fw_path, + _countof(cfg->param_fw_path), + filename); + + GetPrivateProfileStringW( + L"printer", + L"printerOutPath", + L"DEVICE\\print", + cfg->printer_out_path, + _countof(cfg->printer_out_path), + filename); + + cfg->wait_time = GetPrivateProfileIntW(L"printer", L"waitTime", 0, filename); +} diff --git a/common/hooklib/config.h b/common/hooklib/config.h new file mode 100644 index 0000000..9daed18 --- /dev/null +++ b/common/hooklib/config.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#include "hooklib/dvd.h" +#include "hooklib/touch.h" +#include "hooklib/printer.h" + +void dvd_config_load(struct dvd_config *cfg, const wchar_t *filename); +void touch_screen_config_load(struct touch_screen_config *cfg, const wchar_t *filename); +void printer_config_load(struct printer_config *cfg, const wchar_t *filename); diff --git a/common/hooklib/createprocess.c b/common/hooklib/createprocess.c new file mode 100644 index 0000000..6462394 --- /dev/null +++ b/common/hooklib/createprocess.c @@ -0,0 +1,258 @@ +#include + +#include +#include +#include +#include +#include + +#include "hook/table.h" + +#include "hooklib/createprocess.h" + +#include "util/dprintf.h" + +void createprocess_hook_init(); +static BOOL WINAPI my_CreateProcessA( + LPCSTR lpApplicationName, + LPSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + DWORD dwCreationFlags, + LPVOID lpEnvironment, + LPCSTR lpCurrentDirectory, + LPSTARTUPINFOA lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation +); +BOOL my_CreateProcessW( + LPCWSTR lpApplicationName, + LPWSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + DWORD dwCreationFlags, + LPVOID lpEnvironment, + LPCWSTR lpCurrentDirectory, + LPSTARTUPINFOW lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation +); + +static BOOL (WINAPI *next_CreateProcessA)( + LPCSTR lpApplicationName, + LPSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + DWORD dwCreationFlags, + LPVOID lpEnvironment, + LPCSTR lpCurrentDirectory, + LPSTARTUPINFOA lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation +); + +static BOOL (WINAPI *next_CreateProcessW)( + LPCWSTR lpApplicationName, + LPWSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + DWORD dwCreationFlags, + LPVOID lpEnvironment, + LPCWSTR lpCurrentDirectory, + LPSTARTUPINFOW lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation +); + +static const struct hook_symbol win32_hooks[] = { + { + .name = "CreateProcessA", + .patch = my_CreateProcessA, + .link = (void **) &next_CreateProcessA + }, + { + .name = "CreateProcessW", + .patch = my_CreateProcessW, + .link = (void **) &next_CreateProcessW + }, +}; + +static bool did_init = false; + +static struct process_hook_sym_w *process_syms_w; +static struct process_hook_sym_a *process_syms_a; + +static size_t process_nsyms_a = 0; +static size_t process_nsyms_w = 0; + +static CRITICAL_SECTION createproc_lock; + +HRESULT createprocess_push_hook_w(const wchar_t *name, const wchar_t *head, const wchar_t *tail, bool replace_all) { + struct process_hook_sym_w *new_mem; + struct process_hook_sym_w *new_proc; + HRESULT hr; + + assert(name != NULL); + assert(head != NULL); + + createprocess_hook_init(); + EnterCriticalSection(&createproc_lock); + + new_mem = realloc( + process_syms_w, + (process_nsyms_w + 1) * sizeof(struct process_hook_sym_w)); + + if (new_mem == NULL) { + + LeaveCriticalSection(&createproc_lock); + return E_OUTOFMEMORY; + } + + new_proc = &new_mem[process_nsyms_w]; + memset(new_proc, 0, sizeof(*new_proc)); + new_proc->name = name; + new_proc->head = head; + new_proc->tail = tail; + new_proc->replace_all = replace_all; + + process_syms_w = new_mem; + process_nsyms_w++; + + LeaveCriticalSection(&createproc_lock); + return S_OK; +} + +HRESULT createprocess_push_hook_a(const char *name, const char *head, const char *tail, bool replace_all) { + struct process_hook_sym_a *new_mem; + struct process_hook_sym_a *new_proc; + + assert(name != NULL); + assert(head != NULL); + + createprocess_hook_init(); + + EnterCriticalSection(&createproc_lock); + + new_mem = realloc( + process_syms_a, + (process_nsyms_a + 1) * sizeof(struct process_hook_sym_a)); + + if (new_mem == NULL) { + + LeaveCriticalSection(&createproc_lock); + return E_OUTOFMEMORY; + } + + new_proc = &new_mem[process_nsyms_a]; + memset(new_proc, 0, sizeof(*new_proc)); + new_proc->name = name; + new_proc->head = head; + new_proc->tail = tail; + new_proc->replace_all = replace_all; + + process_syms_a = new_mem; + process_nsyms_a++; + + LeaveCriticalSection(&createproc_lock); + return S_OK; +} + +void createprocess_hook_init() { + if (did_init) { + return; + } + did_init = true; + + hook_table_apply( + NULL, + "kernel32.dll", + win32_hooks, + _countof(win32_hooks)); + InitializeCriticalSection(&createproc_lock); + dprintf("CreateProcess: Init\n"); +} + + +static BOOL WINAPI my_CreateProcessA( + LPCSTR lpApplicationName, + LPSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + DWORD dwCreationFlags, + LPVOID lpEnvironment, + LPCSTR lpCurrentDirectory, + LPSTARTUPINFOA lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation +) +{ + for (int i = 0; i < process_nsyms_a; i++) { + if (strncmp(process_syms_a[i].name, lpCommandLine, strlen(process_syms_a[i].name))) { + continue; + } + + dprintf("CreateProcess: Hooking child process %s %s\n", lpApplicationName, lpCommandLine); + char new_cmd[MAX_PATH] = {0}; + strcat_s(new_cmd, MAX_PATH, process_syms_a[i].head); + + if (!process_syms_a[i].replace_all) { + strcat_s(new_cmd, MAX_PATH, lpCommandLine); + } + + if (process_syms_a[i].tail != NULL) { + strcat_s(new_cmd, MAX_PATH, process_syms_a[i].tail); + } + + dprintf("CreateProcess: Replaced CreateProcessA %s\n", new_cmd); + return next_CreateProcessA( + lpApplicationName, + new_cmd, + lpProcessAttributes, + lpThreadAttributes, + bInheritHandles, + dwCreationFlags, + lpEnvironment, + lpCurrentDirectory, + lpStartupInfo, + lpProcessInformation + ); + } + return next_CreateProcessA( + lpApplicationName, + lpCommandLine, + lpProcessAttributes, + lpThreadAttributes, + bInheritHandles, + dwCreationFlags, + lpEnvironment, + lpCurrentDirectory, + lpStartupInfo, + lpProcessInformation + ); +} + +BOOL my_CreateProcessW( + LPCWSTR lpApplicationName, + LPWSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + DWORD dwCreationFlags, + LPVOID lpEnvironment, + LPCWSTR lpCurrentDirectory, + LPSTARTUPINFOW lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation) +{ + return next_CreateProcessW( + lpApplicationName, + lpCommandLine, + lpProcessAttributes, + lpThreadAttributes, + bInheritHandles, + dwCreationFlags, + lpEnvironment, + lpCurrentDirectory, + lpStartupInfo, + lpProcessInformation + ); +} \ No newline at end of file diff --git a/common/hooklib/createprocess.h b/common/hooklib/createprocess.h new file mode 100644 index 0000000..bf226d5 --- /dev/null +++ b/common/hooklib/createprocess.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +HRESULT createprocess_push_hook_w(const wchar_t *name, const wchar_t *head, const wchar_t *tail, bool replace_all); +HRESULT createprocess_push_hook_a(const char *name, const char *head, const char *tail, bool replace_all); + +struct process_hook_sym_w { + const wchar_t *name; + const wchar_t *head; + const wchar_t *tail; + bool replace_all; +}; + +struct process_hook_sym_a { + const char *name; + const char *head; + const char *tail; + bool replace_all; +}; \ No newline at end of file diff --git a/common/hooklib/cursor.c b/common/hooklib/cursor.c new file mode 100644 index 0000000..7caba97 --- /dev/null +++ b/common/hooklib/cursor.c @@ -0,0 +1,80 @@ +#include + +#include +#include +#include +#include +#include + +#include "hook/table.h" +#include "util/dprintf.h" + +static HCURSOR my_SetCursor(HCURSOR hCursor); +static HCURSOR (*next_SetCursor)(HCURSOR hCursor); +static BOOL my_SetCursorPos(int x, int y); +static BOOL my_SetPhysicalCursorPos(int x, int y); +static int my_ShowCursor(BOOL bShow); +static int cursor_track = -1; // If no mouse is connected, this starts as -1 + +static const struct hook_symbol cursor_syms[] = { + { + .name = "SetCursor", + .patch = my_SetCursor, + .link = (void **) &next_SetCursor + },/*{ + .name = "SetCursorPos", + .patch = my_SetCursorPos, + },*/ { + .name = "SetPhysicalCursorPos", + .patch = my_SetPhysicalCursorPos + }, /*{ + .name = "ShowCursor", + .patch = my_ShowCursor + }*/ +}; + +void cursor_hook_init() +{ + hook_table_apply( + NULL, + "user32.dll", + cursor_syms, + _countof(cursor_syms)); + + dprintf("Cursor: Init\n"); +} + +static BOOL my_SetCursorPos(int x, int y) +{ + // dprintf("my_SetCursorPos Hit! x %d y %d\n", x, y); + return true; +} + +static BOOL my_SetPhysicalCursorPos(int x, int y) +{ + dprintf("my_SetPhysicalCursorPos Hit! x %d y %d\n", x, y); + return true; +} + +static int my_ShowCursor(BOOL bShow) +{ + dprintf("my_ShowCursor Hit!\n"); + if (bShow) { + cursor_track++; + } else { + cursor_track--; + } + return cursor_track; +} + +static HCURSOR my_SetCursor(HCURSOR hCursor) +{ + dprintf("my_SetCursor Hit!\n"); + HCURSOR fake_cursor; + + if ( hCursor ) + return next_SetCursor(hCursor); + fake_cursor = LoadCursorA(0, (LPCSTR)0x7F00); + next_SetCursor(fake_cursor); + return 0; +} diff --git a/common/hooklib/cursor.h b/common/hooklib/cursor.h new file mode 100644 index 0000000..de0461a --- /dev/null +++ b/common/hooklib/cursor.h @@ -0,0 +1,3 @@ +#pragma once + +void cursor_hook_init(); \ No newline at end of file diff --git a/hooklib/dll.c b/common/hooklib/dll.c similarity index 100% rename from hooklib/dll.c rename to common/hooklib/dll.c diff --git a/hooklib/dll.h b/common/hooklib/dll.h similarity index 100% rename from hooklib/dll.h rename to common/hooklib/dll.h diff --git a/hooklib/dns.c b/common/hooklib/dns.c similarity index 51% rename from hooklib/dns.c rename to common/hooklib/dns.c index aac90ce..9e06e5b 100644 --- a/hooklib/dns.c +++ b/common/hooklib/dns.c @@ -3,15 +3,20 @@ #include #include #include +#include #include #include #include +#include #include #include "hook/hr.h" #include "hook/table.h" +#include "util/dprintf.h" +#include "util/get_function_ordinal.h" + #include "hooklib/dns.h" /* Latest w32headers does not include DnsQueryEx, so we'll have to "polyfill" @@ -66,6 +71,29 @@ static int WSAAPI hook_getaddrinfo( const ADDRINFOA *pHints, ADDRINFOA **ppResult); +static HINTERNET WINAPI hook_WinHttpConnect( + HINTERNET hSession, + const wchar_t *pwszServerName, + INTERNET_PORT nServerPort, + DWORD dwReserved); + +static bool WINAPI hook_WinHttpCrackUrl( + const wchar_t *pwszUrl, + DWORD dwUrlLength, + DWORD dwFlags, + LPURL_COMPONENTS lpUrlComponents); + +static DWORD WINAPI hook_send( + SOCKET s, + const char* buf, + int len, + int flags); + +static int WINAPI hook_connect( + SOCKET s, + const struct sockaddr *name, + int namelen); + /* Link pointers */ static DNS_STATUS (WINAPI *next_DnsQuery_A)( @@ -95,6 +123,29 @@ static int (WSAAPI *next_getaddrinfo)( const ADDRINFOA *pHints, ADDRINFOA **ppResult); +static HINTERNET (WINAPI *next_WinHttpConnect)( + HINTERNET hSession, + const wchar_t *pwszServerName, + INTERNET_PORT nServerPort, + DWORD dwReserved); + +static bool (WINAPI *next_WinHttpCrackUrl)( + const wchar_t *pwszUrl, + DWORD dwUrlLength, + DWORD dwFlags, + LPURL_COMPONENTS lpUrlComponents); + +static DWORD (WINAPI *next_send)( + SOCKET s, + const char* buf, + int len, + int flags); + +static int (__stdcall *next_connect)( + SOCKET s, + const struct sockaddr *name, + int namelen); + static const struct hook_symbol dns_hook_syms_dnsapi[] = { { .name = "DnsQuery_A", @@ -117,13 +168,45 @@ static const struct hook_symbol dns_hook_syms_ws2[] = { .ordinal = 176, .patch = hook_getaddrinfo, .link = (void **) &next_getaddrinfo, + }, +}; + +static const struct hook_symbol dns_hook_syms_winhttp[] = { + { + .name = "WinHttpConnect", + .patch = hook_WinHttpConnect, + .link = (void **) &next_WinHttpConnect, + }, { + .name = "WinHttpCrackUrl", + .patch = hook_WinHttpCrackUrl, + .link = (void **) &next_WinHttpCrackUrl, } }; +static struct hook_symbol http_hook_syms_ws2[] = { + { + .name = "send", + .patch = hook_send, + .link = (void **) &next_send + }, +}; + +static struct hook_symbol port_hook_syms_ws2[] = { + { + .name = "connect", + .patch = hook_connect, + .link = (void **) &next_connect + }, +}; + static bool dns_hook_initted; static CRITICAL_SECTION dns_hook_lock; static struct dns_hook_entry *dns_hook_entries; static size_t dns_hook_nentries; +static char received_title_url[255]; +static unsigned short startup_port; +static unsigned short billing_port; +static unsigned short aimedb_port; static void dns_hook_init(void) { @@ -134,17 +217,85 @@ static void dns_hook_init(void) dns_hook_initted = true; InitializeCriticalSection(&dns_hook_lock); + dns_hook_apply_hooks(NULL); +} + +void dns_hook_apply_hooks(HMODULE mod){ hook_table_apply( - NULL, + mod, "dnsapi.dll", dns_hook_syms_dnsapi, _countof(dns_hook_syms_dnsapi)); hook_table_apply( - NULL, + mod, "ws2_32.dll", dns_hook_syms_ws2, _countof(dns_hook_syms_ws2)); + + hook_table_apply( + mod, + "winhttp.dll", + dns_hook_syms_winhttp, + _countof(dns_hook_syms_winhttp)); +} + +void http_hook_init(){ + for (size_t i = 0; i < _countof(http_hook_syms_ws2); ++i) { + http_hook_syms_ws2[i].ordinal = get_function_ordinal("ws2_32.dll", http_hook_syms_ws2[i].name); + } + + hook_table_apply( + NULL, + "ws2_32.dll", + http_hook_syms_ws2, + _countof(http_hook_syms_ws2)); +} + +void port_hook_init(unsigned short _startup_port, unsigned short _billing_port, unsigned short _aimedb_port){ + startup_port = _startup_port; + billing_port = _billing_port; + aimedb_port = _aimedb_port; + for (size_t i = 0; i < _countof(port_hook_syms_ws2); ++i) { + port_hook_syms_ws2[i].ordinal = get_function_ordinal("ws2_32.dll", port_hook_syms_ws2[i].name); + } + + hook_table_apply( + NULL, + "ws2_32.dll", + port_hook_syms_ws2, + _countof(port_hook_syms_ws2)); +} + +// This function match domain and subdomains like *.naominet.jp. +bool match_domain(const wchar_t* target, const wchar_t* pattern) { + if (_wcsicmp(pattern, target) == 0) { + return true; + } + + int pattern_ptr_index = 0; + int target_ptr_index = 0; + + while (pattern[pattern_ptr_index] != '\0' && target[target_ptr_index] != '\0') { + if (pattern[pattern_ptr_index] == '*') { + pattern_ptr_index++; // Check next character for wildcard match. + + while (pattern[pattern_ptr_index] != target[target_ptr_index]) { + target_ptr_index++; + + if (target[target_ptr_index] == '\0') return false; + } + } + else if (pattern[pattern_ptr_index] != target[target_ptr_index]) { + return false; + } + else { + pattern_ptr_index++; + target_ptr_index++; + } + } + + return pattern[pattern_ptr_index] == '\0' && target[target_ptr_index] == '\0'; } HRESULT dns_hook_push(const wchar_t *from_src, const wchar_t *to_src) @@ -171,7 +322,7 @@ HRESULT dns_hook_push(const wchar_t *from_src, const wchar_t *to_src) goto end; } - if(to_src != NULL) { + if (to_src != NULL) { to = _wcsdup(to_src); if (to == NULL) { @@ -250,8 +401,8 @@ static DNS_STATUS WINAPI hook_DnsQuery_A( for (i = 0 ; i < dns_hook_nentries ; i++) { pos = &dns_hook_entries[i]; - if (_wcsicmp(wstr, pos->from) == 0) { - if(pos->to == NULL) { + if (match_domain(wstr, pos->from)) { + if (pos->to == NULL) { LeaveCriticalSection(&dns_hook_lock); hr = HRESULT_FROM_WIN32(DNS_ERROR_RCODE_NAME_ERROR); @@ -314,8 +465,8 @@ static DNS_STATUS WINAPI hook_DnsQuery_W( for (i = 0 ; i < dns_hook_nentries ; i++) { pos = &dns_hook_entries[i]; - if (_wcsicmp(pszName, pos->from) == 0) { - if(pos->to == NULL) { + if (match_domain(pszName, pos->from)) { + if (pos->to == NULL) { LeaveCriticalSection(&dns_hook_lock); return HRESULT_FROM_WIN32(DNS_ERROR_RCODE_NAME_ERROR); } @@ -358,8 +509,8 @@ static DNS_STATUS WINAPI hook_DnsQueryEx( for (i = 0 ; i < dns_hook_nentries ; i++) { pos = &dns_hook_entries[i]; - if (_wcsicmp(pRequest->QueryName, pos->from) == 0) { - if(pos->to == NULL) { + if (match_domain(pRequest->QueryName, pos->from)) { + if (pos->to == NULL) { LeaveCriticalSection(&dns_hook_lock); return HRESULT_FROM_WIN32(DNS_ERROR_RCODE_NAME_ERROR); } @@ -425,8 +576,8 @@ static int WSAAPI hook_getaddrinfo( for (i = 0 ; i < dns_hook_nentries ; i++) { pos = &dns_hook_entries[i]; - if (_wcsicmp(wstr, pos->from) == 0) { - if(pos->to == NULL) { + if (match_domain(wstr, pos->from)) { + if (pos->to == NULL) { LeaveCriticalSection(&dns_hook_lock); result = EAI_NONAME; @@ -460,3 +611,173 @@ end: return result; } + +static HINTERNET WINAPI hook_WinHttpConnect( + HINTERNET hSession, + const wchar_t *pwszServerName, + INTERNET_PORT nServerPort, + DWORD dwReserved) +{ + const struct dns_hook_entry *pos; + size_t i; + + if (pwszServerName == NULL) { + return NULL; + } + + EnterCriticalSection(&dns_hook_lock); + + for (i = 0 ; i < dns_hook_nentries ; i++) { + pos = &dns_hook_entries[i]; + + if (match_domain(pwszServerName, pos->from)) { + if (pos->to == NULL) { + LeaveCriticalSection(&dns_hook_lock); + return NULL; + } + + pwszServerName = pos->to; + + break; + } + } + + LeaveCriticalSection(&dns_hook_lock); + + return next_WinHttpConnect(hSession, pwszServerName, nServerPort, dwReserved); +} + +// Hook to replace CXB title url +static bool WINAPI hook_WinHttpCrackUrl( + const wchar_t *pwszUrl, + DWORD dwUrlLength, + DWORD dwFlags, + LPURL_COMPONENTS lpUrlComponents) +{ + const struct dns_hook_entry *pos; + size_t i; + + EnterCriticalSection(&dns_hook_lock); + + for (i = 0 ; i < dns_hook_nentries ; i++) { + pos = &dns_hook_entries[i]; + + if (match_domain(pwszUrl, pos->from)) { + wchar_t* toAddr = pos->to; + wchar_t titleBuffer[255]; + + if (wcscmp(toAddr, L"title") == 0) { + size_t wstr_c; + mbstowcs_s(&wstr_c, titleBuffer, 255, received_title_url, strlen(received_title_url)); + toAddr = titleBuffer; + } + + bool result = next_WinHttpCrackUrl( + toAddr, + wcslen(toAddr), + dwFlags, + lpUrlComponents + ); + LeaveCriticalSection(&dns_hook_lock); + return result; + } + } + + LeaveCriticalSection(&dns_hook_lock); + return next_WinHttpCrackUrl( + pwszUrl, + dwUrlLength, + dwFlags, + lpUrlComponents + ); +} + +int WINAPI hook_connect(SOCKET s, const struct sockaddr *name, int namelen) { + const struct sockaddr_in *n; + struct sockaddr_in new_name; + unsigned ip; + unsigned short port, new_port; + + EnterCriticalSection(&dns_hook_lock); + + n = (const struct sockaddr_in *)name; + ip = n->sin_addr.S_un.S_addr; + if (WSANtohs(s, n->sin_port, &port)) return SOCKET_ERROR; + + if (port == 80 && startup_port) { + new_port = startup_port; + } else if (port == 8443 && billing_port) { + new_port = billing_port; + } else if (port == 22345 && aimedb_port) { + new_port = aimedb_port; + } else { // No match + dprintf("TCP Connect: %u.%u.%u.%u:%hu\n", ip & 0xff, (ip >> 8) & 0xff, (ip >> 16) & 0xff, (ip >> 24) & 0xff, port); + + LeaveCriticalSection(&dns_hook_lock); + return next_connect( + s, + name, + namelen + ); + } + + // matched + new_name = *n; + if (WSAHtons(s, new_port, &new_name.sin_port)) return SOCKET_ERROR; + + dprintf("TCP Connect: %u.%u.%u.%u:%hu, mapped to port %hu\n", ip & 0xff, (ip >> 8) & 0xff, (ip >> 16) & 0xff, (ip >> 24) & 0xff, port, new_port); + + LeaveCriticalSection(&dns_hook_lock); + return next_connect( + s, + (const struct sockaddr *)&new_name, + sizeof(new_name) + ); +} + +DWORD WINAPI hook_send(SOCKET s, const char* buf, int len, int flags) { + if (strstr(buf, "HTTP/") != NULL) { + char *new_buf = malloc(len + 1); + if (new_buf == NULL) return SOCKET_ERROR; + + memcpy(new_buf, buf, len); + new_buf[len] = '\0'; + + char *host_start = strstr(new_buf, "Host: "); + if (host_start != NULL) { + char *host_end = strstr(host_start, "\r\n"); + if (host_end != NULL) { + host_end += 2; + int host_len = host_end - host_start; + + char *host_value_start = host_start + 6; + char *host_value_end = strstr(host_value_start, "\r\n"); + if (host_value_end != NULL) { + int value_len = host_value_end - host_value_start; + char* host_value = (char*)malloc(value_len + 1); + strncpy(host_value, host_value_start, value_len); + host_value[value_len] = '\0'; + + for (struct dns_hook_entry *entry = dns_hook_entries; entry && entry->from; entry++) { + char from_value[256]; + wcstombs(from_value, entry->from, sizeof(from_value)); + + if (strcmp(host_value, from_value) == 0) { + char to_value[256]; + wcstombs(to_value, entry->to, sizeof(to_value)); + snprintf(host_start, len - (host_start - new_buf), "Host: %s\r\n", to_value); + break; + } + } + free(host_value); + } + len = (int)strlen(new_buf); + } + } + DWORD result = next_send(s, new_buf, len, flags); + free(new_buf); + return result; + } + + return next_send(s, buf, len, flags); +} \ No newline at end of file diff --git a/hooklib/dns.h b/common/hooklib/dns.h similarity index 51% rename from hooklib/dns.h rename to common/hooklib/dns.h index 1f93b0f..b03f7dc 100644 --- a/hooklib/dns.h +++ b/common/hooklib/dns.h @@ -3,7 +3,9 @@ #include #include - +void http_hook_init(); +void port_hook_init(unsigned short _startup_port, unsigned short _billing_port, unsigned short _aimedb_port); // if to_src is NULL, all lookups for from_src will fail HRESULT dns_hook_push(const wchar_t *from_src, const wchar_t *to_src); +void dns_hook_apply_hooks(HMODULE mod); diff --git a/hooklib/dvd.c b/common/hooklib/dvd.c similarity index 100% rename from hooklib/dvd.c rename to common/hooklib/dvd.c diff --git a/hooklib/dvd.h b/common/hooklib/dvd.h similarity index 100% rename from hooklib/dvd.h rename to common/hooklib/dvd.h diff --git a/hooklib/fdshark.c b/common/hooklib/fdshark.c similarity index 100% rename from hooklib/fdshark.c rename to common/hooklib/fdshark.c diff --git a/hooklib/fdshark.h b/common/hooklib/fdshark.h similarity index 100% rename from hooklib/fdshark.h rename to common/hooklib/fdshark.h diff --git a/hooklib/meson.build b/common/hooklib/meson.build similarity index 74% rename from hooklib/meson.build rename to common/hooklib/meson.build index d112da6..b0ad6c2 100644 --- a/hooklib/meson.build +++ b/common/hooklib/meson.build @@ -2,13 +2,17 @@ hooklib_lib = static_library( 'hooklib', include_directories : inc, implicit_include_directories : false, - c_pch : '../precompiled.h', dependencies : [ capnhook.get_variable('hook_dep'), + ws2_32_lib ], sources : [ + 'cursor.c', + 'cursor.h', 'config.c', 'config.h', + 'createprocess.c', + 'createprocess.h', 'dll.c', 'dll.h', 'dns.c', @@ -19,13 +23,15 @@ hooklib_lib = static_library( 'fdshark.h', 'path.c', 'path.h', - 'procaddr.c', - 'procaddr.h', 'reg.c', 'reg.h', 'setupapi.c', 'setupapi.h', 'spike.c', 'spike.h', + 'touch.c', + 'touch.h', + 'printer.c', + 'printer.h', ], ) diff --git a/hooklib/path.c b/common/hooklib/path.c similarity index 71% rename from hooklib/path.c rename to common/hooklib/path.c index 0c6fe42..1afb8a8 100644 --- a/hooklib/path.c +++ b/common/hooklib/path.c @@ -11,6 +11,8 @@ #include "hooklib/path.h" +#include + /* Helpers */ static void path_hook_init(void); @@ -97,6 +99,44 @@ static BOOL WINAPI hook_RemoveDirectoryA(const char *lpFileName); static BOOL WINAPI hook_RemoveDirectoryW(const wchar_t *lpFileName); +static BOOL WINAPI hook_PathFileExistsA(LPCSTR pszPath); + +static BOOL WINAPI hook_PathFileExistsW(LPCWSTR pszPath); + +static BOOL WINAPI hook_MoveFileA( + const char *lpExistingFileName, + const char *lpNewFileName); + +static BOOL WINAPI hook_MoveFileW( + const wchar_t *lpExistingFileName, + const wchar_t *lpNewFileName); + +static BOOL WINAPI hook_MoveFileExA( + const char *lpExistingFileName, + const char *lpNewFileName, + uint32_t dwFlags); + + +static BOOL WINAPI hook_ReplaceFileA( + const char *lpReplacedFileName, + const char *lpReplacementFileName, + const char *lpBackupFileName, + uint32_t dwReplaceFlags, + void *lpExclude, + void *lpReserved); + +static BOOL WINAPI hook_ReplaceFileW( + const wchar_t *lpReplacedFileName, + const wchar_t *lpReplacementFileName, + const wchar_t *lpBackupFileName, + uint32_t dwReplaceFlags, + void *lpExclude, + void *lpReserved); + +static BOOL WINAPI hook_DeleteFileA(const char *lpFileName); + +static BOOL WINAPI hook_DeleteFileW(const wchar_t *lpFileName); + /* Link pointers */ static BOOL (WINAPI *next_CreateDirectoryA)( @@ -177,6 +217,43 @@ static BOOL (WINAPI *next_RemoveDirectoryA)(const char *lpFileName); static BOOL (WINAPI *next_RemoveDirectoryW)(const wchar_t *lpFileName); +static BOOL (WINAPI *next_PathFileExistsA)(LPCSTR pszPath); + +static BOOL (WINAPI *next_PathFileExistsW)(LPCWSTR pszPath); + +static BOOL (WINAPI *next_MoveFileA)( + const char *lpExistingFileName, + const char *lpNewFileName); + +static BOOL (WINAPI *next_MoveFileW)( + const wchar_t *lpExistingFileName, + const wchar_t *lpNewFileName); + +static BOOL (WINAPI *next_MoveFileExA)( + const char *lpExistingFileName, + const char *lpNewFileName, + uint32_t dwFlags); + +static BOOL (WINAPI *next_ReplaceFileA)( + const char *lpReplacedFileName, + const char *lpReplacementFileName, + const char *lpBackupFileName, + uint32_t dwReplaceFlags, + void *lpExclude, + void *lpReserved); + +static BOOL (WINAPI *next_ReplaceFileW)( + const wchar_t *lpReplacedFileName, + const wchar_t *lpReplacementFileName, + const wchar_t *lpBackupFileName, + uint32_t dwReplaceFlags, + void *lpExclude, + void *lpReserved); + +static BOOL (WINAPI *next_DeleteFileA)(const char *lpFileName); + +static BOOL (WINAPI *next_DeleteFileW)(const wchar_t *lpFileName); + /* Hook table */ static const struct hook_symbol path_hook_syms[] = { @@ -244,6 +321,42 @@ static const struct hook_symbol path_hook_syms[] = { .name = "RemoveDirectoryW", .patch = hook_RemoveDirectoryW, .link = (void **) &next_RemoveDirectoryW, + }, { + .name = "PathFileExistsA", + .patch = hook_PathFileExistsA, + .link = (void **) &next_PathFileExistsA, + }, { + .name = "PathFileExistsW", + .patch = hook_PathFileExistsW, + .link = (void **) &next_PathFileExistsW, + }, { + .name = "MoveFileA", + .patch = hook_MoveFileA, + .link = (void **) &next_MoveFileA, + }, { + .name = "MoveFileW", + .patch = hook_MoveFileW, + .link = (void **) &next_MoveFileW, + }, { + .name = "MoveFileExA", + .patch = hook_MoveFileExA, + .link = (void **) &next_MoveFileExA, + }, { + .name = "ReplaceFileA", + .patch = hook_ReplaceFileA, + .link = (void **) &next_ReplaceFileA, + }, { + .name = "ReplaceFileW", + .patch = hook_ReplaceFileW, + .link = (void **) &next_ReplaceFileW, + }, { + .name = "DeleteFileA", + .patch = hook_DeleteFileA, + .link = (void **) &next_DeleteFileA, + }, { + .name = "DeleteFileW", + .patch = hook_DeleteFileW, + .link = (void **) &next_DeleteFileW, } }; @@ -422,6 +535,12 @@ static BOOL path_transform_w(wchar_t **out, const wchar_t *src) goto end; } +#if LOG_VFS + if (!wcsstr(src, L"AppUser")) { + dprintf("Path: %ls -> %ls\n", src, dest); + } +#endif + break; } @@ -854,3 +973,249 @@ static BOOL WINAPI hook_RemoveDirectoryW(const wchar_t *lpFileName) return ok; } + +static BOOL WINAPI hook_PathFileExistsA(LPCSTR pszPath) +{ + char *trans; + BOOL ok; + + ok = path_transform_a(&trans, pszPath); + + if (!ok) { + return FALSE; + } + + ok = next_PathFileExistsA(trans ? trans : pszPath); + + free(trans); + + return ok; +} + +static BOOL WINAPI hook_PathFileExistsW(LPCWSTR pszPath) +{ + wchar_t *trans; + BOOL ok; + + ok = path_transform_w(&trans, pszPath); + + if (!ok) { + return FALSE; + } + + ok = next_PathFileExistsW(trans ? trans : pszPath); + + free(trans); + + return ok; +} + +static BOOL WINAPI hook_MoveFileA( + const char *lpExistingFileName, + const char *lpNewFileName) +{ + char *oldTrans; + char *newTrans; + BOOL ok; + + ok = path_transform_a(&oldTrans, lpExistingFileName); + + if (!ok) { + return FALSE; + } + + ok = path_transform_a(&newTrans, lpNewFileName); + + if (!ok) { + free(oldTrans); + + return FALSE; + } + + ok = next_MoveFileA( + oldTrans ? oldTrans : lpExistingFileName, + newTrans ? newTrans : lpNewFileName); + + free(oldTrans); + free(newTrans); + + return ok; +} + +static BOOL WINAPI hook_MoveFileW( + const wchar_t *lpExistingFileName, + const wchar_t *lpNewFileName) +{ + wchar_t *oldTrans; + wchar_t *newTrans; + BOOL ok; + + ok = path_transform_w(&oldTrans, lpExistingFileName); + + if (!ok) { + return FALSE; + } + + ok = path_transform_w(&newTrans, lpNewFileName); + + if (!ok) { + free(oldTrans); + + return FALSE; + } + + ok = next_MoveFileW( + oldTrans ? oldTrans : lpExistingFileName, + newTrans ? newTrans : lpNewFileName); + + free(oldTrans); + free(newTrans); + + return ok; +} + +static BOOL WINAPI hook_MoveFileExA( + const char *lpExistingFileName, + const char *lpNewFileName, + uint32_t dwFlags) +{ + char *oldTrans; + char *newTrans; + BOOL ok; + + ok = path_transform_a(&oldTrans, lpExistingFileName); + + if (!ok) { + return FALSE; + } + + ok = path_transform_a(&newTrans, lpNewFileName); + + if (!ok) { + free(oldTrans); + + return FALSE; + } + + ok = next_MoveFileExA( + oldTrans ? oldTrans : lpExistingFileName, + newTrans ? newTrans : lpNewFileName, + dwFlags); + + free(oldTrans); + free(newTrans); + + return ok; +} + +static BOOL WINAPI hook_ReplaceFileA( + const char *lpReplacedFileName, + const char *lpReplacementFileName, + const char *lpBackupFileName, + uint32_t dwReplaceFlags, + void *lpExclude, + void *lpReserved) +{ + char *oldTrans; + char *newTrans; + BOOL ok; + + ok = path_transform_a(&oldTrans, lpReplacedFileName); + + if (!ok) { + return FALSE; + } + + ok = path_transform_a(&newTrans, lpReplacementFileName); + + if (!ok) { + free(oldTrans); + + return FALSE; + } + + ok = next_ReplaceFileA( + oldTrans ? oldTrans : lpReplacedFileName, + newTrans ? newTrans : lpReplacementFileName, + lpBackupFileName, + dwReplaceFlags, + lpExclude, + lpReserved); + + free(oldTrans); + free(newTrans); + + return ok; +} + +static BOOL WINAPI hook_ReplaceFileW( + const wchar_t *lpReplacedFileName, + const wchar_t *lpReplacementFileName, + const wchar_t *lpBackupFileName, + uint32_t dwReplaceFlags, + void *lpExclude, + void *lpReserved) +{ + wchar_t *oldTrans; + wchar_t *newTrans; + BOOL ok; + + ok = path_transform_w(&oldTrans, lpReplacedFileName); + + if (!ok) { + return FALSE; + } + + ok = path_transform_w(&newTrans, lpReplacementFileName); + + if (!ok) { + free(oldTrans); + + return FALSE; + } + + ok = next_ReplaceFileW( + oldTrans ? oldTrans : lpReplacedFileName, + newTrans ? newTrans : lpReplacementFileName, + lpBackupFileName, + dwReplaceFlags, + lpExclude, + lpReserved); + + free(oldTrans); + free(newTrans); + + return ok; +} + +static BOOL WINAPI hook_DeleteFileA(const char *lpFileName) +{ + char *trans; + BOOL ok; + + ok = path_transform_a(&trans, lpFileName); + + if (!ok) { + return FALSE; + } + + ok = next_DeleteFileA(trans ? trans: lpFileName); + + return ok; +} + +static BOOL WINAPI hook_DeleteFileW(const wchar_t *lpFileName) +{ + wchar_t *trans; + BOOL ok; + + ok = path_transform_w(&trans, lpFileName); + + if (!ok) { + return FALSE; + } + + ok = next_DeleteFileW(trans ? trans: lpFileName); + + return ok; +} diff --git a/hooklib/path.h b/common/hooklib/path.h similarity index 100% rename from hooklib/path.h rename to common/hooklib/path.h diff --git a/common/hooklib/printer.c b/common/hooklib/printer.c new file mode 100644 index 0000000..dbd56f9 --- /dev/null +++ b/common/hooklib/printer.c @@ -0,0 +1,3354 @@ +/* + Sinfonia CHC-C3XX Printer emulator + + Credits: + + c310emu (rakisaionji) + segatools-kancolle (OLEG) + c310emu (doremi) + chc (emihiok) +*/ + +#include "hooklib/printer.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "hook/table.h" +#include "hook/procaddr.h" +#include "hooklib/dll.h" +#include "hooklib/uart.h" +#include "util/dprintf.h" +#include "util/dump.h" + +// #define IMAGE_SIZE 0x24FC00 +#define IMAGE_SIZE 0x1F0800 +#define HOLO_SIZE 0xC5400 + +static const int8_t idNumber = 0x01; +static uint64_t serialNo = 0x333231412D4135; + +static uint16_t WIDTH = 0; +static uint16_t HEIGHT = 0; + +static bool rotate180 = false; +static uint8_t cardRFID[0xC] = {0}; +static uint8_t cardDataBuffer[0x20] = {0}; +static bool awaitingCardExit = false; +static wchar_t printer_out_path[MAX_PATH]; + +/* Firmware data */ + +static uint8_t mainFirmware[0x40] = {0}; +static uint8_t dspFirmware[0x40] = {0}; +static uint8_t paramFirmware[0x40] = {0}; + +/* printerInfo variables */ + +static int32_t PAPERINFO[10]; +static int32_t CURVE[3][3]; +static uint8_t POLISH[2]; +static int32_t MTF[9]; + +/* Printer status */ + +static uint8_t STATUS = 0; +static ULONGLONG finishTime = 0; + +/* C3XXFWDLusb API hooks */ + +int WINAPI fwdlusb_open(uint16_t *rResult); +void WINAPI fwdlusb_close(); +int WINAPI fwdlusb_listupPrinter(uint8_t *rIdArray); +int WINAPI fwdlusb_listupPrinterSN(uint64_t *rSerialArray); +int WINAPI fwdlusb_selectPrinter(uint8_t printerId, uint16_t *rResult); +int WINAPI fwdlusb_selectPrinterSN(uint64_t printerSN, uint16_t *rResult); +int WINAPI fwdlusb_getPrinterInfo(uint16_t tagNumber, uint8_t *rBuffer, uint32_t *rLen); +int WINAPI fwdlusb_status(uint16_t *rResult); +int WINAPI fwdlusb_statusAll(uint8_t *idArray, uint16_t *rResultArray); +int WINAPI fwdlusb_resetPrinter(uint16_t *rResult); +int WINAPI fwdlusb_updateFirmware(uint8_t update, LPCSTR filename, uint16_t *rResult); +int WINAPI fwdlusb_getFirmwareInfo(uint8_t update, LPCSTR filename, uint8_t *rBuffer, uint32_t *rLen, uint16_t *rResult); +int WINAPI fwdlusb_MakeThread(uint16_t maxCount); +int WINAPI fwdlusb_ReleaseThread(uint16_t *rResult); +int WINAPI fwdlusb_AttachThreadCount(uint16_t *rCount, uint16_t *rMaxCount); +int WINAPI fwdlusb_getErrorLog(uint16_t index, uint8_t *rData, uint16_t *rResult); + +/* C3XXusb API hooks */ + +int WINAPI chcusb_MakeThread(uint16_t maxCount); +int WINAPI chcusb_open(uint16_t *rResult); +void WINAPI chcusb_close(); +int WINAPI chcusb_ReleaseThread(uint16_t *rResult); +int WINAPI chcusb_listupPrinter(uint8_t *rIdArray); +int WINAPI chcusb_listupPrinterSN(uint64_t *rSerialArray); +int WINAPI chcusb_selectPrinter(uint8_t printerId, uint16_t *rResult); +int WINAPI chcusb_selectPrinterSN(uint64_t printerSN, uint16_t *rResult); +int WINAPI chcusb_getPrinterInfo(uint16_t tagNumber, uint8_t *rBuffer, uint32_t *rLen); +int WINAPI chcusb_imageformat(uint16_t format, uint16_t ncomp, uint16_t depth, uint16_t width, uint16_t height, uint8_t * image, uint16_t* rResult); +int WINAPI chcusb_imageformat_330( + uint16_t format, + uint16_t ncomp, + uint16_t depth, + uint16_t width, + uint16_t height, + uint16_t *rResult); +int __fastcall chcusb_setmtf(int32_t *mtf); +int WINAPI chcusb_makeGamma(uint16_t k, uint8_t *intoneR, uint8_t *intoneG, uint8_t *intoneB); +int WINAPI chcusb_setIcctable( + LPCSTR icc1, + LPCSTR icc2, + uint16_t intents, + uint8_t *intoneR, + uint8_t *intoneG, + uint8_t *intoneB, + uint8_t *outtoneR, + uint8_t *outtoneG, + uint8_t *outtoneB, + uint16_t *rResult); +int WINAPI chcusb_copies(uint16_t copies, uint16_t *rResult); +int WINAPI chcusb_status(uint16_t *rResult); +int WINAPI chcusb_statusAll(uint8_t *idArray, uint16_t *rResultArray); +int WINAPI chcusb_startpage(uint16_t postCardState, uint16_t *pageId, uint16_t *rResult); +int WINAPI chcusb_startpage_300(uint16_t postCardState, uint16_t *rResult); +int WINAPI chcusb_endpage(uint16_t *rResult); +int WINAPI chcusb_write(uint8_t *data, uint32_t *writeSize, uint16_t *rResult); +int WINAPI chcusb_writeLaminate(uint8_t *data, uint32_t *writeSize, uint16_t *rResult); +int WINAPI chcusb_writeHolo(uint8_t *data, uint32_t *writeSize, uint16_t *rResult); +int WINAPI chcusb_setPrinterInfo(uint16_t tagNumber, uint8_t *rBuffer, uint32_t *rLen, uint16_t *rResult); +int WINAPI chcusb_getGamma(LPCSTR filename, uint8_t *r, uint8_t *g, uint8_t *b, uint16_t *rResult); +int WINAPI chcusb_getMtf(LPCSTR filename, int32_t *mtf, uint16_t *rResult); +int WINAPI chcusb_cancelCopies(uint16_t pageId, uint16_t *rResult); +int WINAPI chcusb_setPrinterToneCurve(uint16_t type, uint16_t number, uint16_t *data, uint16_t *rResult); +int WINAPI chcusb_getPrinterToneCurve(uint16_t type, uint16_t number, uint16_t *data, uint16_t *rResult); +int WINAPI chcusb_blinkLED(uint16_t *rResult); +int WINAPI chcusb_resetPrinter(uint16_t *rResult); +int WINAPI chcusb_AttachThreadCount(uint16_t *rCount, uint16_t *rMaxCount); +int WINAPI chcusb_getPrintIDStatus(uint16_t pageId, uint8_t *rBuffer, uint16_t *rResult); +int WINAPI chcusb_setPrintStandby(uint16_t position, uint16_t *rResult); +int WINAPI chcusb_setPrintStandby_300(uint16_t *rResult); +int WINAPI chcusb_testCardFeed(uint16_t mode, uint16_t times, uint16_t *rResult); +int WINAPI chcusb_exitCard(uint16_t *rResult); +int WINAPI chcusb_getCardRfidTID(uint8_t *rCardTID, uint16_t *rResult); +int WINAPI chcusb_commCardRfidReader(uint8_t *sendData, uint8_t *rRecvData, uint32_t sendSize, uint32_t *rRecvSize, uint16_t *rResult); +int WINAPI chcusb_updateCardRfidReader(uint8_t *data, uint32_t size, uint16_t *rResult); +int WINAPI chcusb_getErrorLog(uint16_t index, uint8_t *rData, uint16_t *rResult); +int WINAPI chcusb_getErrorStatus(uint16_t *rBuffer); +int WINAPI chcusb_setCutList(uint8_t *rData, uint16_t *rResult); +int WINAPI chcusb_setLaminatePattern(uint16_t index, uint8_t *rData, uint16_t *rResult); +int WINAPI chcusb_color_adjustment(LPCSTR filename, int32_t a2, int32_t a3, int16_t a4, int16_t a5, int64_t a6, int64_t a7, uint16_t *rResult); +int WINAPI chcusb_color_adjustmentEx(int32_t a1, int32_t a2, int32_t a3, int16_t a4, int16_t a5, int64_t a6, int64_t a7, uint16_t *rResult); +int WINAPI chcusb_getEEPROM(uint8_t index, uint8_t *rData, uint16_t *rResult); +int WINAPI chcusb_setParameter(uint8_t a1, uint32_t a2, uint16_t *rResult); +int WINAPI chcusb_getParameter(uint8_t a1, uint8_t *a2, uint16_t *rResult); +int WINAPI chcusb_universal_command(int32_t a1, uint8_t a2, int32_t a3, uint8_t *a4, uint16_t *rResult); + +/* PrintDLL API hooks */ + +void CFW_close(const void *handle); +int CFW_getFirmwareInfo(const void *handle, uint8_t update, LPCSTR filename, uint8_t *rBuffer, uint32_t *rLen, uint16_t *rResult); +int CFW_getPrinterInfo(const void *handle, uint16_t tagNumber, uint8_t *rBuffer, uint32_t *rLen); +int CFW_init(LPCSTR dllpath); +int CFW_listupPrinter(const void *handle, uint8_t *rIdArray); +int CFW_listupPrinterSN(const void *handle, uint64_t *rSerialArray); +int CFW_open(const void *handle, uint16_t *rResult); +int CFW_resetPrinter(const void *handle, uint16_t *rResult); +int CFW_selectPrinter(const void *handle, uint8_t printerId, uint16_t *rResult); +int CFW_selectPrinterSN(const void *handle, uint64_t printerSN, uint16_t *rResult); +int CFW_status(const void *handle, uint16_t *rResult); +int CFW_statusAll(const void *handle, uint8_t *idArray, uint16_t *rResultArray); +void CFW_term(const void *handle); +int CFW_updateFirmware(const void *handle, uint8_t update, LPCSTR filename, uint16_t *rResult); +int CHCUSB_AttachThreadCount(const void *handle, uint16_t *rCount, uint16_t *rMaxCount); +int CHCUSB_MakeThread(const void *handle, uint16_t maxCount); +int CHCUSB_ReleaseThread(const void *handle, uint16_t *rResult); +int CHCUSB_blinkLED(const void *handle, uint16_t *rResult); +int CHCUSB_cancelCopies(const void *handle, uint16_t pageId, uint16_t *rResult); +void CHCUSB_close(const void *handle); +int CHCUSB_commCardRfidReader(const void *handle, uint8_t *sendData, uint8_t *rRecvData, uint32_t sendSize, uint32_t *rRecvSize, uint16_t *rResult); +int CHCUSB_copies(const void *handle, uint16_t copies, uint16_t *rResult); +int CHCUSB_endpage(const void *handle, uint16_t *rResult); +int CHCUSB_exitCard(const void *handle, uint16_t *rResult); +int CHCUSB_getCardRfidTID(const void *handle, uint8_t *rCardTID, uint16_t *rResult); +int CHCUSB_getErrorLog(const void *handle, uint16_t index, uint8_t *rData, uint16_t *rResult); +int CHCUSB_getErrorStatus(const void *handle, uint16_t *rBuffer); +int CHCUSB_getGamma(const void *handle, LPCSTR filename, uint8_t *r, uint8_t *g, uint8_t *b, uint16_t *rResult); +int CHCUSB_getMtf(const void *handle, LPCSTR filename, int32_t *mtf, uint16_t *rResult); +int CHCUSB_getPrintIDStatus(const void *handle, uint16_t pageId, uint8_t *rBuffer, uint16_t *rResult); +int CHCUSB_getPrinterInfo(const void *handle, uint16_t tagNumber, uint8_t *rBuffer, uint32_t *rLen); +int CHCUSB_getPrinterToneCurve(const void *handle, uint16_t type, uint16_t number, uint16_t *data, uint16_t *rResult); +int CHCUSB_imageformat(const void *handle, uint16_t format, uint16_t ncomp, uint16_t depth, uint16_t width, uint16_t height, uint8_t *inputImage, uint16_t *rResult); +int CHCUSB_init(LPCSTR dllpath); +int CHCUSB_listupPrinter(const void *handle, uint8_t *rIdArray); +int CHCUSB_listupPrinterSN(const void *handle, uint64_t *rSerialArray); +int CHCUSB_makeGamma(const void *handle, uint16_t k, uint8_t *intoneR, uint8_t *intoneG, uint8_t *intoneB); +int CHCUSB_open(const void *handle, uint16_t *rResult); +int CHCUSB_resetPrinter(const void *handle, uint16_t *rResult); +int CHCUSB_selectPrinter(const void *handle, uint8_t printerId, uint16_t *rResult); +int CHCUSB_selectPrinterSN(const void *handle, uint64_t printerSN, uint16_t *rResult); +int CHCUSB_setIcctable(const void *handle, uint16_t intents, uint8_t *intoneR, uint8_t *intoneG, uint8_t *intoneB, uint8_t *outtoneR, uint8_t *outtoneG, uint8_t *outtoneB, uint16_t *rResult); +int CHCUSB_setIcctableProfile(const void *handle, LPCSTR icc1, LPCSTR icc2, uint16_t intents, uint16_t *rResult); +int CHCUSB_setPrintStandby(const void *handle, uint16_t position, uint16_t *rResult); +int CHCUSB_setPrinterInfo(const void *handle, uint16_t tagNumber, uint8_t *rBuffer, uint32_t *rLen, uint16_t *rResult); +int CHCUSB_setPrinterToneCurve(const void *handle, uint16_t type, uint16_t number, uint16_t *data, uint16_t *rResult); +int CHCUSB_setmtf(const void *handle, int32_t *mtf); +int CHCUSB_startpage(const void *handle, uint16_t postCardState, uint16_t *pageId, uint16_t *rResult); +int CHCUSB_status(const void *handle, uint16_t *rResult); +int CHCUSB_statusAll(const void *handle, uint8_t *idArray, uint16_t *rResultArray); +int CHCUSB_testCardFeed(const void *handle, uint16_t mode, uint16_t times, uint16_t *rResult); +void CHCUSB_term(const void *handle); +int CHCUSB_updateCardRfidReader(const void *handle, uint8_t *data, uint32_t size, uint16_t *rResult); +int CHCUSB_write(const void *handle, uint8_t *data, uint32_t offset, uint32_t *writeSize, uint16_t *rResult); +int CHCUSB_writeHolo(const void *handle, uint8_t *data, uint32_t offset, uint32_t *writeSize, uint16_t *rResult); +int CHCUSB_writeLaminate(const void *handle, uint8_t *data, uint32_t offset, uint32_t *writeSize, uint16_t *rResult); + +/* Bitmap writing utils */ + +DWORD WriteDataToBitmapFile( + LPCWSTR lpFilePath, DWORD dwBitCount, + DWORD dwWidth, DWORD dwHeight, + PBYTE pbInput, DWORD cbInput, + PBYTE pbMetadata, DWORD cbMetadata, + bool pFlip); +DWORD WriteArrayToFile(LPCSTR lpOutputFilePath, LPVOID lpDataTemp, DWORD nDataSize, BOOL isAppend); + +#define MAX_CARDS 36 + +// big thanks to Coburn and Mitsuhide for helping me figure out this thing + +// request format: +// 0xe0 - sync +// 0x?? - command +// 0x?? - payload length +// ... - payload +// 0x?? - checksum (sum of everything except the sync byte) +// +// response format: +// 0xe0 - sync +// 0x?? - command +// 0x?? - status code +// 0x?? - payload length +// ... - payload +// 0x?? - checksum + +enum { + DECK_CMD_RESET = 0x41, + DECK_CMD_GET_BOOT_FW_VERSION = 0x84, + DECK_CMD_GET_BOARD_INFO = 0x85, + DECK_CMD_INIT_UNK1 = 0x81, + DECK_CMD_GET_APP_FW_VERSION = 0x42, + DECK_CMD_READ_DATA = 0x02, + DECK_CMD_WRITE = 0x03, + DECK_CMD_INIT_UNK2 = 0x04, + DECK_CMD_INIT_UNK3 = 0x05, + DECK_CMD_READ = 0x06 +}; + +enum { + DECK_READ_START = 0x81, + DECK_READ_DATA = 0x82, + DECK_READ_END = 0x83 +}; + +struct deck_hdr { + uint8_t sync; + uint8_t cmd; + uint8_t nbytes; +}; + +struct deck_resp_hdr { + uint8_t sync; + uint8_t cmd; + uint8_t status; + uint8_t nbytes; +}; + +struct deck_resp_get_boot_fw_version { + struct deck_resp_hdr hdr; + uint8_t boot_fw_version; +}; + +struct deck_resp_get_app_fw_version { + struct deck_resp_hdr hdr; + uint8_t app_fw_version; +}; + +struct deck_resp_get_board_info { + struct deck_resp_hdr hdr; + uint8_t board[9]; +}; + +struct deck_resp_read_data { + struct deck_resp_hdr hdr; + uint8_t card_data[32]; +}; + +struct deck_resp_read { + struct deck_resp_hdr hdr; + uint8_t card_data[44]; +}; + +union deck_req_any { + struct deck_hdr hdr; + uint8_t bytes[520]; +}; + +static HRESULT deck_handle_irp(struct irp *irp); +static HRESULT deck_handle_irp_locked(struct irp *irp); +static HRESULT deck_req_dispatch(const union deck_req_any *req); +static HRESULT deck_req_get_boot_fw_version(void); +static HRESULT deck_req_get_app_fw_version(void); +static HRESULT deck_req_get_board_info(void); +static HRESULT deck_req_read(void); +static HRESULT deck_req_read_data(void); +static HRESULT deck_req_write(const uint8_t *req_bytes); +static HRESULT deck_req_nop(uint8_t cmd); +static void deck_read_one(void); + +static HRESULT deck_frame_accept(const struct iobuf *dest); +static void deck_frame_sync(struct iobuf *src); +static HRESULT deck_frame_decode(struct iobuf *dest, struct iobuf *src); +static HRESULT deck_frame_encode(struct iobuf *dest, const void *ptr, size_t nbytes); +static HRESULT deck_frame_encode_byte(struct iobuf *dest, uint8_t byte); + +static CRITICAL_SECTION deck_lock; +static struct uart deck_uart; +static uint8_t deck_written_bytes[1024]; +static uint8_t deck_readable_bytes[1024]; +static uint8_t current_card_idx = 0; +static bool read_pending = false; + +/* C3XXFWDLusb hook tbl. The ordinals are required, as some games, for example + Sekito, will import this library by ordinal and not by name. */ + +static const struct hook_symbol C3XXFWDLusb_hooks[] = { + { + .name = "fwdlusb_open", + .ordinal = 0x0001, + .patch = fwdlusb_open, + .link = NULL + }, { + .name = "fwdlusb_close", + .ordinal = 0x0002, + .patch = fwdlusb_close, + .link = NULL + }, { + .name = "fwdlusb_listupPrinter", + .ordinal = 0x0003, + .patch = fwdlusb_listupPrinter, + .link = NULL + }, { + .name = "fwdlusb_listupPrinterSN", + .ordinal = 0x0004, + .patch = fwdlusb_listupPrinterSN, + .link = NULL + }, { + .name = "fwdlusb_selectPrinter", + .ordinal = 0x0005, + .patch = fwdlusb_selectPrinter, + .link = NULL + }, { + .name = "fwdlusb_selectPrinterSN", + .ordinal = 0x0006, + .patch = fwdlusb_selectPrinterSN, + .link = NULL + }, { + .name = "fwdlusb_getPrinterInfo", + .ordinal = 0x0007, + .patch = fwdlusb_getPrinterInfo, + .link = NULL + }, { + .name = "fwdlusb_status", + .ordinal = 0x0008, + .patch = fwdlusb_status, + .link = NULL + }, { + .name = "fwdlusb_statusAll", + .ordinal = 0x0009, + .patch = fwdlusb_statusAll, + .link = NULL + }, { + .name = "fwdlusb_resetPrinter", + .ordinal = 0x000a, + .patch = fwdlusb_resetPrinter, + .link = NULL + }, { + .name = "fwdlusb_updateFirmware", + .ordinal = 0x000b, + .patch = fwdlusb_updateFirmware, + .link = NULL + }, { + .name = "fwdlusb_getFirmwareInfo", + .ordinal = 0x000c, + .patch = fwdlusb_getFirmwareInfo, + .link = NULL + }, { + .name = "fwdlusb_MakeThread", + .ordinal = 0x0065, + .patch = fwdlusb_MakeThread, + .link = NULL + }, { + .name = "fwdlusb_ReleaseThread", + .ordinal = 0x0066, + .patch = fwdlusb_ReleaseThread, + .link = NULL + }, { + .name = "fwdlusb_AttachThreadCount", + .ordinal = 0x0067, + .patch = fwdlusb_AttachThreadCount, + .link = NULL + }, { + .name = "fwdlusb_getErrorLog", + .ordinal = 0x0068, + .patch = fwdlusb_getErrorLog, + .link = NULL + }, +}; + +/* C300usb hook tbl */ + +static const struct hook_symbol C300usb_hooks[] = { + { + .name = "chcusb_MakeThread", + .ordinal = 0x0001, + .patch = chcusb_MakeThread, + .link = NULL + }, { + .name = "chcusb_open", + .ordinal = 0x0002, + .patch = chcusb_open, + .link = NULL + }, { + .name = "chcusb_close", + .ordinal = 0x0003, + .patch = chcusb_close, + .link = NULL + }, { + .name = "chcusb_ReleaseThread", + .ordinal = 0x0004, + .patch = chcusb_ReleaseThread, + .link = NULL + }, { + .name = "chcusb_listupPrinter", + .ordinal = 0x0005, + .patch = chcusb_listupPrinter, + .link = NULL + }, { + .name = "chcusb_listupPrinterSN", + .ordinal = 0x0006, + .patch = chcusb_listupPrinterSN, + .link = NULL + }, { + .name = "chcusb_selectPrinter", + .ordinal = 0x0007, + .patch = chcusb_selectPrinter, + .link = NULL + }, { + .name = "chcusb_selectPrinterSN", + .ordinal = 0x0008, + .patch = chcusb_selectPrinterSN, + .link = NULL + }, { + .name = "chcusb_getPrinterInfo", + .ordinal = 0x0009, + .patch = chcusb_getPrinterInfo, + .link = NULL + }, { + .name = "chcusb_imageformat", + .ordinal = 0x000a, + .patch = chcusb_imageformat, + .link = NULL + }, { + .name = "chcusb_setmtf", + .ordinal = 0x000b, + .patch = chcusb_setmtf, + .link = NULL + }, { + .name = "chcusb_makeGamma", + .ordinal = 0x000c, + .patch = chcusb_makeGamma, + .link = NULL + }, { + .name = "chcusb_setIcctable", + .ordinal = 0x000d, + .patch = chcusb_setIcctable, + .link = NULL + }, { + .name = "chcusb_copies", + .ordinal = 0x000e, + .patch = chcusb_copies, + .link = NULL + }, { + .name = "chcusb_status", + .ordinal = 0x000f, + .patch = chcusb_status, + .link = NULL + }, { + .name = "chcusb_statusAll", + .ordinal = 0x0010, + .patch = chcusb_statusAll, + .link = NULL + }, { + .name = "chcusb_startpage", + .ordinal = 0x0011, + .patch = chcusb_startpage_300, + .link = NULL + }, { + .name = "chcusb_endpage", + .ordinal = 0x0012, + .patch = chcusb_endpage, + .link = NULL + }, { + .name = "chcusb_write", + .ordinal = 0x0013, + .patch = chcusb_write, + .link = NULL + }, { + .name = "chcusb_writeLaminate", + .ordinal = 0x0014, + .patch = chcusb_writeLaminate, + .link = NULL + }, { + .name = "chcusb_setPrinterInfo", + .ordinal = 0x0015, + .patch = chcusb_setPrinterInfo, + .link = NULL + }, { + .name = "chcusb_getGamma", + .ordinal = 0x0016, + .patch = chcusb_getGamma, + .link = NULL + }, { + .name = "chcusb_getMtf", + .ordinal = 0x0017, + .patch = chcusb_getMtf, + .link = NULL + }, { + .name = "chcusb_cancelCopies", + .ordinal = 0x0018, + .patch = chcusb_cancelCopies, + .link = NULL + }, { + .name = "chcusb_setPrinterToneCurve", + .ordinal = 0x0019, + .patch = chcusb_setPrinterToneCurve, + .link = NULL + }, { + .name = "chcusb_getPrinterToneCurve", + .ordinal = 0x001a, + .patch = chcusb_getPrinterToneCurve, + .link = NULL + }, { + .name = "chcusb_blinkLED", + .ordinal = 0x001b, + .patch = chcusb_blinkLED, + .link = NULL + }, { + .name = "chcusb_resetPrinter", + .ordinal = 0x001c, + .patch = chcusb_resetPrinter, + .link = NULL + }, { + .name = "chcusb_AttachThreadCount", + .ordinal = 0x001d, + .patch = chcusb_AttachThreadCount, + .link = NULL + }, { + .name = "chcusb_getPrintIDStatus", + .ordinal = 0x001e, + .patch = chcusb_getPrintIDStatus, + .link = NULL + }, { + .name = "chcusb_setPrintStandby", + .ordinal = 0x001f, + .patch = chcusb_setPrintStandby_300, + .link = NULL + }, { + .name = "chcusb_testCardFeed", + .ordinal = 0x0020, + .patch = chcusb_testCardFeed, + .link = NULL + }, { + .name = "chcusb_setParameter", + .ordinal = 0x0021, + .patch = chcusb_setParameter, + .link = NULL + }, { + .name = "chcusb_getParameter", + .ordinal = 0x0022, + .patch = chcusb_getParameter, + .link = NULL + }, { + .name = "chcusb_getErrorStatus", + .ordinal = 0x0023, + .patch = chcusb_getErrorStatus, + .link = NULL + }, { + .name = "chcusb_setCutList", + .ordinal = 0x0028, + .patch = chcusb_setCutList, + .link = NULL + }, { + .name = "chcusb_setLaminatePattern", + .ordinal = 0x0029, + .patch = chcusb_setLaminatePattern, + .link = NULL + }, { + .name = "chcusb_color_adjustment", + .ordinal = 0x002a, + .patch = chcusb_color_adjustment, + .link = NULL + }, { + .name = "chcusb_color_adjustmentEx", + .ordinal = 0x002b, + .patch = chcusb_color_adjustmentEx, + .link = NULL + }, { + .name = "chcusb_getEEPROM", + .ordinal = 0x003a, + .patch = chcusb_getEEPROM, + .link = NULL + }, { + .name = "chcusb_universal_command", + .ordinal = 0x0049, + .patch = chcusb_universal_command, + .link = NULL + }, +}; + +/* C310A-Busb/C320Ausb/C330Ausb hook tbl. The ordinals are required, as some + games, for example Sekito, will import this library by ordinal and not by + name. */ + +static const struct hook_symbol C3XXusb_hooks[] = { + { + .name = "chcusb_MakeThread", + .ordinal = 0x0001, + .patch = chcusb_MakeThread, + .link = NULL + }, { + .name = "chcusb_open", + .ordinal = 0x0002, + .patch = chcusb_open, + .link = NULL + }, { + .name = "chcusb_close", + .ordinal = 0x0003, + .patch = chcusb_close, + .link = NULL + }, { + .name = "chcusb_ReleaseThread", + .ordinal = 0x0004, + .patch = chcusb_ReleaseThread, + .link = NULL + }, { + .name = "chcusb_listupPrinter", + .ordinal = 0x0005, + .patch = chcusb_listupPrinter, + .link = NULL + }, { + .name = "chcusb_listupPrinterSN", + .ordinal = 0x0006, + .patch = chcusb_listupPrinterSN, + .link = NULL + }, { + .name = "chcusb_selectPrinter", + .ordinal = 0x0007, + .patch = chcusb_selectPrinter, + .link = NULL + }, { + .name = "chcusb_selectPrinterSN", + .ordinal = 0x0008, + .patch = chcusb_selectPrinterSN, + .link = NULL + }, { + .name = "chcusb_getPrinterInfo", + .ordinal = 0x0009, + .patch = chcusb_getPrinterInfo, + .link = NULL + }, { + .name = "chcusb_imageformat", + .ordinal = 0x000a, + .patch = chcusb_imageformat, + .link = NULL + }, { + .name = "chcusb_setmtf", + .ordinal = 0x000b, + .patch = chcusb_setmtf, + .link = NULL + }, { + .name = "chcusb_makeGamma", + .ordinal = 0x000c, + .patch = chcusb_makeGamma, + .link = NULL + }, { + .name = "chcusb_setIcctable", + .ordinal = 0x000d, + .patch = chcusb_setIcctable, + .link = NULL + }, { + .name = "chcusb_copies", + .ordinal = 0x000e, + .patch = chcusb_copies, + .link = NULL + }, { + .name = "chcusb_status", + .ordinal = 0x000f, + .patch = chcusb_status, + .link = NULL + }, { + .name = "chcusb_statusAll", + .ordinal = 0x0010, + .patch = chcusb_statusAll, + .link = NULL + }, { + .name = "chcusb_startpage", + .ordinal = 0x0011, + .patch = chcusb_startpage, + .link = NULL + }, { + .name = "chcusb_endpage", + .ordinal = 0x0012, + .patch = chcusb_endpage, + .link = NULL + }, { + .name = "chcusb_write", + .ordinal = 0x0013, + .patch = chcusb_write, + .link = NULL + }, { + .name = "chcusb_writeLaminate", + .ordinal = 0x0014, + .patch = chcusb_writeLaminate, + .link = NULL + }, { + .name = "chcusb_writeHolo", + .ordinal = 0x0015, + .patch = chcusb_writeHolo, + .link = NULL + }, { + .name = "chcusb_setPrinterInfo", + .ordinal = 0x0016, + .patch = chcusb_setPrinterInfo, + .link = NULL + }, { + .name = "chcusb_getGamma", + .ordinal = 0x0017, + .patch = chcusb_getGamma, + .link = NULL + }, { + .name = "chcusb_getMtf", + .ordinal = 0x0018, + .patch = chcusb_getMtf, + .link = NULL + }, { + .name = "chcusb_cancelCopies", + .ordinal = 0x0019, + .patch = chcusb_cancelCopies, + .link = NULL + }, { + .name = "chcusb_setPrinterToneCurve", + .ordinal = 0x001a, + .patch = chcusb_setPrinterToneCurve, + .link = NULL + }, { + .name = "chcusb_getPrinterToneCurve", + .ordinal = 0x001b, + .patch = chcusb_getPrinterToneCurve, + .link = NULL + }, { + .name = "chcusb_blinkLED", + .ordinal = 0x001c, + .patch = chcusb_blinkLED, + .link = NULL + }, { + .name = "chcusb_resetPrinter", + .ordinal = 0x001d, + .patch = chcusb_resetPrinter, + .link = NULL + }, { + .name = "chcusb_AttachThreadCount", + .ordinal = 0x001e, + .patch = chcusb_AttachThreadCount, + .link = NULL + }, { + .name = "chcusb_getPrintIDStatus", + .ordinal = 0x001f, + .patch = chcusb_getPrintIDStatus, + .link = NULL + }, { + .name = "chcusb_setPrintStandby", + .ordinal = 0x0020, + .patch = chcusb_setPrintStandby, + .link = NULL + }, { + .name = "chcusb_testCardFeed", + .ordinal = 0x0021, + .patch = chcusb_testCardFeed, + .link = NULL + }, { + .name = "chcusb_exitCard", + .ordinal = 0x0022, + .patch = chcusb_exitCard, + .link = NULL + }, { + .name = "chcusb_getCardRfidTID", + .ordinal = 0x0023, + .patch = chcusb_getCardRfidTID, + .link = NULL + }, { + .name = "chcusb_commCardRfidReader", + .ordinal = 0x0024, + .patch = chcusb_commCardRfidReader, + .link = NULL + }, { + .name = "chcusb_updateCardRfidReader", + .ordinal = 0x0025, + .patch = chcusb_updateCardRfidReader, + .link = NULL + }, { + .name = "chcusb_getErrorLog", + .ordinal = 0x0026, + .patch = chcusb_getErrorLog, + .link = NULL + }, { + .name = "chcusb_getErrorStatus", + .ordinal = 0x0027, + .patch = chcusb_getErrorStatus, + .link = NULL + }, { + .name = "chcusb_setCutList", + .ordinal = 0x0028, + .patch = chcusb_setCutList, + .link = NULL + }, { + .name = "chcusb_setLaminatePattern", + .ordinal = 0x0029, + .patch = chcusb_setLaminatePattern, + .link = NULL + }, { + .name = "chcusb_color_adjustment", + .ordinal = 0x002a, + .patch = chcusb_color_adjustment, + .link = NULL + }, { + .name = "chcusb_color_adjustmentEx", + .ordinal = 0x002b, + .patch = chcusb_color_adjustmentEx, + .link = NULL + }, { + .name = "chcusb_getEEPROM", + .ordinal = 0x003a, + .patch = chcusb_getEEPROM, + .link = NULL + }, { + .name = "chcusb_setParameter", + .ordinal = 0x0040, + .patch = chcusb_setParameter, + .link = NULL + }, { + .name = "chcusb_getParameter", + .ordinal = 0x0041, + .patch = chcusb_getParameter, + .link = NULL + }, { + .name = "chcusb_universal_command", + .ordinal = 0x0049, + .patch = chcusb_universal_command, + .link = NULL + }, +}; + +/* PrintDLL hook tbl */ + +static struct hook_symbol printdll_hooks[] = { + { + .name = "CFW_close", + .ordinal = 0x0001, + .patch = CFW_close, + .link = NULL + }, { + .name = "CFW_getFirmwareInfo", + .ordinal = 0x0002, + .patch = CFW_getFirmwareInfo, + .link = NULL + }, { + .name = "CFW_getPrinterInfo", + .ordinal = 0x0003, + .patch = CFW_getPrinterInfo, + .link = NULL + }, { + .name = "CFW_init", + .ordinal = 0x0004, + .patch = CFW_init, + .link = NULL + }, { + .name = "CFW_listupPrinter", + .ordinal = 0x0005, + .patch = CFW_listupPrinter, + .link = NULL + }, { + .name = "CFW_listupPrinterSN", + .ordinal = 0x0006, + .patch = CFW_listupPrinterSN, + .link = NULL + }, { + .name = "CFW_open", + .ordinal = 0x0007, + .patch = CFW_open, + .link = NULL + }, { + .name = "CFW_resetPrinter", + .ordinal = 0x0008, + .patch = CFW_resetPrinter, + .link = NULL + }, { + .name = "CFW_selectPrinter", + .ordinal = 0x0009, + .patch = CFW_selectPrinter, + .link = NULL + }, { + .name = "CFW_selectPrinterSN", + .ordinal = 0x000a, + .patch = CFW_selectPrinterSN, + .link = NULL + }, { + .name = "CFW_status", + .ordinal = 0x000b, + .patch = CFW_status, + .link = NULL + }, { + .name = "CFW_statusAll", + .ordinal = 0x000c, + .patch = CFW_statusAll, + .link = NULL + }, { + .name = "CFW_term", + .ordinal = 0x000d, + .patch = CFW_term, + .link = NULL + }, { + .name = "CFW_updateFirmware", + .ordinal = 0x000e, + .patch = CFW_updateFirmware, + .link = NULL + }, { + .name = "CHCUSB_AttachThreadCount", + .ordinal = 0x000f, + .patch = CHCUSB_AttachThreadCount, + .link = NULL + }, { + .name = "CHCUSB_MakeThread", + .ordinal = 0x0010, + .patch = CHCUSB_MakeThread, + .link = NULL + }, { + .name = "CHCUSB_ReleaseThread", + .ordinal = 0x0011, + .patch = CHCUSB_ReleaseThread, + .link = NULL + }, { + .name = "CHCUSB_blinkLED", + .ordinal = 0x0012, + .patch = CHCUSB_blinkLED, + .link = NULL + }, { + .name = "CHCUSB_cancelCopies", + .ordinal = 0x0013, + .patch = CHCUSB_cancelCopies, + .link = NULL + }, { + .name = "CHCUSB_close", + .ordinal = 0x0014, + .patch = CHCUSB_close, + .link = NULL + }, { + .name = "CHCUSB_commCardRfidReader", + .ordinal = 0x0015, + .patch = CHCUSB_commCardRfidReader, + .link = NULL + }, { + .name = "CHCUSB_copies", + .ordinal = 0x0016, + .patch = CHCUSB_copies, + .link = NULL + }, { + .name = "CHCUSB_endpage", + .ordinal = 0x0017, + .patch = CHCUSB_endpage, + .link = NULL + }, { + .name = "CHCUSB_exitCard", + .ordinal = 0x0018, + .patch = CHCUSB_exitCard, + .link = NULL + }, { + .name = "CHCUSB_getCardRfidTID", + .ordinal = 0x0019, + .patch = CHCUSB_getCardRfidTID, + .link = NULL + }, { + .name = "CHCUSB_getErrorLog", + .ordinal = 0x001a, + .patch = CHCUSB_getErrorLog, + .link = NULL + }, { + .name = "CHCUSB_getErrorStatus", + .ordinal = 0x001b, + .patch = CHCUSB_getErrorStatus, + .link = NULL + }, { + .name = "CHCUSB_getGamma", + .ordinal = 0x001c, + .patch = CHCUSB_getGamma, + .link = NULL + }, { + .name = "CHCUSB_getMtf", + .ordinal = 0x001d, + .patch = CHCUSB_getMtf, + .link = NULL + }, { + .name = "CHCUSB_getPrintIDStatus", + .ordinal = 0x001e, + .patch = CHCUSB_getPrintIDStatus, + .link = NULL + }, { + .name = "CHCUSB_getPrinterInfo", + .ordinal = 0x001f, + .patch = CHCUSB_getPrinterInfo, + .link = NULL + }, { + .name = "CHCUSB_getPrinterToneCurve", + .ordinal = 0x0020, + .patch = CHCUSB_getPrinterToneCurve, + .link = NULL + }, { + .name = "CHCUSB_imageformat", + .ordinal = 0x0021, + .patch = CHCUSB_imageformat, + .link = NULL + }, { + .name = "CHCUSB_init", + .ordinal = 0x0022, + .patch = CHCUSB_init, + .link = NULL + }, { + .name = "CHCUSB_listupPrinter", + .ordinal = 0x0023, + .patch = CHCUSB_listupPrinter, + .link = NULL + }, { + .name = "CHCUSB_listupPrinterSN", + .ordinal = 0x0024, + .patch = CHCUSB_listupPrinterSN, + .link = NULL + }, { + .name = "CHCUSB_makeGamma", + .ordinal = 0x0025, + .patch = CHCUSB_makeGamma, + .link = NULL + }, { + .name = "CHCUSB_open", + .ordinal = 0x0026, + .patch = CHCUSB_open, + .link = NULL + }, { + .name = "CHCUSB_resetPrinter", + .ordinal = 0x0027, + .patch = CHCUSB_resetPrinter, + .link = NULL + }, { + .name = "CHCUSB_selectPrinter", + .ordinal = 0x0028, + .patch = CHCUSB_selectPrinter, + .link = NULL + }, { + .name = "CHCUSB_selectPrinterSN", + .ordinal = 0x0029, + .patch = CHCUSB_selectPrinterSN, + .link = NULL + }, { + .name = "CHCUSB_setIcctable", + .ordinal = 0x002a, + .patch = CHCUSB_setIcctable, + .link = NULL + }, { + .name = "CHCUSB_setIcctableProfile", + .ordinal = 0x002b, + .patch = CHCUSB_setIcctableProfile, + .link = NULL + }, { + .name = "CHCUSB_setPrintStandby", + .ordinal = 0x002c, + .patch = CHCUSB_setPrintStandby, + .link = NULL + }, { + .name = "CHCUSB_setPrinterInfo", + .ordinal = 0x002d, + .patch = CHCUSB_setPrinterInfo, + .link = NULL + }, { + .name = "CHCUSB_setPrinterToneCurve", + .ordinal = 0x0023, + .patch = CHCUSB_setPrinterToneCurve, + .link = NULL + }, { + .name = "CHCUSB_setmtf", + .ordinal = 0x002f, + .patch = CHCUSB_setmtf, + .link = NULL + }, { + .name = "CHCUSB_startpage", + .ordinal = 0x0030, + .patch = CHCUSB_startpage, + .link = NULL + }, { + .name = "CHCUSB_status", + .ordinal = 0x0031, + .patch = CHCUSB_status, + .link = NULL + }, { + .name = "CHCUSB_statusAll", + .ordinal = 0x0032, + .patch = CHCUSB_statusAll, + .link = NULL + }, { + .name = "CHCUSB_term", + .ordinal = 0x0033, + .patch = CHCUSB_term, + .link = NULL + }, { + .name = "CHCUSB_testCardFeed", + .ordinal = 0x0034, + .patch = CHCUSB_testCardFeed, + .link = NULL + }, { + .name = "CHCUSB_updateCardRfidReader", + .ordinal = 0x0035, + .patch = CHCUSB_updateCardRfidReader, + .link = NULL + }, { + .name = "CHCUSB_write", + .ordinal = 0x0036, + .patch = CHCUSB_write, + .link = NULL + }, { + .name = "CHCUSB_writeHolo", + .ordinal = 0x0037, + .patch = CHCUSB_writeHolo, + .link = NULL + }, { + .name = "CHCUSB_writeLaminate", + .ordinal = 0x0038, + .patch = CHCUSB_writeLaminate, + .link = NULL + }, +}; + +static struct printer_config printer_config; + +void printer_hook_init(const struct printer_config *cfg, int rfid_port_no, HINSTANCE self) { + HANDLE fwFile = NULL; + DWORD bytesRead = 0; + + assert(cfg != NULL); + + if (!cfg->enable) { + return; + } + + /* Set serial and rotate cfg */ + + memcpy(&serialNo, cfg->serial_no, sizeof(serialNo)); + _byteswap_uint64(serialNo); + + rotate180 = cfg->rotate_180; + + memcpy(&printer_config, cfg, sizeof(*cfg)); + printer_hook_insert_hooks(NULL); + + /* + if (self != NULL) { + // dll_hook_push(self, L"PrintDLL.dll"); // TODO: This doesn't work. Unity moment + dll_hook_push(self, L"C300usb.dll"); + dll_hook_push(self, L"C300FWDLusb.dll"); + dll_hook_push(self, L"C310Ausb.dll"); + dll_hook_push(self, L"C310Busb.dll"); + dll_hook_push(self, L"C310FWDLusb.dll"); + dll_hook_push(self, L"C310BFWDLusb.dll"); + dll_hook_push(self, L"C320Ausb.dll"); + dll_hook_push(self, L"C320AFWDLusb.dll"); + dll_hook_push(self, L"C330Ausb.dll"); + dll_hook_push(self, L"C330AFWDLusb.dll"); + } + */ + + // Load firmware if has previously been written to + fwFile = CreateFileW(cfg->main_fw_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (fwFile != NULL) { + ReadFile(fwFile, mainFirmware, sizeof(mainFirmware), &bytesRead, NULL); + CloseHandle(fwFile); + } + + fwFile = CreateFileW(cfg->dsp_fw_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (fwFile != NULL) { + ReadFile(fwFile, dspFirmware, sizeof(dspFirmware), &bytesRead, NULL); + CloseHandle(fwFile); + } + + fwFile = CreateFileW(cfg->param_fw_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (fwFile != NULL) { + ReadFile(fwFile, paramFirmware, sizeof(dspFirmware), &bytesRead, NULL); + CloseHandle(fwFile); + } + + CreateDirectoryW(cfg->printer_out_path, NULL); + memcpy(printer_out_path, cfg->printer_out_path, MAX_PATH); + + if (rfid_port_no != 0) { + InitializeCriticalSection(&deck_lock); + + uart_init(&deck_uart, rfid_port_no); + deck_uart.written.bytes = deck_written_bytes; + deck_uart.written.nbytes = sizeof(deck_written_bytes); + deck_uart.readable.bytes = deck_readable_bytes; + deck_uart.readable.nbytes = sizeof(deck_readable_bytes); + + dprintf("Printer: RFID: hook enabled.\n"); + + if (FAILED(iohook_push_handler(deck_handle_irp))) { + dprintf("Printer: RFID: failed to hook IRP handler.\n"); + } + } + + dprintf("Printer: hook enabled.\n"); +} + +void printer_hook_insert_hooks(HMODULE target) { + hook_table_apply(target, "C310Ausb.dll", C3XXusb_hooks, _countof(C3XXusb_hooks)); + hook_table_apply(target, "C310Busb.dll", C3XXusb_hooks, _countof(C3XXusb_hooks)); + hook_table_apply(target, "C310FWDLusb.dll", C3XXFWDLusb_hooks, _countof(C3XXFWDLusb_hooks)); + hook_table_apply(target, "C310BFWDLusb.dll", C3XXFWDLusb_hooks, _countof(C3XXFWDLusb_hooks)); + hook_table_apply(target, "C320Ausb.dll", C3XXusb_hooks, _countof(C3XXusb_hooks)); + hook_table_apply(target, "C320AFWDLusb.dll", C3XXFWDLusb_hooks, _countof(C3XXFWDLusb_hooks)); + hook_table_apply(target, "C330Ausb.dll", C3XXusb_hooks, _countof(C3XXusb_hooks)); + hook_table_apply(target, "C330AFWDLusb.dll", C3XXFWDLusb_hooks, _countof(C3XXFWDLusb_hooks)); + + /* Unity workaround */ + proc_addr_table_push(target, "PrintDLL.dll", printdll_hooks, _countof(printdll_hooks)); + proc_addr_table_push(target, "C300usb.dll", C300usb_hooks, _countof(C300usb_hooks)); + proc_addr_table_push(target, "C300FWDLusb.dll", C3XXFWDLusb_hooks, _countof(C3XXFWDLusb_hooks)); +} + +static inline bool check_for_wait_time(){ + return finishTime > 0 && GetTickCount64() < finishTime; +} + +static void generate_rfid(void) { + for (int i = 0; i < sizeof(cardRFID); i++) + cardRFID[i] = rand(); + + dprintf("Printer: New card RFID:\n"); + dump(cardRFID, sizeof(cardRFID)); +} + +static HRESULT deck_handle_irp(struct irp *irp) { + HRESULT hr; + + assert(irp != NULL); + + if (!uart_match_irp(&deck_uart, irp)) { + return iohook_invoke_next(irp); + } + + EnterCriticalSection(&deck_lock); + hr = deck_handle_irp_locked(irp); + LeaveCriticalSection(&deck_lock); + + return hr; +} + +static HRESULT deck_handle_irp_locked(struct irp *irp) { + uint8_t req[1024]; + struct iobuf req_iobuf; + HRESULT hr; + + if (irp->op == IRP_OP_OPEN) { + dprintf("Printer: RFID: Starting backend\n"); + } + + hr = uart_handle_irp(&deck_uart, irp); + + if (SUCCEEDED(hr) && irp->op == IRP_OP_READ && read_pending == true) { + deck_read_one(); + return S_OK; + } + + if (FAILED(hr) || irp->op != IRP_OP_WRITE) { + return hr; + } + + for (;;) { + // if (deck_uart.written.pos != 0) { + // dprintf("Printer: RFID: TX Buffer:\n"); + // dump_iobuf(&deck_uart.written); + // } + + req_iobuf.bytes = req; + req_iobuf.nbytes = sizeof(req); + req_iobuf.pos = 0; + + hr = deck_frame_decode(&req_iobuf, &deck_uart.written); + + if (hr != S_OK) { + if (FAILED(hr)) { + dprintf("Printer: RFID: Deframe error: %x, %d %d\n", (int)hr, (int)req_iobuf.nbytes, (int)req_iobuf.pos); + } + + return hr; + } + + // dprintf("Printer: RFID: Deframe Buffer:\n"); + // dump_iobuf(&req_iobuf); + + hr = deck_req_dispatch((const union deck_req_any *)&req); + + if (FAILED(hr)) { + dprintf("Printer: RFID: Processing error: %x\n", (int)hr); + } + + // dprintf("Printer: RFID: Written bytes:\n"); + // dump_iobuf(&deck_uart.readable); + } +} + +static HRESULT deck_req_dispatch(const union deck_req_any *req) { + switch (req->hdr.cmd) { + case DECK_CMD_RESET: + case DECK_CMD_INIT_UNK1: + case DECK_CMD_INIT_UNK2: + case DECK_CMD_INIT_UNK3: + return deck_req_nop(req->hdr.cmd); + + case DECK_CMD_GET_BOOT_FW_VERSION: + return deck_req_get_boot_fw_version(); + + case DECK_CMD_GET_APP_FW_VERSION: + return deck_req_get_app_fw_version(); + + case DECK_CMD_GET_BOARD_INFO: + return deck_req_get_board_info(); + + case DECK_CMD_READ: + return deck_req_read(); + + case DECK_CMD_READ_DATA: + return deck_req_read_data(); + + case DECK_CMD_WRITE: + return deck_req_write(req->bytes); + + default: + dprintf("Printer: RFID: Unhandled command %#02x\n", req->hdr.cmd); + + return S_OK; + } +} + +static HRESULT deck_req_get_boot_fw_version(void) { + struct deck_resp_get_boot_fw_version resp; + + dprintf("Printer: RFID: Get Boot FW Version\n"); + + resp.hdr.sync = 0xE0; + resp.hdr.cmd = DECK_CMD_GET_BOOT_FW_VERSION; + resp.hdr.status = 0; + resp.hdr.nbytes = 1; + resp.boot_fw_version = 0x90; + + return deck_frame_encode(&deck_uart.readable, &resp, sizeof(resp)); +} + +static HRESULT deck_req_get_app_fw_version(void) { + struct deck_resp_get_app_fw_version resp; + + dprintf("Printer: RFID: Get App FW Version\n"); + + resp.hdr.sync = 0xE0; + resp.hdr.cmd = DECK_CMD_GET_APP_FW_VERSION; + resp.hdr.status = 0; + resp.hdr.nbytes = 1; + resp.app_fw_version = 0x90; + + return deck_frame_encode(&deck_uart.readable, &resp, sizeof(resp)); +} + +static HRESULT deck_req_get_board_info(void) { + struct deck_resp_get_board_info resp; + + dprintf("Printer: RFID: Get Board Info\n"); + + resp.hdr.sync = 0xE0; + resp.hdr.cmd = DECK_CMD_GET_BOARD_INFO; + resp.hdr.status = 0; + resp.hdr.nbytes = 9; + memcpy(resp.board, (void *)"837-15347", 9); + + return deck_frame_encode(&deck_uart.readable, &resp, sizeof(resp)); +} + +static HRESULT deck_req_read(void) { + struct deck_resp_read resp; + + dprintf("Printer: RFID: Read Card\n"); + + resp.hdr.sync = 0xE0; + resp.hdr.cmd = DECK_CMD_READ; + resp.hdr.status = DECK_READ_START; + resp.hdr.nbytes = 0; + + current_card_idx = 0; + read_pending = true; + + return deck_frame_encode(&deck_uart.readable, &resp.hdr, sizeof(resp.hdr)); +} + +static HRESULT deck_req_read_data(void) { + struct deck_resp_read_data resp; + + dprintf("Printer: RFID: Read Card Data\n"); + + resp.hdr.sync = 0xE0; + resp.hdr.cmd = DECK_CMD_READ_DATA; + resp.hdr.status = 0; + resp.hdr.nbytes = 32; + memcpy(resp.card_data, cardDataBuffer, 32); + + return deck_frame_encode(&deck_uart.readable, &resp, sizeof(resp)); +} + +static HRESULT deck_req_write(const uint8_t *req_bytes) { + struct deck_resp_hdr resp; + uint8_t off; + + resp.sync = 0xE0; + resp.cmd = DECK_CMD_WRITE; + resp.status = 0; + resp.nbytes = 0; + + off = (req_bytes[5] - 1) * 2; + cardDataBuffer[off] = req_bytes[6]; + cardDataBuffer[off + 1] = req_bytes[7]; + if (req_bytes[5] == 0x10) { + dprintf("Printer: RFID: Card Data Buffer: \n"); + dump(cardDataBuffer, 0x20); + } + + return deck_frame_encode(&deck_uart.readable, &resp, sizeof(resp)); +} + +static void deck_read_one(void) { + struct deck_resp_read resp; + + dprintf("Printer: RFID: Read One Card\n"); + + resp.hdr.sync = 0xE0; + resp.hdr.cmd = DECK_CMD_READ; + + if (current_card_idx < 1) { + resp.hdr.status = DECK_READ_DATA; + resp.hdr.nbytes = 44; + memset(resp.card_data, 0, 44); + memcpy(resp.card_data, cardRFID, 0xC); + memcpy(resp.card_data + 0x0C, cardDataBuffer, 0x20); + dump(resp.card_data, 44); + + deck_frame_encode(&deck_uart.readable, &resp, sizeof(resp)); + current_card_idx++; + } else { + resp.hdr.status = DECK_READ_END; + resp.hdr.nbytes = 0; + deck_frame_encode(&deck_uart.readable, &resp.hdr, sizeof(resp.hdr)); + read_pending = false; + } +} + +static HRESULT deck_req_nop(uint8_t cmd) { + struct deck_resp_hdr resp; + + dprintf("Printer: RFID: No-op cmd %#02x\n", cmd); + + resp.sync = 0xE0; + resp.cmd = cmd; + resp.status = 0; + resp.nbytes = 0; + + return deck_frame_encode(&deck_uart.readable, &resp, sizeof(resp)); +} + +static void deck_frame_sync(struct iobuf *src) { + size_t i; + + for (i = 0; i < src->pos && src->bytes[i] != 0xE0; i++) + ; + + src->pos -= i; + memmove(&src->bytes[0], &src->bytes[i], i); +} + +static HRESULT deck_frame_accept(const struct iobuf *dest) { + if (dest->pos < 2 || dest->pos != dest->bytes[2] + 4) { + return S_FALSE; + } + + return S_OK; +} + +static HRESULT deck_frame_decode(struct iobuf *dest, struct iobuf *src) { + uint8_t byte; + bool escape; + size_t i; + HRESULT hr; + + assert(dest != NULL); + assert(dest->bytes != NULL || dest->nbytes == 0); + assert(dest->pos <= dest->nbytes); + assert(src != NULL); + assert(src->bytes != NULL || src->nbytes == 0); + assert(src->pos <= src->nbytes); + + dest->pos = 0; + escape = false; + + for (i = 0, hr = S_FALSE; i < src->pos && hr == S_FALSE; i++) { + /* Step the FSM to unstuff another byte */ + + byte = src->bytes[i]; + + if (dest->pos >= dest->nbytes) { + hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } else if (i == 0) { + dest->bytes[dest->pos++] = byte; + } else if (byte == 0xE0) { + hr = E_FAIL; + } else if (byte == 0xD0) { + if (escape) { + hr = E_FAIL; + } + + escape = true; + } else if (escape) { + dest->bytes[dest->pos++] = byte + 1; + escape = false; + } else { + dest->bytes[dest->pos++] = byte; + } + + /* Try to accept the packet we've built up so far */ + + if (SUCCEEDED(hr)) { + hr = deck_frame_accept(dest); + } + } + + /* Handle FSM terminal state */ + + if (hr != S_FALSE) { + /* Frame was either accepted or rejected, remove it from src */ + memmove(&src->bytes[0], &src->bytes[i], src->pos - i); + src->pos -= i; + } + + return hr; +} + +static HRESULT deck_frame_encode( + struct iobuf *dest, + const void *ptr, + size_t nbytes) { + const uint8_t *src; + uint8_t checksum; + uint8_t byte; + size_t i; + HRESULT hr; + + assert(dest != NULL); + assert(dest->bytes != NULL || dest->nbytes == 0); + assert(dest->pos <= dest->nbytes); + assert(ptr != NULL); + + src = ptr; + + assert(nbytes >= 2 && src[0] == 0xE0 && src[3] + 4 == nbytes); + + if (dest->pos >= dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = 0xE0; + checksum = 0x0; + + for (i = 1; i < nbytes; i++) { + byte = src[i]; + checksum += byte; + + hr = deck_frame_encode_byte(dest, byte); + + if (FAILED(hr)) { + return hr; + } + } + + return deck_frame_encode_byte(dest, checksum); +} + +static HRESULT deck_frame_encode_byte(struct iobuf *dest, uint8_t byte) { + if (byte == 0xD0 || byte == 0xE0) { + if (dest->pos + 2 > dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = 0xD0; + dest->bytes[dest->pos++] = byte - 1; + } else { + if (dest->pos + 1 > dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = byte; + } + + return S_OK; +} + +// C3XXFWDLusb stubs + +int WINAPI fwdlusb_open(uint16_t *rResult) { + dprintf("Printer: C3XXFWDLusb: %s\n", __func__); + *rResult = 0; + return 1; +} + +void WINAPI fwdlusb_close() {} + +int WINAPI fwdlusb_listupPrinter(uint8_t *rIdArray) { + dprintf("Printer: C3XXFWDLusb: %s\n", __func__); + memset(rIdArray, 0xFF, 0x80); + rIdArray[0] = idNumber; + return 1; +} + +int WINAPI fwdlusb_listupPrinterSN(uint64_t *rSerialArray) { + dprintf("Printer: C3XXFWDLusb: %s\n", __func__); + memset(rSerialArray, 0xFF, 0x400); + rSerialArray[0] = serialNo; + return 1; +} + +int WINAPI fwdlusb_selectPrinter(uint8_t printerId, uint16_t *rResult) { + dprintf("Printer: C3XXFWDLusb: %s\n", __func__); + *rResult = 0; + return 1; +} + +int WINAPI fwdlusb_selectPrinterSN(uint64_t printerSN, uint16_t *rResult) { + dprintf("Printer: C3XXFWDLusb: %s\n", __func__); + *rResult = 0; + return 1; +} + +int WINAPI fwdlusb_getPrinterInfo(uint16_t tagNumber, uint8_t *rBuffer, uint32_t *rLen) { + dprintf("Printer: C3XXFWDLusb: %s\n", __func__); + switch (tagNumber) { + case 0: // getPaperInfo + if (*rLen != 0x67) *rLen = 0x67; + if (rBuffer) memset(rBuffer, 0, *rLen); + return 1; + case 3: // getFirmwareVersion + if (*rLen != 0x99) *rLen = 0x99; + if (rBuffer) { + memset(rBuffer, 0, *rLen); + rBuffer[0] = 4; // firmware count + // bootFirmware + int i = 1; + memcpy(rBuffer + i, mainFirmware, sizeof(mainFirmware)); + // mainFirmware + i += 0x26; + memcpy(rBuffer + i, mainFirmware, sizeof(mainFirmware)); + // printParameterTable + i += 0x26; + memcpy(rBuffer + i, paramFirmware, sizeof(paramFirmware)); + // dspFirmware (C300 only) + i += 0x26; + memcpy(rBuffer + i, dspFirmware, 0x26); + } + return 1; + case 4: // getPrintCountInfo (C300 only) + if (!rBuffer) { + *rLen = 0x1C; + return 1; + } + int32_t bInfoC300[10] = {0}; + uint16_t printCounterC300; + bInfoC300[0] = 22; // printCounter0 + bInfoC300[1] = 23; // printCounter1 + bInfoC300[2] = 33; // feedRollerCount + bInfoC300[3] = 55; // cutterCount + bInfoC300[4] = 88; // headCount + bInfoC300[5] = 999; // ribbonRemain + bInfoC300[6] = 0; // dummy + if (*rLen <= 0x1Cu) { + memcpy(rBuffer, bInfoC300, *rLen); + } else { + bInfoC300[7] = 0; // TODO + if (*rLen > 0x20u) *rLen = 0x20; + memcpy(rBuffer, bInfoC300, *rLen); + } + break; + case 5: // getPrintCountInfo + if (!rBuffer) { + *rLen = 0x28; + return 1; + } + int32_t bInfo[10] = {0}; + bInfo[0] = 22; // printCounter0 + bInfo[1] = 23; // printCounter1 + bInfo[2] = 33; // feedRollerCount + bInfo[3] = 55; // cutterCount + bInfo[4] = 88; // headCount + bInfo[5] = 999; // ribbonRemain + bInfo[6] = 99; // holoHeadCount + if (*rLen <= 0x1Cu) { + memcpy(rBuffer, bInfo, *rLen); + } else { + bInfo[7] = 69; // paperCount + bInfo[8] = 21; // printCounter2 + bInfo[9] = 20; // holoPrintCounter + if (*rLen > 0x28u) *rLen = 0x28; + memcpy(rBuffer, bInfo, *rLen); + } + break; + case 26: // getPrinterSerial + if (*rLen != 8) *rLen = 8; + if (rBuffer) memcpy(rBuffer, &serialNo, 8); + return 1; + default: + dprintf("Printer: %s: Unknown parameter 'tagNumber' value %d.\n", __func__, tagNumber); + break; + } + return 1; +} + +int WINAPI fwdlusb_status(uint16_t *rResult) { + dprintf("Printer: C3XXFWDLusb: %s\n", __func__); + *rResult = 0; + return 1; +} + +int WINAPI fwdlusb_statusAll(uint8_t *idArray, uint16_t *rResultArray) { + dprintf("Printer: C3XXFWDLusb: %s\n", __func__); + for (int i = 0; *(uint8_t *)(idArray + i) != 255 && i < 128; ++i) { + *(uint16_t *)(rResultArray + 2 * i) = 0; + } + + return 1; +} + +int WINAPI fwdlusb_resetPrinter(uint16_t *rResult) { + dprintf("Printer: C3XXFWDLusb: %s\n", __func__); + *rResult = 0; + return 1; +} + +int WINAPI fwdlusb_getFirmwareVersion(uint8_t *buffer, int size) { + int8_t a; + uint32_t b = 0; + for (int32_t i = 0; i < size; ++i) { + if (*(int8_t *)(buffer + i) < 0x30 || *(int8_t *)(buffer + i) > 0x39) { + if (*(int8_t *)(buffer + i) < 0x41 || *(int8_t *)(buffer + i) > 0x46) { + if (*(int8_t *)(buffer + i) < 0x61 || *(int8_t *)(buffer + i) > 0x66) { + return 0; + } + a = *(int8_t *)(buffer + i) - 0x57; + } else { + a = *(int8_t *)(buffer + i) - 0x37; + } + } else { + a = *(int8_t *)(buffer + i) - 0x30; + } + b = a + 0x10 * b; + } + return b; +} + +int WINAPI fwdlusb_updateFirmware_main(uint8_t update, LPCSTR filename, uint16_t *rResult) { + DWORD result; + HANDLE fwFile = NULL; + DWORD bytesWritten = 0; + + if (filename) { + HANDLE hFile = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) return 0; + { + if (rResult) *rResult = 1005; + result = 0; + } + + DWORD read; + uint8_t buffer[0x40]; + result = ReadFile(hFile, buffer, 0x40, &read, NULL); + CloseHandle(hFile); + if (result && read > 0x24) { + uint8_t rBuffer[0x40] = {0}; + + memcpy(rBuffer, buffer + 0x2, 0x8); + memcpy(rBuffer + 0x8, buffer + 0xA, 0x10); + memcpy(rBuffer + 0x18, buffer + 0x20, 0xA); + *(rBuffer + 0x22) = (uint8_t)fwdlusb_getFirmwareVersion(buffer + 0x1A, 0x2); + *(rBuffer + 0x23) = (uint8_t)fwdlusb_getFirmwareVersion(buffer + 0x1C, 0x2); + memcpy(rBuffer + 0x24, buffer + 0x2A, 0x2); + + memcpy(mainFirmware, rBuffer, 0x40); + + fwFile = CreateFileW(printer_config.main_fw_path, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (fwFile != NULL) { + WriteFile(fwFile, rBuffer, 0x40, &bytesWritten, NULL); + CloseHandle(fwFile); + } + + if (rResult) *rResult = 0; + result = 1; + } else { + if (rResult) *rResult = 1005; + result = 0; + } + } else { + if (rResult) *rResult = 1006; + result = 0; + } + + return result; +} + +int WINAPI fwdlusb_updateFirmware_dsp(uint8_t update, LPCSTR filename, uint16_t *rResult) { + DWORD result; + HANDLE fwFile = NULL; + DWORD bytesWritten = 0; + + if (filename) { + HANDLE hFile = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) return 0; + { + if (rResult) *rResult = 1005; + result = 0; + } + + DWORD read; + uint8_t buffer[0x40]; + result = ReadFile(hFile, buffer, 0x40, &read, NULL); + CloseHandle(hFile); + if (result && read > 0x24) { + uint8_t rBuffer[0x40] = {0}; + + memcpy(rBuffer, buffer + 0x2, 8); + memcpy(rBuffer + 0x8, buffer + 0xA, 0x10); + memcpy(rBuffer + 0x18, buffer + 0x20, 0xA); + memcpy(rBuffer + 0x22, buffer + 0x1A, 0x1); + memcpy(rBuffer + 0x23, buffer + 0x1C, 0x1); + memcpy(rBuffer + 0x24, buffer + 0x2A, 0x2); + + memcpy(dspFirmware, rBuffer, 0x40); + + fwFile = CreateFileW(printer_config.dsp_fw_path, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (fwFile != NULL) { + WriteFile(fwFile, rBuffer, 0x40, &bytesWritten, NULL); + CloseHandle(fwFile); + } + + if (rResult) *rResult = 0; + result = 1; + } else { + if (rResult) *rResult = 1005; + result = 0; + } + } else { + if (rResult) *rResult = 1006; + result = 0; + } + + return result; +} + +int WINAPI fwdlusb_updateFirmware_param(uint8_t update, LPCSTR filename, uint16_t *rResult) { + DWORD result; + HANDLE fwFile = NULL; + DWORD bytesWritten = 0; + + if (filename) { + HANDLE hFile = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) return 0; + { + if (rResult) *rResult = 1005; + result = 0; + } + + DWORD read; + uint8_t buffer[0x40]; + result = ReadFile(hFile, buffer, 0x40, &read, NULL); + CloseHandle(hFile); + if (result && read > 0x24) { + uint8_t rBuffer[0x40] = {0}; + + memcpy(rBuffer, buffer + 0x2, 8); + memcpy(rBuffer + 0x8, buffer + 0xA, 0x10); + memcpy(rBuffer + 0x18, buffer + 0x20, 0xA); + memcpy(rBuffer + 0x22, buffer + 0x1A, 0x1); + memcpy(rBuffer + 0x23, buffer + 0x1C, 0x1); + memcpy(rBuffer + 0x24, buffer + 0x2A, 0x2); + + memcpy(paramFirmware, rBuffer, 0x40); + + fwFile = CreateFileW(printer_config.param_fw_path, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (fwFile != NULL) { + WriteFile(fwFile, rBuffer, 0x40, &bytesWritten, NULL); + CloseHandle(fwFile); + } + + if (rResult) *rResult = 0; + result = 1; + } else { + if (rResult) *rResult = 1005; + result = 0; + } + } else { + if (rResult) *rResult = 1006; + result = 0; + } + + return result; +} + +int WINAPI fwdlusb_updateFirmware(uint8_t update, LPCSTR filename, uint16_t *rResult) { + dprintf("Printer: C3XXFWDLusb: %s\n", __func__); + if (update == 1) { + return fwdlusb_updateFirmware_main(update, filename, rResult); + } else if (update == 2) { + return fwdlusb_updateFirmware_dsp(update, filename, rResult); + } else if (update == 3) { + return fwdlusb_updateFirmware_param(update, filename, rResult); + } else { + *rResult = 0; + return 1; + } +} + +int WINAPI fwdlusb_getFirmwareInfo_main(LPCSTR filename, uint8_t *rBuffer, uint32_t *rLen, uint16_t *rResult) { + DWORD result; + + if (filename) { + HANDLE hFile = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) return 0; + { + if (rResult) *rResult = 1005; + result = 0; + } + + DWORD read; + uint8_t buffer[0x40]; + result = ReadFile(hFile, buffer, 0x40, &read, NULL); + if (result && read > 0x24) { + memcpy(rBuffer, buffer + 0x2, 0x8); + memcpy(rBuffer + 0x8, buffer + 0xA, 0x10); + memcpy(rBuffer + 0x18, buffer + 0x20, 0xA); + *(rBuffer + 0x22) = (uint8_t)fwdlusb_getFirmwareVersion(buffer + 0x1A, 0x2); + *(rBuffer + 0x23) = (uint8_t)fwdlusb_getFirmwareVersion(buffer + 0x1C, 0x2); + memcpy(rBuffer + 0x24, buffer + 0x2A, 0x2); + + if (rResult) *rResult = 0; + result = 1; + } else { + if (rResult) *rResult = 1005; + result = 0; + } + } else { + if (rResult) *rResult = 1006; + result = 0; + } + + return result; +} + +int WINAPI fwdlusb_getFirmwareInfo_dsp(LPCSTR filename, uint8_t *rBuffer, uint32_t *rLen, uint16_t *rResult) { + DWORD result; + + if (filename) { + HANDLE hFile = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) return 0; + { + if (rResult) *rResult = 1005; + result = 0; + } + + DWORD read; + uint8_t buffer[0x40]; + result = ReadFile(hFile, buffer, 0x40, &read, NULL); + if (result && read > 0x24) { + memcpy(rBuffer, buffer + 0x2, 8); + memcpy(rBuffer + 0x8, buffer + 0xA, 0x10); + memcpy(rBuffer + 0x18, buffer + 0x20, 0xA); + memcpy(rBuffer + 0x22, buffer + 0x1A, 0x1); + memcpy(rBuffer + 0x23, buffer + 0x1C, 0x1); + memcpy(rBuffer + 0x24, buffer + 0x2A, 0x2); + + if (rResult) *rResult = 0; + result = 1; + } else { + if (rResult) *rResult = 1005; + result = 0; + } + } else { + if (rResult) *rResult = 1006; + result = 0; + } + + return result; +} + +int WINAPI fwdlusb_getFirmwareInfo_param(LPCSTR filename, uint8_t *rBuffer, uint32_t *rLen, uint16_t *rResult) { + DWORD result; + + if (filename) { + HANDLE hFile = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) return 0; + { + if (rResult) *rResult = 1005; + result = 0; + } + + DWORD read; + uint8_t buffer[0x40]; + result = ReadFile(hFile, buffer, 0x40, &read, NULL); + if (result && read > 0x24) { + memcpy(rBuffer, buffer + 0x2, 8); + memcpy(rBuffer + 0x8, buffer + 0xA, 0x10); + memcpy(rBuffer + 0x18, buffer + 0x20, 0xA); + memcpy(rBuffer + 0x22, buffer + 0x1A, 0x1); + memcpy(rBuffer + 0x23, buffer + 0x1C, 0x1); + memcpy(rBuffer + 0x24, buffer + 0x2A, 0x2); + + if (rResult) *rResult = 0; + result = 1; + } else { + if (rResult) *rResult = 1005; + result = 0; + } + } else { + if (rResult) *rResult = 1006; + result = 0; + } + + return result; +} + +int WINAPI fwdlusb_getFirmwareInfo(uint8_t update, LPCSTR filename, uint8_t *rBuffer, uint32_t *rLen, uint16_t *rResult) { + dprintf("Printer: C3XXFWDLusb: %s(update: %d, filename: %s)\n", __func__, update, filename); + if (!rBuffer) { + *rLen = 38; + return 1; + } + if (*rLen > 38) *rLen = 38; + if (update == 1) { + return fwdlusb_getFirmwareInfo_main(filename, rBuffer, rLen, rResult); + } else if (update == 2) { + return fwdlusb_getFirmwareInfo_dsp(filename, rBuffer, rLen, rResult); + } else if (update == 3) { + return fwdlusb_getFirmwareInfo_param(filename, rBuffer, rLen, rResult); + } else { + if (rResult) *rResult = 0; + return 1; + } +} + +int WINAPI fwdlusb_MakeThread(uint16_t maxCount) { + dprintf("Printer: C3XXFWDLusb: %s\n", __func__); + return 1; +} + +int WINAPI fwdlusb_ReleaseThread(uint16_t *rResult) { + dprintf("Printer: C3XXFWDLusb: %s\n", __func__); + *rResult = 0; + return 1; +} + +int WINAPI fwdlusb_AttachThreadCount(uint16_t *rCount, uint16_t *rMaxCount) { + dprintf("Printer: C3XXFWDLusb: %s\n", __func__); + *rCount = 0; + *rMaxCount = 1; + return 1; +} + +int WINAPI fwdlusb_getErrorLog(uint16_t index, uint8_t *rData, uint16_t *rResult) { + dprintf("Printer: C3XXFWDLusb: %s\n", __func__); + *rResult = 0; + return 1; +} + +// C3XXusb stubs + +int WINAPI chcusb_MakeThread(uint16_t maxCount) { + dprintf("Printer: C3XXusb: %s\n", __func__); + return 1; +} + +int WINAPI chcusb_open(uint16_t *rResult) { + // Seed random for card id generation + srand(time(NULL)); + generate_rfid(); + + dprintf("Printer: C3XXusb: %s\n", __func__); + *rResult = 0; + return 1; +} + +void WINAPI chcusb_close() { + dprintf("Printer: C3XXusb: %s\n", __func__); +} + +int WINAPI chcusb_ReleaseThread(uint16_t *rResult) { + dprintf("Printer: C3XXusb: %s\n", __func__); + return 1; +} + +int WINAPI chcusb_listupPrinter(uint8_t *rIdArray) { + dprintf("Printer: C3XXusb: %s\n", __func__); + memset(rIdArray, 0xFF, 0x80); + rIdArray[0] = idNumber; + return 1; +} + +int WINAPI chcusb_listupPrinterSN(uint64_t *rSerialArray) { + dprintf("Printer: C3XXusb: %s\n", __func__); + memset(rSerialArray, 0xFF, 0x400); + rSerialArray[0] = serialNo; + return 1; +} + +int WINAPI chcusb_selectPrinter(uint8_t printerId, uint16_t *rResult) { + dprintf("Printer: C3XXusb: %s\n", __func__); + *rResult = 0; + return 1; +} + +int WINAPI chcusb_selectPrinterSN(uint64_t printerSN, uint16_t *rResult) { + dprintf("Printer: C3XXusb: %s\n", __func__); + *rResult = 0; + return 1; +} + +int WINAPI chcusb_getPrinterInfo(uint16_t tagNumber, uint8_t *rBuffer, uint32_t *rLen) { + // dprintf("Printer: C3XXusb: %s\n", __func__); + + switch (tagNumber) { + // getPaperInfo + case 0: + if (*rLen != 0x67) *rLen = 0x67; + if (rBuffer) memset(rBuffer, 0, *rLen); + break; + + case 3: // getFirmwareVersion + if (*rLen != 0x99) *rLen = 0x99; + if (rBuffer) { + memset(rBuffer, 0, *rLen); + rBuffer[0] = 4; // firmware count + // bootFirmware + int i = 1; + memcpy(rBuffer + i, mainFirmware, sizeof(mainFirmware)); + // mainFirmware + i += 0x26; + memcpy(rBuffer + i, mainFirmware, sizeof(mainFirmware)); + // printParameterTable + i += 0x26; + memcpy(rBuffer + i, paramFirmware, sizeof(paramFirmware)); + // dspFirmware (C300 only) + i += 0x26; + memcpy(rBuffer + i, dspFirmware, 0x26); + } + break; + + case 4: // getPrintCountInfo (C300 only) + if (!rBuffer) { + *rLen = 0x1C; + return 1; + } + int32_t bInfoC300[10] = {0}; + bInfoC300[0] = 22; // printCounter0 + bInfoC300[1] = 23; // printCounter1 + bInfoC300[2] = 33; // feedRollerCount + bInfoC300[3] = 55; // cutterCount + bInfoC300[4] = 88; // headCount + bInfoC300[5] = 999; // ribbonRemain + bInfoC300[6] = 0; // dummy + if (*rLen <= 0x1Cu) { + memcpy(rBuffer, bInfoC300, *rLen); + } else { + bInfoC300[7] = 0; // TODO + if (*rLen > 0x20u) *rLen = 0x20; + memcpy(rBuffer, bInfoC300, *rLen); + } + break; + + case 5: // getPrintCountInfo2 (C310A and later) + if (!rBuffer) { + *rLen = 0x28; + return 1; + } + int32_t bInfo[10] = {0}; + bInfo[0] = 22; // printCounter0 + bInfo[1] = 23; // printCounter1 + bInfo[2] = 33; // feedRollerCount + bInfo[3] = 55; // cutterCount + bInfo[4] = 88; // headCount + bInfo[5] = 999; // ribbonRemain + bInfo[6] = 99; // holoHeadCount + if (*rLen <= 0x1Cu) { + memcpy(rBuffer, bInfo, *rLen); + } else { + bInfo[7] = 69; // paperCount + bInfo[8] = 21; // printCounter2 + bInfo[9] = 20; // holoPrintCounter + if (*rLen > 0x28u) *rLen = 0x28; + memcpy(rBuffer, bInfo, *rLen); + } + break; + + case 6: // pageStatusInfo + *rLen = 32; + if (rBuffer) { + memset(rBuffer, 0, 32); + rBuffer[0] = 88; // holoRemain + rBuffer[1] = 0; + rBuffer[2] = 0; + rBuffer[3] = 0; + } + break; + + case 7: // svc + *rLen = 2; + if (rBuffer) { + memset(rBuffer, 0, 2); + rBuffer[0] = 0; // mainError + rBuffer[1] = 0; // subError + } + break; + + case 8: // printStandby + *rLen = 1; + if (awaitingCardExit) + *rBuffer = 0xF0; + else if (STATUS == 1) + *rBuffer = 1; + else + *rBuffer = 0; + break; + + case 16: // memoryInfo + *rLen = 18; + if (rBuffer) memset(rBuffer, 0, 18); + break; + + case 20: // printMode + dprintf("Printer: C3XXusb: Unimpl tagNumber 20\n"); + break; + + case 26: // getPrinterSerial + if (*rLen != 8) *rLen = 8; + if (rBuffer) memcpy(rBuffer, &serialNo, 8); + break; + + case 30: // TODO + dprintf("Printer: C3XXusb: Unimpl tagNumber 30\n"); + break; + + case 31: // TODO, possibly CardRFIDCheck? + *rLen = 1; + if (rBuffer) memset(rBuffer, 0, 1); + break; + + case 40: // temperature + *rLen = 10; + if (rBuffer) { + memset(rBuffer, 0, 10); + rBuffer[0] = 1; + rBuffer[1] = 2; + rBuffer[2] = 3; + } + break; + + case 50: // errHistory + *rLen = 61; + if (rBuffer) memset(rBuffer, 0, 61); + break; + + case 60: // getToneTable + *rLen = 6; + if (rBuffer) memset(rBuffer, 0, 6); + break; + + default: + dprintf("Printer: unknown tagNumber, %d", tagNumber); + break; + } + + return 1; +} + +int WINAPI chcusb_imageformat( + uint16_t format, + uint16_t ncomp, + uint16_t depth, + uint16_t width, + uint16_t height, + uint8_t *image, + uint16_t *rResult) { + return chcusb_imageformat_330(format, ncomp, depth, width, height, rResult); +} + +int WINAPI chcusb_imageformat_330( + uint16_t format, + uint16_t ncomp, + uint16_t depth, + uint16_t width, + uint16_t height, + uint16_t *rResult) { + dprintf("Printer: C3XXusb: %s\n", __func__); + + WIDTH = width; + HEIGHT = height; + + *rResult = 0; + return 1; +} + +int __fastcall chcusb_setmtf(int32_t *mtf) { + dprintf("Printer: C3XXusb: %s\n", __func__); + + memcpy(MTF, mtf, sizeof(MTF)); + return 1; +} + +int WINAPI chcusb_makeGamma(uint16_t k, uint8_t *intoneR, uint8_t *intoneG, uint8_t *intoneB) { + dprintf("Printer: C3XXusb: %s\n", __func__); + + uint8_t tone; + int32_t value; + double power; + + double factor = (double)k / 100.0; + + for (int i = 0; i < 256; ++i) { + power = pow((double)i, factor); + value = (int)(power / pow(255.0, factor) * 255.0); + + if (value > 255) + tone = 255; + if (value >= 0) + tone = value; + else + tone = 0; + + if (intoneR) + *(uint8_t *)(intoneR + i) = tone; + if (intoneG) + *(uint8_t *)(intoneG + i) = tone; + if (intoneB) + *(uint8_t *)(intoneB + i) = tone; + } + + return 1; +} + +int WINAPI chcusb_setIcctable( + LPCSTR icc1, + LPCSTR icc2, + uint16_t intents, + uint8_t *intoneR, + uint8_t *intoneG, + uint8_t *intoneB, + uint8_t *outtoneR, + uint8_t *outtoneG, + uint8_t *outtoneB, + uint16_t *rResult) { + dprintf("Printer: C3XXusb: %s\n", __func__); + + if (intoneR != NULL && intoneG != NULL && intoneB != NULL && outtoneR != NULL && outtoneG != NULL && outtoneB != NULL) { + for (int i = 0; i < 256; ++i) { + intoneR[i] = i; + intoneG[i] = i; + intoneB[i] = i; + outtoneR[i] = i; + outtoneG[i] = i; + outtoneB[i] = i; + } + } + + *rResult = 0; + return 1; +} + +int WINAPI chcusb_copies(uint16_t copies, uint16_t *rResult) { + dprintf("Printer: C3XXusb: %s\n", __func__); + *rResult = 0; + return 1; +} + +int WINAPI chcusb_status(uint16_t *rResult) { + // dprintf("Printer: C3XXusb: %s\n", __func__); + if (check_for_wait_time()) { + *rResult = 2203; + } else { + *rResult = 0; + } + return 1; +} + +int WINAPI chcusb_statusAll(uint8_t *idArray, uint16_t *rResultArray) { + dprintf("Printer: C3XXusb: %s\n", __func__); + + for (int i = 0; *(uint8_t *)(idArray + i) != 255 && i < 128; ++i) { + *(uint16_t *)(rResultArray + 2 * i) = 0; + } + + return 1; +} + +int WINAPI chcusb_startpage(uint16_t postCardState, uint16_t *pageId, uint16_t *rResult) { + dprintf("Printer: C3XXusb: %s\n", __func__); + + STATUS = 2; + + *pageId = 1; + *rResult = 0; + return 1; +} + +int WINAPI chcusb_startpage_300(uint16_t postCardState, uint16_t *rResult) { + dprintf("Printer: C300usb: %s\n", __func__); + + STATUS = 2; + + *rResult = 0; + return 1; +} + +int WINAPI chcusb_endpage(uint16_t *rResult) { + dprintf("Printer: C3XXusb: %s\n", __func__); + + if (printer_config.wait_time > 0){ + finishTime = GetTickCount64() + printer_config.wait_time; + dprintf("Printer: Waiting for %dms...\n", printer_config.wait_time); + } + awaitingCardExit = true; + + *rResult = 0; + return 1; +} + +int WINAPI chcusb_write(uint8_t *data, uint32_t *writeSize, uint16_t *rResult) { + SYSTEMTIME t; + GetLocalTime(&t); + + wchar_t dumpPath[MAX_PATH]; + uint8_t metadata[44]; + swprintf_s( + dumpPath, MAX_PATH, + L"%s\\C3XX_%04d%02d%02d_%02d%02d%02d.bmp", + printer_out_path, t.wYear, t.wMonth, t.wDay, t.wHour, t.wMinute, t.wSecond); + + memcpy(metadata, cardRFID, 12); + memcpy(metadata + 12, cardDataBuffer, 32); + + WriteDataToBitmapFile(dumpPath, 24, WIDTH, HEIGHT, data, *writeSize, metadata, 44, rotate180); + // WriteArrayToFile(dumpPath, data, IMAGE_SIZE, FALSE); + dprintf("Printer: C3XXusb: %s\n", __func__); + dwprintf(L"Printer: File written: %s\n", dumpPath); + + *rResult = 0; + + return 1; +} + +int WINAPI chcusb_writeLaminate(uint8_t *data, uint32_t *writeSize, uint16_t *rResult) { + SYSTEMTIME t; + GetLocalTime(&t); + + wchar_t dumpPath[MAX_PATH]; + swprintf_s( + dumpPath, MAX_PATH, + L"%s\\C3XX_%04d%02d%02d_%02d%02d%02d_laminate.bmp", + printer_out_path, t.wYear, t.wMonth, t.wDay, t.wHour, t.wMinute, t.wSecond); + + // WriteDataToBitmapFile(dumpPath, 8, WIDTH, HEIGHT, data, HOLO_SIZE, NULL, 0, rotate180); + dprintf("Printer: C3XXusb: %s\n", __func__); + + // *writeSize = written; + *rResult = 0; + + return 1; +} + +int WINAPI chcusb_writeHolo(uint8_t *data, uint32_t *writeSize, uint16_t *rResult) { + SYSTEMTIME t; + GetLocalTime(&t); + + wchar_t dumpPath[MAX_PATH]; + swprintf_s( + dumpPath, MAX_PATH, + L"%s\\C3XX_%04d%02d%02d_%02d%02d%02d_holo.bmp", + printer_out_path, t.wYear, t.wMonth, t.wDay, t.wHour, t.wMinute, t.wSecond); + + WriteDataToBitmapFile(dumpPath, 8, WIDTH, HEIGHT, data, HOLO_SIZE, NULL, 0, rotate180); + dprintf("Printer: C3XXusb: %s\n", __func__); + + *writeSize = HOLO_SIZE; + *rResult = 0; + + return 1; +} + +int WINAPI chcusb_setPrinterInfo(uint16_t tagNumber, uint8_t *rBuffer, uint32_t *rLen, uint16_t *rResult) { + dprintf("Printer: C3XXusb: %s\n", __func__); + + switch (tagNumber) { + case 0: // setPaperInfo + memcpy(PAPERINFO, rBuffer, sizeof(PAPERINFO)); + break; + case 20: // setPolishInfo + memcpy(POLISH, rBuffer, sizeof(POLISH)); + break; + default: + break; + } + + *rResult = 0; + return 1; +} + +int WINAPI chcusb_getGamma(LPCSTR filename, uint8_t *r, uint8_t *g, uint8_t *b, uint16_t *rResult) { + dprintf("Printer: C3XXusb: %s\n", __func__); + + for (int i = 0; i < 256; ++i) { + r[i] = i; + g[i] = i; + b[i] = i; + } + + *rResult = 0; + return 1; +} + +int WINAPI chcusb_getMtf(LPCSTR filename, int32_t *mtf, uint16_t *rResult) { + dprintf("Printer: C3XXusb: %s\n", __func__); + + HANDLE hFile = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) return 0; + + DWORD read; + uint8_t buffer[0x40]; + BOOL result = ReadFile(hFile, buffer, 0x40, &read, NULL); + if (!result) return 0; + + int a, c; + int b = 1; + int d = c = 0; + + memset(mtf, 0, sizeof(MTF)); + + for (DWORD i = 0; i < read; i++) { + a = buffer[i] - 0x30; + if (a == -3 && c == 0) { + b = -1; + } else if (a >= 0 && a <= 9) { + mtf[d] = mtf[d] * 10 + a; + c++; + } else if (c > 0) { + mtf[d] *= b; + b = 1; + c = 0; + d++; + } + if (d > 9) break; + } + + *rResult = 0; + return 1; +} + +int WINAPI chcusb_cancelCopies(uint16_t pageId, uint16_t *rResult) { + dprintf("Printer: C3XXusb: %s\n", __func__); + *rResult = 0; + return 1; +} + +int WINAPI chcusb_setPrinterToneCurve(uint16_t type, uint16_t number, uint16_t *data, uint16_t *rResult) { + dprintf("Printer: C3XXusb: %s\n", __func__); + if ((type > 0 && type < 3) && (number > 0 && number < 3)) { + CURVE[type][number] = *data; + } + *rResult = 0; + return 1; +} + +int WINAPI chcusb_getPrinterToneCurve(uint16_t type, uint16_t number, uint16_t *data, uint16_t *rResult) { + dprintf("Printer: C3XXusb: %s\n", __func__); + if ((type > 0 && type < 3) && (number > 0 && number < 3)) { + *data = CURVE[type][number]; + } + *rResult = 0; + return 1; +} + +int WINAPI chcusb_blinkLED(uint16_t *rResult) { + dprintf("Printer: C3XXusb: %s\n", __func__); + *rResult = 0; + return 1; +} + +int WINAPI chcusb_resetPrinter(uint16_t *rResult) { + dprintf("Printer: C3XXusb: %s\n", __func__); + *rResult = 0; + return 1; +} + +int WINAPI chcusb_AttachThreadCount(uint16_t *rCount, uint16_t *rMaxCount) { + dprintf("Printer: C3XXusb: %s\n", __func__); + *rCount = 0; + *rMaxCount = 1; + return 1; +} + +int WINAPI chcusb_getPrintIDStatus(uint16_t pageId, uint8_t *rBuffer, uint16_t *rResult) { + // dprintf("Printer: C3XXusb: %s\n", __func__); + memset(rBuffer, 0, 8); + + if (check_for_wait_time()) { + *((uint16_t*)(rBuffer + 6)) = 2203; + } else if (STATUS > 1) { + STATUS = 0; + *((uint16_t*)(rBuffer + 6)) = 2212; + } else { + *((uint16_t*)(rBuffer + 6)) = 2300; + } + + *rResult = 0; + return 1; +} + +int WINAPI chcusb_setPrintStandby(uint16_t position, uint16_t *rResult) { + dprintf("Printer: C3XXusb: %s\n", __func__); + + if (STATUS == 0) + { + STATUS = 1; + *rResult = 2100; + } + else + { + *rResult = 0; + } + + return 1; +} + +int WINAPI chcusb_setPrintStandby_300(uint16_t *rResult) { + dprintf("Printer: C300usb: %s\n", __func__); + + *rResult = 0; + if (awaitingCardExit){ // 300 does not use exitCard, so reset this for getPrinterInfo. + awaitingCardExit = false; + finishTime = 0; + STATUS = 1; + } + if (STATUS == 0) + { + STATUS = 1; + } + + return 1; +} + +int WINAPI chcusb_testCardFeed(uint16_t mode, uint16_t times, uint16_t *rResult) { + dprintf("Printer: C3XXusb: %s\n", __func__); + *rResult = 0; + return 1; +} + +int WINAPI chcusb_exitCard(uint16_t *rResult) { + dprintf("Printer: C3XXusb: %s\n", __func__); + + if (check_for_wait_time()) { + *rResult = 2203; + return 0; + } else { + awaitingCardExit = false; + finishTime = 0; + generate_rfid(); + } + + *rResult = 0; + return 1; +} + +int WINAPI chcusb_getCardRfidTID(uint8_t *rCardTID, uint16_t *rResult) { + dprintf("Printer: C3XXusb: %s\n", __func__); + + memcpy(rCardTID, cardRFID, sizeof(cardRFID)); + *rResult = 2405; + return 1; +} + +int WINAPI chcusb_commCardRfidReader(uint8_t *sendData, uint8_t *rRecvData, uint32_t sendSize, uint32_t *rRecvSize, uint16_t *rResult) { + uint8_t off; + + dprintf("Printer: C3XXusb: %s\n", __func__); + // dprintf("Printer: C3XXusb: commCardRfidReader send buffer: \n"); + // dump(sendData, sendSize); + + *rResult = 0; + + switch (sendData[0]) { + case 0x02: + memset(rRecvData, 0, 35); + rRecvData[0] = 2; + rRecvData[2] = 32; + memcpy(rRecvData + 3, cardDataBuffer, 32); + *rRecvSize = 35; + break; + case 0x03: + off = (sendData[4] - 1) * 2; + cardDataBuffer[off] = sendData[5]; + cardDataBuffer[off + 1] = sendData[6]; + if (sendData[4] == 0x10) { + dprintf("Printer: C3XXusb: commCardRfidReader card data buffer: \n"); + dump(cardDataBuffer, 0x20); + } + memset(rRecvData, 0, 3); + rRecvData[0] = 0x03; + *rRecvSize = 3; + break; + case 0x84: + memset(rRecvData, 0, 4); + rRecvData[0] = 0x84; + rRecvData[2] = 1; + rRecvData[3] = 0x90; + *rRecvSize = 4; + break; + case 0x85: + memset(rRecvData, 0, 12); + rRecvData[0] = 0x85; + rRecvData[2] = 9; + memcpy(rRecvData + 3, (void *)"837-15345", 9); + *rRecvSize = 12; + break; + case 0x42: + memset(rRecvData, 0, 4); + rRecvData[0] = 0x42; + rRecvData[2] = 1; + rRecvData[3] = 0x91; + *rRecvSize = 4; + break; + default: + memset(rRecvData, 0, 3); + rRecvData[0] = sendData[0]; + *rRecvSize = 3; + break; + } + + // dprintf("Printer: C3XXusb: commCardRfidReader recv buffer: \n"); + // dump(rRecvData, *rRecvSize); + + return 1; +} + +int WINAPI chcusb_updateCardRfidReader(uint8_t *data, uint32_t size, uint16_t *rResult) { + dprintf("Printer: C3XXusb: %s\n", __func__); + *rResult = 0; + return 1; +} + +int WINAPI chcusb_getErrorLog(uint16_t index, uint8_t *rData, uint16_t *rResult) { + dprintf("Printer: C3XXusb: %s\n", __func__); + *rResult = 0; + return 1; +} + +int WINAPI chcusb_getErrorStatus(uint16_t *rBuffer) { + dprintf("Printer: C3XXusb: %s\n", __func__); + memset(rBuffer, 0, 0x80); + return 1; +} + +int WINAPI chcusb_setCutList(uint8_t *rData, uint16_t *rResult) { + dprintf("Printer: C3XXusb: %s\n", __func__); + *rResult = 0; + return 1; +} + +int WINAPI chcusb_setLaminatePattern(uint16_t index, uint8_t *rData, uint16_t *rResult) { + dprintf("Printer: C3XXusb: %s\n", __func__); + *rResult = 0; + return 1; +} + +int WINAPI chcusb_color_adjustment(LPCSTR filename, int32_t a2, int32_t a3, int16_t a4, int16_t a5, int64_t a6, int64_t a7, uint16_t *rResult) { + dprintf("Printer: C3XXusb: %s\n", __func__); + *rResult = 0; + return 1; +} + +int WINAPI chcusb_color_adjustmentEx(int32_t a1, int32_t a2, int32_t a3, int16_t a4, int16_t a5, int64_t a6, int64_t a7, uint16_t *rResult) { + dprintf("Printer: C3XXusb: %s\n", __func__); + *rResult = 0; + return 1; +} + +int WINAPI chcusb_getEEPROM(uint8_t index, uint8_t *rData, uint16_t *rResult) { + dprintf("Printer: C3XXusb: %s\n", __func__); + *rResult = 0; + return 1; +} + +int WINAPI chcusb_setParameter(uint8_t a1, uint32_t a2, uint16_t *rResult) { + dprintf("Printer: C3XXusb: %s\n", __func__); + *rResult = 0; + return 1; +} + +int WINAPI chcusb_getParameter(uint8_t a1, uint8_t *a2, uint16_t *rResult) { + dprintf("Printer: C3XXusb: %s\n", __func__); + *rResult = 0; + return 1; +} + +int WINAPI chcusb_universal_command(int32_t a1, uint8_t a2, int32_t a3, uint8_t *a4, uint16_t *rResult) { + dprintf("Printer: C3XXusb: %s\n", __func__); + *rResult = 0; + return 1; +} + +/* PrintDll hooks */ + +int CFW_AttachThreadCount(const void *handle, uint16_t *rCount, uint16_t *rMaxCount) +{ + return fwdlusb_AttachThreadCount(rCount, rMaxCount); +} + +void CFW_close(const void *handle) +{ + fwdlusb_close(); +} + +int CFW_getErrorLog(const void *handle, uint16_t index, uint8_t *rData, uint16_t *rResult) +{ + return fwdlusb_getErrorLog(index, rData, rResult); +} + +int CFW_getFirmwareInfo(const void *handle, uint8_t update, LPCSTR filename, uint8_t *rBuffer, uint32_t *rLen, uint16_t *rResult) +{ + return fwdlusb_getFirmwareInfo(update, filename, rBuffer, rLen, rResult); +} + +int CFW_getPrinterInfo(const void *handle, uint16_t tagNumber, uint8_t *rBuffer, uint32_t *rLen) +{ + return fwdlusb_getPrinterInfo(tagNumber, rBuffer, rLen); +} + +int CFW_init(LPCSTR dllpath) +{ + dprintf("Printer PrintDLL: %s (dllpath: %s)\n", __func__, dllpath); + return 1; +} + +int CFW_listupPrinter(const void *handle, uint8_t *rIdArray) +{ + return fwdlusb_listupPrinter(rIdArray); +} + +int CFW_listupPrinterSN(const void *handle, uint64_t *rSerialArray) +{ + return fwdlusb_listupPrinterSN(rSerialArray); +} + +int CFW_MakeThread(const void *handle, uint16_t maxCount) +{ + return fwdlusb_MakeThread(maxCount); +} + +int CFW_open(const void *handle, uint16_t *rResult) +{ + return fwdlusb_open(rResult); +} + +int CFW_ReleaseThread(const void *handle, uint16_t *rResult) +{ + return fwdlusb_ReleaseThread(rResult); +} + +int CFW_resetPrinter(const void *handle, uint16_t *rResult) +{ + return fwdlusb_resetPrinter(rResult); +} + +int CFW_selectPrinter(const void *handle, uint8_t printerId, uint16_t *rResult) +{ + return fwdlusb_selectPrinter(printerId, rResult); +} + +int CFW_selectPrinterSN(const void *handle, uint64_t printerSN, uint16_t *rResult) +{ + return fwdlusb_selectPrinterSN(printerSN, rResult); +} + +int CFW_status(const void *handle, uint16_t *rResult) +{ + return fwdlusb_status(rResult); +} + +int CFW_statusAll(const void *handle, uint8_t *idArray, uint16_t *rResultArray) +{ + return fwdlusb_statusAll(idArray, rResultArray); +} + +void CFW_term(const void *handle) +{ + dprintf("Printer PrintDLL: %s\n", __func__); +} + +int CFW_updateFirmware(const void *handle, uint8_t update, LPCSTR filename, uint16_t *rResult) +{ + return fwdlusb_updateFirmware(update, filename, rResult); +} + +int CHCUSB_AttachThreadCount(const void *handle, uint16_t *rCount, uint16_t *rMaxCount) +{ + return chcusb_AttachThreadCount(rCount, rMaxCount); +} + +int CHCUSB_MakeThread(const void *handle, uint16_t maxCount) +{ + return chcusb_MakeThread(maxCount); +} + +int CHCUSB_ReleaseThread(const void *handle, uint16_t *rResult) +{ + return chcusb_ReleaseThread(rResult); +} + +int CHCUSB_blinkLED(const void *handle, uint16_t *rResult) +{ + return chcusb_blinkLED(rResult); +} + +int CHCUSB_cancelCopies(const void *handle, uint16_t pageId, uint16_t *rResult) +{ + return chcusb_cancelCopies(pageId, rResult); +} + +void CHCUSB_close(const void *handle) +{ + chcusb_close(); +} + +int CHCUSB_commCardRfidReader(const void *handle, uint8_t *sendData, uint8_t *rRecvData, uint32_t sendSize, uint32_t *rRecvSize, uint16_t *rResult) +{ + return chcusb_commCardRfidReader(sendData, rRecvData, sendSize, rRecvSize, rResult); +} + +int CHCUSB_copies(const void *handle, uint16_t copies, uint16_t *rResult) +{ + return chcusb_copies(copies, rResult); +} + +int CHCUSB_endpage(const void *handle, uint16_t *rResult) +{ + return chcusb_endpage(rResult); +} + +int CHCUSB_exitCard(const void *handle, uint16_t *rResult) +{ + return chcusb_exitCard(rResult); +} + +int CHCUSB_getCardRfidTID(const void *handle, uint8_t *rCardTID, uint16_t *rResult) +{ + return chcusb_getCardRfidTID(rCardTID, rResult); +} + +int CHCUSB_getErrorLog(const void *handle, uint16_t index, uint8_t *rData, uint16_t *rResult) +{ + return chcusb_getErrorLog(index, rData, rResult); +} + +int CHCUSB_getErrorStatus(const void *handle, uint16_t *rBuffer) +{ + return chcusb_getErrorStatus(rBuffer); +} + +int CHCUSB_getGamma(const void *handle, LPCSTR filename, uint8_t *r, uint8_t *g, uint8_t *b, uint16_t *rResult) +{ + return chcusb_getGamma(filename, r, g, b, rResult); +} + +int CHCUSB_getMtf(const void *handle, LPCSTR filename, int32_t *mtf, uint16_t *rResult) +{ + return chcusb_getMtf(filename, mtf, rResult); +} + +int CHCUSB_getPrintIDStatus(const void *handle, uint16_t pageId, uint8_t *rBuffer, uint16_t *rResult) +{ + return chcusb_getPrintIDStatus(pageId, rBuffer, rResult); +} + +int CHCUSB_getPrinterInfo(const void *handle, uint16_t tagNumber, uint8_t *rBuffer, uint32_t *rLen) +{ + return chcusb_getPrinterInfo(tagNumber, rBuffer, rLen); +} + +int CHCUSB_getPrinterToneCurve(const void *handle, uint16_t type, uint16_t number, uint16_t *data, uint16_t *rResult) +{ + return chcusb_getPrinterToneCurve(type, number, data, rResult); +} + +int CHCUSB_imageformat(const void *handle, uint16_t format, uint16_t ncomp, uint16_t depth, uint16_t width, uint16_t height, uint8_t *inputImage, uint16_t *rResult) +{ + return chcusb_imageformat(format, ncomp, depth, width, height, inputImage, rResult); +} + +int CHCUSB_init(LPCSTR dllpath) +{ + dprintf("Printer PrintDLL: %s (dllpath: %s)\n", __func__, dllpath); + return 1; +} + +int CHCUSB_listupPrinter(const void *handle, uint8_t *rIdArray) +{ + return chcusb_listupPrinter(rIdArray); +} + +int CHCUSB_listupPrinterSN(const void *handle, uint64_t *rSerialArray) +{ + return chcusb_listupPrinterSN(rSerialArray); +} + +int CHCUSB_makeGamma(const void *handle, uint16_t k, uint8_t *intoneR, uint8_t *intoneG, uint8_t *intoneB) +{ + return chcusb_makeGamma(k, intoneR, intoneG, intoneB); +} + +int CHCUSB_open(const void *handle, uint16_t *rResult) +{ + return chcusb_open(rResult); +} + +int CHCUSB_resetPrinter(const void *handle, uint16_t *rResult) +{ + return chcusb_resetPrinter(rResult); +} + +int CHCUSB_selectPrinter(const void *handle, uint8_t printerId, uint16_t *rResult) +{ + return chcusb_selectPrinter(printerId, rResult); +} + +int CHCUSB_selectPrinterSN(const void *handle, uint64_t printerSN, uint16_t *rResult) +{ + return chcusb_selectPrinterSN(printerSN, rResult); +} + +int CHCUSB_setIcctableProfile(const void *handle, LPCSTR icc1, LPCSTR icc2, uint16_t intents, uint16_t *rResult) +{ + dprintf("Printer PrintDLL: %s (icc1: %s, icc2: %s)\n", + __func__, icc1, icc2); + *rResult = 0; + return 1; +} + +int CHCUSB_setIcctable(const void *handle, uint16_t intents, uint8_t *intoneR, uint8_t *intoneG, uint8_t *intoneB, uint8_t *outtoneR, uint8_t *outtoneG, uint8_t *outtoneB, uint16_t *rResult) +{ + return chcusb_setIcctable("CFW", "CFW", intents, intoneR, intoneG, intoneB, outtoneR, outtoneG, outtoneB, rResult); +} + +int CHCUSB_setPrintStandby(const void *handle, uint16_t position, uint16_t *rResult) +{ + return chcusb_setPrintStandby(position, rResult); +} + +int CHCUSB_setPrinterInfo(const void *handle, uint16_t tagNumber, uint8_t *rBuffer, uint32_t *rLen, uint16_t *rResult) +{ + return chcusb_setPrinterInfo(tagNumber, rBuffer, rLen, rResult); +} + +int CHCUSB_setPrinterToneCurve(const void *handle, uint16_t type, uint16_t number, uint16_t *data, uint16_t *rResult) +{ + return chcusb_setPrinterToneCurve(type, number, data, rResult); +} + +int CHCUSB_setmtf(const void *handle, int32_t *mtf) +{ + return chcusb_setmtf(mtf); +} + +int CHCUSB_startpage(const void *handle, uint16_t postCardState, uint16_t *pageId, uint16_t *rResult) +{ + return chcusb_startpage(postCardState, pageId, rResult); +} + +int CHCUSB_status(const void *handle, uint16_t *rResult) +{ + return chcusb_status(rResult); +} + +int CHCUSB_statusAll(const void *handle, uint8_t *idArray, uint16_t *rResultArray) +{ + return chcusb_statusAll(idArray, rResultArray); +} + +int CHCUSB_testCardFeed(const void *handle, uint16_t mode, uint16_t times, uint16_t *rResult) +{ + return chcusb_testCardFeed(mode, times, rResult); +} + +void CHCUSB_term(const void *handle) +{ + dprintf("Printer PrintDLL: %s\n", __func__); +} + +int CHCUSB_updateCardRfidReader(const void *handle, uint8_t *data, uint32_t size, uint16_t *rResult) +{ + return chcusb_updateCardRfidReader(data, size, rResult); +} + +int CHCUSB_write(const void *handle, uint8_t *data, uint32_t offset, uint32_t *writeSize, uint16_t *rResult) +{ + return chcusb_write(data, writeSize, rResult); +} + +int CHCUSB_writeHolo(const void *handle, uint8_t *data, uint32_t offset, uint32_t *writeSize, uint16_t *rResult) +{ + return chcusb_writeHolo(data, writeSize, rResult); +} + +int CHCUSB_writeLaminate(const void *handle, uint8_t *data, uint32_t offset, uint32_t *writeSize, uint16_t *rResult) +{ + return chcusb_writeLaminate(data, writeSize, rResult); +} + +// copy pasted from https://dev.s-ul.net/domeori/c310emu +#define BITMAPHEADERSIZE 0x36 + +DWORD ConvertDataToBitmap( + DWORD dwBitCount, + DWORD dwWidth, DWORD dwHeight, + PBYTE pbInput, DWORD cbInput, + PBYTE pbOutput, DWORD cbOutput, + PDWORD pcbResult, + bool pFlip) { + if (!pbInput || !pbOutput || dwBitCount < 8) return -3; + + if (cbInput < (dwWidth * dwHeight * dwBitCount / 8)) return -3; + + PBYTE pBuffer = (PBYTE)malloc(cbInput); + if (!pBuffer) return -2; + + BYTE dwColors = (BYTE)(dwBitCount / 8); + if (!dwColors) return -1; + + UINT16 cbColors; + RGBQUAD pbColors[256]; + + switch (dwBitCount) { + case 1: + cbColors = 1; + break; + case 2: + cbColors = 4; + break; + case 4: + cbColors = 16; + break; + case 8: + cbColors = 256; + break; + default: + cbColors = 0; + break; + } + + if (cbColors) { + BYTE dwStep = (BYTE)(256 / cbColors); + + for (UINT16 i = 0; i < cbColors; ++i) { + pbColors[i].rgbRed = dwStep * i; + pbColors[i].rgbGreen = dwStep * i; + pbColors[i].rgbBlue = dwStep * i; + pbColors[i].rgbReserved = 0; + } + } + + DWORD dwTable = cbColors * sizeof(RGBQUAD); + DWORD dwOffset = BITMAPHEADERSIZE + dwTable; + + // calculate the padded row size, again + DWORD dwLineSize = (dwWidth * dwBitCount / 8 + 3) & ~3; + + BITMAPFILEHEADER bFile = {0}; + BITMAPINFOHEADER bInfo = {0}; + + bFile.bfType = 0x4D42; // MAGIC + bFile.bfSize = dwOffset + cbInput; + bFile.bfOffBits = dwOffset; + + bInfo.biSize = sizeof(BITMAPINFOHEADER); + bInfo.biWidth = dwWidth; + bInfo.biHeight = dwHeight; + bInfo.biPlanes = 1; + bInfo.biBitCount = (WORD)dwBitCount; + bInfo.biCompression = BI_RGB; + bInfo.biSizeImage = cbInput; + + if (cbOutput < bFile.bfSize) return -1; + + // Flip the image (if necessary) and add padding to each row + if (pFlip) { + for (size_t i = 0; i < dwHeight; i++) { + for (size_t j = 0; j < dwWidth; j++) { + for (size_t k = 0; k < dwColors; k++) { + // Calculate the position in the padded buffer + // Make sure to also flip the colors from RGB to BRG + size_t x = (dwHeight - i - 1) * dwLineSize + (dwWidth - j - 1) * dwColors + (dwColors - k - 1); + size_t y = (dwHeight - i - 1) * dwWidth * dwColors + j * dwColors + k; + *(pBuffer + x) = *(pbInput + y); + } + } + } + } else { + for (size_t i = 0; i < dwHeight; i++) { + for (size_t j = 0; j < dwWidth; j++) { + for (size_t k = 0; k < dwColors; k++) { + // Calculate the position in the padded buffer + size_t x = i * dwLineSize + j * dwColors + (dwColors - k - 1); + size_t y = (dwHeight - i - 1) * dwWidth * dwColors + j * dwColors + k; + *(pBuffer + x) = *(pbInput + y); + } + } + } + } + + memcpy(pbOutput, &bFile, sizeof(BITMAPFILEHEADER)); + memcpy(pbOutput + sizeof(BITMAPFILEHEADER), &bInfo, sizeof(BITMAPINFOHEADER)); + if (cbColors) memcpy(pbOutput + BITMAPHEADERSIZE, pbColors, dwTable); + memcpy(pbOutput + dwOffset, pBuffer, cbInput); + + *pcbResult = bFile.bfSize; + + free(pBuffer); + return 0; +} + +DWORD WriteDataToBitmapFile( + LPCWSTR lpFilePath, DWORD dwBitCount, + DWORD dwWidth, DWORD dwHeight, + PBYTE pbInput, DWORD cbInput, + PBYTE pbMetadata, DWORD cbMetadata, + bool pFlip) { + if (!lpFilePath || !pbInput) return -3; + + HANDLE hFile; + DWORD dwBytesWritten; + + hFile = CreateFileW( + lpFilePath, + GENERIC_WRITE, + FILE_SHARE_READ, + NULL, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, + NULL); + if (hFile == INVALID_HANDLE_VALUE) return -1; + + // calculate the padded row size and padded image size + DWORD dwLineSize = (dwWidth * dwBitCount / 8 + 3) & ~3; + DWORD dwImageSize = dwLineSize * dwHeight; + + DWORD cbResult; + DWORD cbBuffer = dwImageSize + 0x500; + PBYTE pbBuffer = (PBYTE)calloc(cbBuffer, 1); + if (!pbBuffer) return -2; + + if (ConvertDataToBitmap(dwBitCount, dwWidth, dwHeight, pbInput, dwImageSize, pbBuffer, cbBuffer, &cbResult, pFlip) < 0) { + cbResult = -1; + goto WriteDataToBitmapFile_End; + } + + WriteFile(hFile, pbBuffer, cbResult, &dwBytesWritten, NULL); + + if (pbMetadata) + WriteFile(hFile, pbMetadata, cbMetadata, &dwBytesWritten, NULL); + + CloseHandle(hFile); + + cbResult = dwBytesWritten; + +WriteDataToBitmapFile_End: + free(pbBuffer); + return cbResult; +} + +DWORD WriteArrayToFile(LPCSTR lpOutputFilePath, LPVOID lpDataTemp, DWORD nDataSize, BOOL isAppend) { +#ifdef NDEBUG + + return nDataSize; + +#else + + HANDLE hFile; + DWORD dwBytesWritten; + DWORD dwDesiredAccess; + DWORD dwCreationDisposition; + + if (isAppend) { + dwDesiredAccess = FILE_APPEND_DATA; + dwCreationDisposition = OPEN_ALWAYS; + } else { + dwDesiredAccess = GENERIC_WRITE; + dwCreationDisposition = CREATE_ALWAYS; + } + + hFile = CreateFileA( + lpOutputFilePath, + dwDesiredAccess, + FILE_SHARE_READ, + NULL, + dwCreationDisposition, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, + NULL); + if (hFile == INVALID_HANDLE_VALUE) { + return FALSE; + } + + WriteFile(hFile, lpDataTemp, nDataSize, &dwBytesWritten, NULL); + CloseHandle(hFile); + + return dwBytesWritten; + +#endif +} + +void printer_set_dimensions(int width, int height){ + WIDTH = width; + HEIGHT = height; +} diff --git a/common/hooklib/printer.h b/common/hooklib/printer.h new file mode 100644 index 0000000..3e451d1 --- /dev/null +++ b/common/hooklib/printer.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include + +struct printer_config { + bool enable; + bool rotate_180; + char serial_no[8]; + wchar_t main_fw_path[MAX_PATH]; + wchar_t dsp_fw_path[MAX_PATH]; + wchar_t param_fw_path[MAX_PATH]; + wchar_t printer_out_path[MAX_PATH]; + uint32_t wait_time; +}; + +void printer_hook_init(const struct printer_config *cfg, int rfid_port_no, HINSTANCE self); +void printer_hook_insert_hooks(HMODULE target); + +void printer_set_dimensions(int width, int height); +int WINAPI fwdlusb_updateFirmware_main(uint8_t update, LPCSTR filename, uint16_t *rResult); +int WINAPI fwdlusb_updateFirmware_dsp(uint8_t update, LPCSTR filename, uint16_t *rResult); +int WINAPI fwdlusb_updateFirmware_param(uint8_t update, LPCSTR filename, uint16_t *rResult); \ No newline at end of file diff --git a/hooklib/reg.c b/common/hooklib/reg.c similarity index 83% rename from hooklib/reg.c rename to common/hooklib/reg.c index cc3c584..d4a3bc3 100644 --- a/hooklib/reg.c +++ b/common/hooklib/reg.c @@ -7,6 +7,7 @@ #include "hook/table.h" #include "hooklib/reg.h" +#include "hook/procaddr.h" #include "util/dprintf.h" #include "util/str.h" @@ -99,6 +100,29 @@ static LSTATUS WINAPI hook_RegGetValueW( uint32_t *numData ); +static LSTATUS WINAPI hook_RegQueryInfoKeyW( + HKEY hKey, + LPWSTR lpClass, + LPDWORD lpcchClass, + LPDWORD lpReserved, + LPDWORD lpcSubKeys, + LPDWORD lpcbMaxSubKeyLen, + LPDWORD lpcbMaxClassLen, + LPDWORD lpcValues, + LPDWORD lpcbMaxValueNameLen, + LPDWORD lpcbMaxValueLen, + LPDWORD lpcbSecurityDescriptor, + PFILETIME lpftLastWriteTime); + +static LSTATUS WINAPI hook_RegEnumValueW( + HKEY hkey, + DWORD dwIndex, + LPWSTR lpValueName, + LPDWORD lpcchValueName, + LPDWORD lpReserved, + LPDWORD lpType, + LPBYTE lpData, + LPDWORD lpcbData); /* Link pointers */ static LSTATUS (WINAPI *next_RegOpenKeyExW)( @@ -155,6 +179,30 @@ static LSTATUS (WINAPI *next_RegGetValueW)( uint32_t *numData ); +static LSTATUS (WINAPI *next_RegQueryInfoKeyW)( + HKEY hKey, + LPWSTR lpClass, + LPDWORD lpcchClass, + LPDWORD lpReserved, + LPDWORD lpcSubKeys, + LPDWORD lpcbMaxSubKeyLen, + LPDWORD lpcbMaxClassLen, + LPDWORD lpcValues, + LPDWORD lpcbMaxValueNameLen, + LPDWORD lpcbMaxValueLen, + LPDWORD lpcbSecurityDescriptor, + PFILETIME lpftLastWriteTime); + +static LSTATUS (WINAPI *next_RegEnumValueW)( + HKEY hkey, + DWORD dwIndex, + LPWSTR lpValueName, + LPDWORD lpcchValueName, + LPDWORD lpReserved, + LPDWORD lpType, + LPBYTE lpData, + LPDWORD lpcbData); + static const struct hook_symbol reg_hook_syms[] = { { .name = "RegOpenKeyExW", @@ -184,6 +232,14 @@ static const struct hook_symbol reg_hook_syms[] = { .name = "RegGetValueW", .patch = hook_RegGetValueW, .link = (void **) &next_RegGetValueW, + }, { + .name = "RegQueryInfoKeyW", + .patch = hook_RegQueryInfoKeyW, + .link = (void **) &next_RegQueryInfoKeyW, + }, { + .name = "RegEnumValueW", + .patch = hook_RegEnumValueW, + .link = (void **) &next_RegEnumValueW, } }; @@ -252,13 +308,26 @@ static void reg_hook_init(void) reg_hook_initted = true; InitializeCriticalSection(®_hook_lock); - dprintf("Reg hook init\n"); + dprintf("Reg: hook init.\n"); + reg_hook_insert_hooks(NULL); + + proc_addr_table_push( + NULL, + "ADVAPI32.dll", + (struct hook_symbol *) reg_hook_syms, + _countof(reg_hook_syms)); + +} + +void reg_hook_insert_hooks(HMODULE target) +{ hook_table_apply( - NULL, + target, "advapi32.dll", reg_hook_syms, _countof(reg_hook_syms)); + } static LRESULT reg_hook_propagate_hr(HRESULT hr) @@ -331,6 +400,7 @@ static LSTATUS reg_hook_open_locked( /* Assume reg keys are referenced from a root key and not from some intermediary key */ key = ®_hook_keys[i]; + //dprintf("Reg: %ls vs %ls\n", name, key->name); if (key->root == parent && wstr_ieq(key->name, name)) { break; @@ -821,6 +891,99 @@ static LSTATUS WINAPI hook_RegGetValueW( return err; } +static LSTATUS WINAPI hook_RegQueryInfoKeyW( + HKEY hKey, + LPWSTR lpClass, + LPDWORD lpcchClass, + LPDWORD lpReserved, + LPDWORD lpcSubKeys, + LPDWORD lpcbMaxSubKeyLen, + LPDWORD lpcbMaxClassLen, + LPDWORD lpcValues, + LPDWORD lpcbMaxValueNameLen, + LPDWORD lpcbMaxValueLen, + LPDWORD lpcbSecurityDescriptor, + PFILETIME lpftLastWriteTime) +{ + struct reg_hook_key *key; + LSTATUS err; + + EnterCriticalSection(®_hook_lock); + + key = reg_hook_match_key_locked(hKey); + + /* Check if this is a virtualized registry key */ + + if (key == NULL) { + LeaveCriticalSection(®_hook_lock); + + return next_RegQueryInfoKeyW( + hKey, + lpClass, + lpcchClass, + lpReserved, + lpcSubKeys, + lpcbMaxSubKeyLen, + lpcbMaxClassLen, + lpcValues, + lpcbMaxValueNameLen, + lpcbMaxValueLen, + lpcbSecurityDescriptor, + lpftLastWriteTime); + } + + // This is the only one I've seen even be changed, so it's all I'm doing + // until I see otherwise. + *lpcValues = key->nvals; + LeaveCriticalSection(®_hook_lock); + return ERROR_SUCCESS; +} + +static LSTATUS WINAPI hook_RegEnumValueW( + HKEY hkey, + DWORD dwIndex, + LPWSTR lpValueName, + LPDWORD lpcchValueName, + LPDWORD lpReserved, + LPDWORD lpType, + LPBYTE lpData, + LPDWORD lpcbData) +{ + struct reg_hook_key *key; + HRESULT hr; + LSTATUS err; + + EnterCriticalSection(®_hook_lock); + + key = reg_hook_match_key_locked(hkey); + + /* Check if this is a virtualized registry key */ + + if (key == NULL) { + LeaveCriticalSection(®_hook_lock); + + return next_RegEnumValueW( + hkey, + dwIndex, + lpValueName, + lpcchValueName, + lpReserved, + lpType, + lpData, + lpcbData); + } + + if (dwIndex >= key->nvals) { + LeaveCriticalSection(®_hook_lock); + return ERROR_NO_MORE_ITEMS; // Pretty sure this is what it actually returns here? + } + + wcscpy_s(lpValueName, *lpcchValueName, key->vals[dwIndex].name); + *lpcchValueName = wcslen(key->vals[dwIndex].name); + LeaveCriticalSection(®_hook_lock); + return ERROR_SUCCESS; +} + HRESULT reg_hook_read_bin( void *bytes, uint32_t *nbytes, diff --git a/hooklib/reg.h b/common/hooklib/reg.h similarity index 94% rename from hooklib/reg.h rename to common/hooklib/reg.h index eb280c6..20e3dda 100644 --- a/hooklib/reg.h +++ b/common/hooklib/reg.h @@ -12,6 +12,8 @@ struct reg_hook_val { uint32_t type; }; +void reg_hook_insert_hooks(HMODULE target); + HRESULT reg_hook_push_key( HKEY root, const wchar_t *name, diff --git a/hooklib/setupapi.c b/common/hooklib/setupapi.c similarity index 67% rename from hooklib/setupapi.c rename to common/hooklib/setupapi.c index edd416a..6f4fbb5 100644 --- a/hooklib/setupapi.c +++ b/common/hooklib/setupapi.c @@ -16,6 +16,7 @@ struct setupapi_class { const GUID *guid; const wchar_t *path; + char *a_path; HDEVINFO cur_handle; }; @@ -29,6 +30,12 @@ static HDEVINFO WINAPI my_SetupDiGetClassDevsW( HWND hwndParent, DWORD Flags); +static HDEVINFO WINAPI my_SetupDiGetClassDevsA( + const GUID *ClassGuid, + char *Enumerator, + HWND hwndParent, + DWORD Flags); + static BOOL WINAPI my_SetupDiEnumDeviceInterfaces( HDEVINFO DeviceInfoSet, SP_DEVINFO_DATA *DeviceInfoData, @@ -44,6 +51,14 @@ static BOOL WINAPI my_SetupDiGetDeviceInterfaceDetailW( DWORD *RequiredSize, SP_DEVINFO_DATA *DeviceInfoData); +static BOOL WINAPI my_SetupDiGetDeviceInterfaceDetailA( + HDEVINFO DeviceInfoSet, + SP_DEVICE_INTERFACE_DATA *DeviceInterfaceData, + SP_DEVICE_INTERFACE_DETAIL_DATA_A *DeviceInterfaceDetailData, + DWORD DeviceInterfaceDetailDataSize, + DWORD *RequiredSize, + SP_DEVINFO_DATA *DeviceInfoData); + static BOOL WINAPI my_SetupDiDestroyDeviceInfoList(HDEVINFO DeviceInfoSet); /* Links */ @@ -54,6 +69,12 @@ static HDEVINFO (WINAPI *next_SetupDiGetClassDevsW)( HWND hwndParent, DWORD Flags); +static HDEVINFO (WINAPI *next_SetupDiGetClassDevsA)( + const GUID *ClassGuid, + char *Enumerator, + HWND hwndParent, + DWORD Flags); + static BOOL (WINAPI *next_SetupDiEnumDeviceInterfaces)( HDEVINFO DeviceInfoSet, SP_DEVINFO_DATA *DeviceInfoData, @@ -69,6 +90,14 @@ static BOOL (WINAPI *next_SetupDiGetDeviceInterfaceDetailW)( DWORD *RequiredSize, SP_DEVINFO_DATA *DeviceInfoData); +static BOOL (WINAPI *next_SetupDiGetDeviceInterfaceDetailA)( + HDEVINFO DeviceInfoSet, + SP_DEVICE_INTERFACE_DATA *DeviceInterfaceData, + SP_DEVICE_INTERFACE_DETAIL_DATA_A *DeviceInterfaceDetailData, + DWORD DeviceInterfaceDetailDataSize, + DWORD *RequiredSize, + SP_DEVINFO_DATA *DeviceInfoData); + static BOOL (WINAPI *next_SetupDiDestroyDeviceInfoList)(HDEVINFO DeviceInfoSet); /* Hook tbl */ @@ -78,6 +107,10 @@ static const struct hook_symbol setupapi_syms[] = { .name = "SetupDiGetClassDevsW", .patch = my_SetupDiGetClassDevsW, .link = (void *) &next_SetupDiGetClassDevsW, + }, { + .name = "SetupDiGetClassDevsA", + .patch = my_SetupDiGetClassDevsA, + .link = (void *) &next_SetupDiGetClassDevsA, }, { .name = "SetupDiEnumDeviceInterfaces", .patch = my_SetupDiEnumDeviceInterfaces, @@ -86,6 +119,10 @@ static const struct hook_symbol setupapi_syms[] = { .name = "SetupDiGetDeviceInterfaceDetailW", .patch = my_SetupDiGetDeviceInterfaceDetailW, .link = (void *) &next_SetupDiGetDeviceInterfaceDetailW, + }, { + .name = "SetupDiGetDeviceInterfaceDetailA", + .patch = my_SetupDiGetDeviceInterfaceDetailA, + .link = (void *) &next_SetupDiGetDeviceInterfaceDetailA, }, { .name = "SetupDiDestroyDeviceInfoList", .patch = my_SetupDiDestroyDeviceInfoList, @@ -102,6 +139,7 @@ HRESULT setupapi_add_phantom_dev(const GUID *iface_class, const wchar_t *path) { struct setupapi_class *class_; struct setupapi_class *new_array; + size_t a_path_len; HRESULT hr; assert(iface_class != NULL); @@ -126,6 +164,11 @@ HRESULT setupapi_add_phantom_dev(const GUID *iface_class, const wchar_t *path) class_ = &setupapi_classes[setupapi_nclasses++]; class_->guid = iface_class; class_->path = path; + + a_path_len = wcslen(path) * sizeof(wchar_t) + 1; + class_->a_path = (char*)malloc(a_path_len); + wcstombs(class_->a_path, path, a_path_len); + hr = S_OK; end: @@ -189,6 +232,40 @@ static HDEVINFO WINAPI my_SetupDiGetClassDevsW( return result; } +static HDEVINFO WINAPI my_SetupDiGetClassDevsA( + const GUID *ClassGuid, + char *Enumerator, + HWND hwndParent, + DWORD Flags) +{ + struct setupapi_class *class_; + HDEVINFO result; + size_t i; + + result = next_SetupDiGetClassDevsA( + ClassGuid, + Enumerator, + hwndParent, + Flags); + + if (result == INVALID_HANDLE_VALUE || ClassGuid == NULL) { + return result; + } + + EnterCriticalSection(&setupapi_lock); + + for (i = 0 ; i < setupapi_nclasses ; i++) { + class_ = &setupapi_classes[i]; + if (memcmp(ClassGuid, class_->guid, sizeof(*ClassGuid)) == 0) { + class_->cur_handle = result; + } + } + + LeaveCriticalSection(&setupapi_lock); + + return result; +} + static BOOL WINAPI my_SetupDiEnumDeviceInterfaces( HDEVINFO DeviceInfoSet, SP_DEVINFO_DATA *DeviceInfoData, @@ -322,6 +399,76 @@ pass: DeviceInfoData); } +static BOOL WINAPI my_SetupDiGetDeviceInterfaceDetailA( + HDEVINFO DeviceInfoSet, + SP_DEVICE_INTERFACE_DATA *DeviceInterfaceData, + SP_DEVICE_INTERFACE_DETAIL_DATA_A *DeviceInterfaceDetailData, + DWORD DeviceInterfaceDetailDataSize, + DWORD *RequiredSize, + SP_DEVINFO_DATA *DeviceInfoData) +{ + const char *str; + size_t nbytes_str; + size_t nbytes_total; + size_t i; + bool match; + + if (DeviceInfoSet == INVALID_HANDLE_VALUE || DeviceInterfaceData == NULL) { + goto pass; + } + + EnterCriticalSection(&setupapi_lock); + + for (i = 0, match = false; i < setupapi_nclasses && !match; i++) { + if (DeviceInfoSet == setupapi_classes[i].cur_handle && + DeviceInterfaceData->Reserved == (ULONG_PTR) setupapi_classes[i].path) { + str = setupapi_classes[i].a_path; + match = true; + } + } + + LeaveCriticalSection(&setupapi_lock); + + if (!match) { + goto pass; + } + + nbytes_str = strlen(str) + 1; + nbytes_total = offsetof(SP_DEVICE_INTERFACE_DETAIL_DATA_W, DevicePath); + nbytes_total += nbytes_str; + + if (RequiredSize != NULL) { + *RequiredSize = (DWORD) nbytes_total; + } + + if ( DeviceInterfaceDetailData == NULL && + DeviceInterfaceDetailDataSize < nbytes_total) { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + + return FALSE; + } + + if (DeviceInterfaceDetailData->cbSize!=sizeof(*DeviceInterfaceDetailData)) { + SetLastError(ERROR_INVALID_PARAMETER); + + return FALSE; + } + + memcpy(DeviceInterfaceDetailData->DevicePath, str, nbytes_str); + SetLastError(ERROR_SUCCESS); + + return TRUE; + + pass: + return next_SetupDiGetDeviceInterfaceDetailA( + DeviceInfoSet, + DeviceInterfaceData, + DeviceInterfaceDetailData, + DeviceInterfaceDetailDataSize, + RequiredSize, + DeviceInfoData); +} + static BOOL WINAPI my_SetupDiDestroyDeviceInfoList(HDEVINFO DeviceInfoSet) { size_t i; diff --git a/hooklib/setupapi.h b/common/hooklib/setupapi.h similarity index 100% rename from hooklib/setupapi.h rename to common/hooklib/setupapi.h diff --git a/hooklib/spike.c b/common/hooklib/spike.c similarity index 99% rename from hooklib/spike.c rename to common/hooklib/spike.c index f06166c..007c6ef 100644 --- a/hooklib/spike.c +++ b/common/hooklib/spike.c @@ -1,5 +1,6 @@ #include +#include #include #include #include diff --git a/hooklib/spike.h b/common/hooklib/spike.h similarity index 100% rename from hooklib/spike.h rename to common/hooklib/spike.h diff --git a/common/hooklib/touch.c b/common/hooklib/touch.c new file mode 100644 index 0000000..a76692a --- /dev/null +++ b/common/hooklib/touch.c @@ -0,0 +1,255 @@ +/* +This part (touch screen hook) is mostly taken from spicetools, which is GPL. +This means there can be some license issues if you do use this code in some other places without including source code. +*/ +#include + +#include +#include +#include + +#include "hook/com-proxy.h" +#include "hook/table.h" + +#include "hooklib/config.h" +#include "hooklib/dll.h" +#include "hooklib/touch.h" + +#include "util/dprintf.h" + +/* API hooks */ + +static LRESULT hook_wndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); + +static ATOM WINAPI hook_RegisterClassExA( + WNDCLASSEXA* wndClass +); + +static int WINAPI hook_GetSystemMetrics( + int nIndex +); + +static BOOL WINAPI hook_RegisterTouchWindow( + HWND hwnd, + ULONG ulFlags +); + +static BOOL WINAPI hook_GetTouchInputInfo( + HANDLE hTouchInput, + UINT cInputs, + PTOUCHINPUT pInputs, + int cbSize +); + +static HCURSOR WINAPI hook_SetCursor(HCURSOR cursor); + +/* Link pointers */ + +static ATOM (WINAPI *next_RegisterClassExA)( + const WNDCLASSEXA* wndClass +); + +static int (WINAPI *next_GetSystemMetrics)( + int nIndex +); + +static BOOL (WINAPI *next_RegisterTouchWindow)( + HWND hwnd, + ULONG ulFlags +); + +static BOOL (WINAPI *next_GetTouchInputInfo)( + HANDLE hTouchInput, + UINT cInputs, + PTOUCHINPUT pInputs, + int cbSize +); + +static HCURSOR(WINAPI *next_SetCursor)(HCURSOR cursor); + +static bool touch_hook_initted; +static bool touch_held; +static HWND registered_hWnd; +static struct touch_screen_config touch_config; +static WNDPROC orig_wndProc; +static HCURSOR defaultCursor; + +static const struct hook_symbol touch_hooks[] = { + { + .name = "RegisterClassExA", + .patch = hook_RegisterClassExA, + .link = (void**)&next_RegisterClassExA + }, + { + .name = "GetSystemMetrics", + .patch = hook_GetSystemMetrics, + .link = (void **) &next_GetSystemMetrics + }, + { + .name = "RegisterTouchWindow", + .patch = hook_RegisterTouchWindow, + .link = (void **) &next_RegisterTouchWindow + }, + { + .name = "GetTouchInputInfo", + .patch = hook_GetTouchInputInfo, + .link = (void **) &next_GetTouchInputInfo + }, + { + .name = "SetCursor", + .patch = hook_SetCursor, + .link = (void **) &next_SetCursor + }, +}; + +void touch_screen_hook_init(const struct touch_screen_config *cfg, HINSTANCE self) +{ + assert(cfg != NULL); + + if (!cfg->enable) { + return; + } + + if (touch_hook_initted) { + return; + } + + touch_hook_initted = true; + + defaultCursor = LoadCursorA(NULL, IDC_CROSS); + + memcpy(&touch_config, cfg, sizeof(*cfg)); + touch_hook_insert_hooks(NULL); + dprintf("TOUCH: hook enabled.\n"); +} + +void touch_hook_insert_hooks(HMODULE target) +{ + hook_table_apply( + target, + "user32.dll", + touch_hooks, + _countof(touch_hooks)); +} + +static HCURSOR WINAPI hook_SetCursor(HCURSOR cursor) { + if (cursor == 0 && touch_config.cursor) + return next_SetCursor(defaultCursor); + + return next_SetCursor(cursor); +} + +// remap mouse events to touch events + +static LRESULT hook_wndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { + if (hWnd == registered_hWnd && (Msg == WM_LBUTTONDOWN || Msg == WM_LBUTTONUP || (touch_held && Msg == WM_MOUSEMOVE))) { + orig_wndProc(hWnd, WM_TOUCH, 1, 1); + } + + return orig_wndProc(hWnd, Msg, wParam, lParam); +} + +static ATOM WINAPI hook_RegisterClassExA( + WNDCLASSEXA* wndClass +) { + if (wndClass->lpfnWndProc) { + orig_wndProc = wndClass->lpfnWndProc; + wndClass->lpfnWndProc = (WNDPROC) hook_wndProc; + } + + return next_RegisterClassExA(wndClass); +} + +// Spicetools misc/wintouchemu.cpp + +static int WINAPI hook_GetSystemMetrics( + int nIndex +) +{ + if (nIndex == 94) return 0x01 | 0x02 | 0x40 | 0x80; + + int orig = next_GetSystemMetrics(nIndex); + + return orig; +} + +static BOOL WINAPI hook_RegisterTouchWindow( + HWND hwnd, + ULONG ulFlags +) +{ + registered_hWnd = hwnd; + return true; +} + +// Converting mouse event to touch event +static BOOL WINAPI hook_GetTouchInputInfo( + HANDLE hTouchInput, + UINT cInputs, + PTOUCHINPUT pInputs, + int cbSize +) +{ + bool result = false; + int sw, sh, cw, ch; + RECT cRect; + sw = GetSystemMetrics(SM_CXSCREEN); + sh = GetSystemMetrics(SM_CYSCREEN); + GetClientRect(registered_hWnd, &cRect); + cw = cRect.right - cRect.left; + ch = cRect.bottom - cRect.top; + static bool mouse_state_old = false; + + for (UINT input = 0; input < cInputs; input++) { + TOUCHINPUT *touch_input = &pInputs[input]; + + touch_input->x = 0; + touch_input->y = 0; + touch_input->hSource = NULL; + touch_input->dwID = 0; + touch_input->dwFlags = 0; + touch_input->dwMask = 0; + touch_input->dwTime = 0; + touch_input->dwExtraInfo = 0; + touch_input->cxContact = 0; + touch_input->cyContact = 0; + + bool mouse_state = (GetKeyState(VK_LBUTTON) & 0x100) != 0; + + if (mouse_state || mouse_state_old) { + POINT cursorPos; + + GetCursorPos(&cursorPos); + + if (touch_config.remap) { + ScreenToClient(registered_hWnd, &cursorPos); + cursorPos.x = (long)(cursorPos.x * ((double)sw / cw)); + cursorPos.y = (long)(cursorPos.y * ((double)sh / ch)); + } + + result = true; + touch_input->x = cursorPos.x * 100; + touch_input->y = cursorPos.y * 100; + touch_input->hSource = hTouchInput; + touch_input->dwID = 0; + touch_input->dwFlags = 0; + if (mouse_state && !mouse_state_old) { + touch_input->dwFlags |= TOUCHEVENTF_DOWN; + touch_held = true; + } else if (mouse_state && mouse_state_old) { + touch_input->dwFlags |= TOUCHEVENTF_MOVE; + } else if (!mouse_state && mouse_state_old) { + touch_input->dwFlags |= TOUCHEVENTF_UP; + touch_held = false; + } + touch_input->dwMask = 0; + touch_input->dwTime = 0; + touch_input->dwExtraInfo = 0; + touch_input->cxContact = 0; + touch_input->cyContact = 0; + } + mouse_state_old = mouse_state; + } + + return result; +} diff --git a/common/hooklib/touch.h b/common/hooklib/touch.h new file mode 100644 index 0000000..dcb9082 --- /dev/null +++ b/common/hooklib/touch.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#include + +struct touch_screen_config { + bool enable; + bool remap; + bool cursor; +}; + +/* Init is not thread safe because API hook init is not thread safe blah + blah blah you know the drill by now. */ + +void touch_screen_hook_init(const struct touch_screen_config *cfg, HINSTANCE self); +void touch_hook_insert_hooks(HMODULE target); diff --git a/iccard/aime.c b/common/iccard/aime.c similarity index 96% rename from iccard/aime.c rename to common/iccard/aime.c index 5d2b1cd..bae12c1 100644 --- a/iccard/aime.c +++ b/common/iccard/aime.c @@ -1,6 +1,8 @@ #include #include #include +#include +#include #include "iccard/aime.h" #include "iccard/mifare.h" diff --git a/iccard/aime.h b/common/iccard/aime.h similarity index 100% rename from iccard/aime.h rename to common/iccard/aime.h diff --git a/common/iccard/felica.c b/common/iccard/felica.c new file mode 100644 index 0000000..392f3b2 --- /dev/null +++ b/common/iccard/felica.c @@ -0,0 +1,333 @@ +#include + +#include +#include +#include +#include + +#include "hook/iobuf.h" + +#include "iccard/felica.h" + +#include "util/dprintf.h" +#include "util/dump.h" + +static HRESULT felica_cmd_poll( + struct felica *f, + struct const_iobuf *req, + struct iobuf *res); + +static HRESULT felica_cmd_get_system_code( + struct felica *f, + struct const_iobuf *req, + struct iobuf *res); + +static HRESULT felica_cmd_active( + struct felica *f, + struct const_iobuf *req, + struct iobuf *res); + +static HRESULT felica_cmd_read_without_encryption( + struct felica *f, + struct const_iobuf *req, + struct iobuf *res); + +static HRESULT felica_cmd_write_without_encryption( + struct felica* f, + struct const_iobuf* req, + struct iobuf* res); + +uint64_t felica_get_amusement_ic_PMm(void) +{ + /* + * AIC Card PMm, if this is returned from the card, + * the aimelib will access the actual blocks for authentication. + */ + + return 0x00F1000000014300; +} + +HRESULT felica_transact( + struct felica *f, + struct const_iobuf *req, + struct iobuf *res) +{ + uint64_t IDm; + uint8_t code; + HRESULT hr; + + assert(f != NULL); + assert(req != NULL); + assert(res != NULL); + + hr = iobuf_read_8(req, &code); + + if (FAILED(hr)) { + return hr; + } + + hr = iobuf_write_8(res, code + 1); + + if (FAILED(hr)) { + return hr; + } + + if (code != FELICA_CMD_POLL) { + hr = iobuf_read_be64(req, &IDm); + + if (FAILED(hr)) { + return hr; + } + + if (IDm != f->IDm) { + return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + } + + hr = iobuf_write_be64(res, IDm); + + if (FAILED(hr)) { + return hr; + } + } + + switch (code) { + case FELICA_CMD_POLL: + return felica_cmd_poll(f, req, res); + + case FELICA_CMD_GET_SYSTEM_CODE: + return felica_cmd_get_system_code(f, req, res); + + case FELICA_READ_WITHOUT_ENCRYPTION: + return felica_cmd_read_without_encryption(f, req, res); + + case FELICA_WRITE_WITHOUT_ENCRYPTION: + return felica_cmd_write_without_encryption(f, req, res); + + case FELICA_CMD_ACTIVE: + return felica_cmd_active(f, req, res); + + default: + dprintf("FeliCa: Unimplemented command %02x, payload:\n", code); + dump_const_iobuf(req); + + return E_NOTIMPL; + } +} + +static HRESULT felica_cmd_poll( + struct felica *f, + struct const_iobuf *req, + struct iobuf *res) +{ + uint16_t system_code; + uint8_t request_code; + uint8_t time_slot; + HRESULT hr; + + /* Request: */ + + hr = iobuf_read_be16(req, &system_code); + + if (FAILED(hr)) { + return hr; + } + + hr = iobuf_read_8(req, &request_code); + + if (FAILED(hr)) { + return hr; + } + + hr = iobuf_read_8(req, &time_slot); + + if (FAILED(hr)) { + return hr; + } + + if (system_code != 0xFFFF && system_code != f->system_code) { + return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + } + + // TODO handle other params correctly... + + /* Response: */ + + hr = iobuf_write_be64(res, f->IDm); + + if (FAILED(hr)) { + return hr; + } + + hr = iobuf_write_be64(res, f->PMm); + + if (FAILED(hr)) { + return hr; + } + + if (request_code == 0x01) { + hr = iobuf_write_be16(res, f->system_code); + + if (FAILED(hr)) { + return hr; + } + } + + return S_OK; +} + +static HRESULT felica_cmd_get_system_code( + struct felica *f, + struct const_iobuf *req, + struct iobuf *res) +{ + HRESULT hr; + + hr = iobuf_write_8(res, 1); /* Number of system codes */ + + if (FAILED(hr)) { + return hr; + } + + hr = iobuf_write_be16(res, f->system_code); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} + +static HRESULT felica_cmd_read_without_encryption( + struct felica *f, + struct const_iobuf *req, + struct iobuf *res) +{ + HRESULT hr; + uint8_t system_code_count; + uint16_t* system_codes; + uint8_t read_block_count; + uint8_t* blocks; + size_t i; + + hr = iobuf_read_8(req, &system_code_count); + + if (FAILED(hr)) { + goto fail; + } + + system_codes = malloc(sizeof(uint16_t) * system_code_count); + if (!system_codes) goto fail; + + for (i = 0; i < system_code_count; i++) { + hr = iobuf_read_be16(req, system_codes + i); + + if (FAILED(hr)) { + goto fail; + } + } + + hr = iobuf_read_8(req, &read_block_count); + + if (FAILED(hr)) { + goto fail; + } + + blocks = malloc(read_block_count); + if (!system_codes) goto fail; + + for (i = 0; i < read_block_count; i++) { + // 0x80 + hr = iobuf_read_8(req, blocks + i); + + if (FAILED(hr)) { + goto fail; + } + + // actual block num + hr = iobuf_read_8(req, blocks + i); + + if (FAILED(hr)) { + goto fail; + } + } + + // status + hr = iobuf_write_be16(res, 0); + + if (FAILED(hr)) { + goto fail; + } + + // block count + hr = iobuf_write_8(res, read_block_count); + + if (FAILED(hr)) { + goto fail; + } + + // block data + for (i = 0; i < read_block_count; i++) + { + dprintf("FeliCa: Read block %x\n", blocks[i]); + + switch (blocks[i]) { + case 0x82: { + hr = iobuf_write_be64(res, f->IDm); + + if (FAILED(hr)) + { + goto fail; + } + + hr = iobuf_write_be64(res, 0x0078000000000000ull); + + if (FAILED(hr)) + { + goto fail; + } + } + default: { + hr = iobuf_write_be64(res, 0); + + if (FAILED(hr)) + { + goto fail; + } + + hr = iobuf_write_be64(res, 0); + + if (FAILED(hr)) + { + goto fail; + } + } + } + } + + hr = S_OK; + +fail: + if (system_codes) free(system_codes); + if (blocks) free(blocks); + + return hr; +} + +static HRESULT felica_cmd_write_without_encryption( + struct felica* f, + struct const_iobuf* req, + struct iobuf* res) +{ + return iobuf_write_be16(res, 0); +} + +static HRESULT felica_cmd_active( + struct felica *f, + struct const_iobuf *req, + struct iobuf *res) +{ + /* The specification for this command is probably only available under NDA. + Returning what the driver seems to want. */ + + return iobuf_write_8(res, 0); +} diff --git a/iccard/felica.h b/common/iccard/felica.h similarity index 53% rename from iccard/felica.h rename to common/iccard/felica.h index 33be201..b55cc87 100644 --- a/iccard/felica.h +++ b/common/iccard/felica.h @@ -8,9 +8,11 @@ #include "hook/iobuf.h" enum { - FELICA_CMD_POLL = 0x00, - FELICA_CMD_GET_SYSTEM_CODE = 0x0c, - FELICA_CMD_NDA_A4 = 0xa4, + FELICA_CMD_POLL = 0x00, + FELICA_READ_WITHOUT_ENCRYPTION = 0x06, + FELICA_WRITE_WITHOUT_ENCRYPTION = 0x08, + FELICA_CMD_GET_SYSTEM_CODE = 0x0c, + FELICA_CMD_ACTIVE = 0xa4, }; struct felica { @@ -24,4 +26,4 @@ HRESULT felica_transact( struct const_iobuf *req, struct iobuf *res); -uint64_t felica_get_generic_PMm(void); +uint64_t felica_get_amusement_ic_PMm(void); diff --git a/iccard/meson.build b/common/iccard/meson.build similarity index 90% rename from iccard/meson.build rename to common/iccard/meson.build index e068ae0..72a6f78 100644 --- a/iccard/meson.build +++ b/common/iccard/meson.build @@ -2,7 +2,6 @@ iccard_lib = static_library( 'iccard', include_directories : inc, implicit_include_directories : false, - c_pch : '../precompiled.h', dependencies : [ capnhook.get_variable('hook_dep'), ], diff --git a/iccard/mifare.h b/common/iccard/mifare.h similarity index 100% rename from iccard/mifare.h rename to common/iccard/mifare.h diff --git a/jvs/jvs-bus.c b/common/jvs/jvs-bus.c similarity index 100% rename from jvs/jvs-bus.c rename to common/jvs/jvs-bus.c diff --git a/jvs/jvs-bus.h b/common/jvs/jvs-bus.h similarity index 100% rename from jvs/jvs-bus.h rename to common/jvs/jvs-bus.h diff --git a/jvs/jvs-cmd.h b/common/jvs/jvs-cmd.h similarity index 88% rename from jvs/jvs-cmd.h rename to common/jvs/jvs-cmd.h index 0197c9f..65ad599 100644 --- a/jvs/jvs-cmd.h +++ b/common/jvs/jvs-cmd.h @@ -9,6 +9,7 @@ enum { JVS_CMD_READ_SWITCHES = 0x20, JVS_CMD_READ_COIN = 0x21, JVS_CMD_READ_ANALOGS = 0x22, + JVS_CMD_READ_ROTARYS = 0x23, JVS_CMD_WRITE_GPIO = 0x32, JVS_CMD_RESET = 0xF0, JVS_CMD_ASSIGN_ADDR = 0xF1, @@ -32,6 +33,11 @@ struct jvs_req_read_analogs { uint8_t nanalogs; }; +struct jvs_req_read_rotarys { + uint8_t cmd; + uint8_t nrotarys; +}; + struct jvs_req_reset { uint8_t cmd; uint8_t unknown; diff --git a/jvs/jvs-frame.c b/common/jvs/jvs-frame.c similarity index 100% rename from jvs/jvs-frame.c rename to common/jvs/jvs-frame.c diff --git a/jvs/jvs-frame.h b/common/jvs/jvs-frame.h similarity index 100% rename from jvs/jvs-frame.h rename to common/jvs/jvs-frame.h diff --git a/jvs/jvs-util.c b/common/jvs/jvs-util.c similarity index 97% rename from jvs/jvs-util.c rename to common/jvs/jvs-util.c index 1bbe10e..336453e 100644 --- a/jvs/jvs-util.c +++ b/common/jvs/jvs-util.c @@ -10,6 +10,7 @@ #include "jvs/jvs-util.h" #include "util/dprintf.h" +#include "util/dump.h" typedef HRESULT (*jvs_dispatch_fn_t)( void *ctx, @@ -46,7 +47,7 @@ void jvs_crack_request( return; } -#if 0 +#if defined(LOG_JVS) dprintf("Decoded request:\n"); dump_iobuf(&decode); #endif @@ -96,7 +97,7 @@ void jvs_crack_request( resp_bytes[2] = 0x01; /* Status: Success */ } -#if 0 +#if defined(LOG_JVS) dprintf("Encoding response:\n"); dump_iobuf(&encode); #endif diff --git a/jvs/jvs-util.h b/common/jvs/jvs-util.h similarity index 100% rename from jvs/jvs-util.h rename to common/jvs/jvs-util.h diff --git a/jvs/meson.build b/common/jvs/meson.build similarity index 91% rename from jvs/meson.build rename to common/jvs/meson.build index 49737a0..14a7f66 100644 --- a/jvs/meson.build +++ b/common/jvs/meson.build @@ -2,7 +2,6 @@ jvs_lib = static_library( 'jvs', include_directories : inc, implicit_include_directories : false, - c_pch : '../precompiled.h', dependencies : [ capnhook.get_variable('hook_dep'), ], diff --git a/platform/amvideo.c b/common/platform/amvideo.c similarity index 100% rename from platform/amvideo.c rename to common/platform/amvideo.c diff --git a/platform/amvideo.h b/common/platform/amvideo.h similarity index 100% rename from platform/amvideo.h rename to common/platform/amvideo.h diff --git a/platform/clock.c b/common/platform/clock.c similarity index 93% rename from platform/clock.c rename to common/platform/clock.c index b67b111..b7249fa 100644 --- a/platform/clock.c +++ b/common/platform/clock.c @@ -2,8 +2,10 @@ #include #include +#include #include "hook/table.h" +#include "hook/procaddr.h" #include "platform/clock.h" @@ -19,7 +21,9 @@ static BOOL WINAPI my_SetTimeZoneInformation(TIME_ZONE_INFORMATION *tzinfo); static BOOL (WINAPI * next_GetSystemTimeAsFileTime)(FILETIME *out); static int64_t clock_current_day; +static bool clock_timezone; static bool clock_time_warp; +static bool clock_writeable; static const struct hook_symbol clock_base_hook_syms[] = { { @@ -158,7 +162,7 @@ static BOOL WINAPI my_GetSystemTime(SYSTEMTIME *out) return ok; } -#if 0 +#if defined(LOG_CLOCK) static int last_second; if (out->wSecond != last_second) { @@ -225,35 +229,41 @@ HRESULT clock_hook_init(const struct clock_config *cfg) { assert(cfg != NULL); + clock_timezone = cfg->timezone; clock_time_warp = cfg->timewarp; + clock_writeable = cfg->writeable; - if (cfg->timezone || cfg->timewarp || !cfg->writeable) { + clock_hook_insert_hooks(NULL); + + return S_OK; +} + +void clock_hook_insert_hooks(HMODULE target) { + if (clock_timezone || clock_time_warp || !clock_writeable) { /* All the clock hooks require the core GSTAFT hook to be installed */ /* Note the ! up there btw. */ hook_table_apply( - NULL, + target, "kernel32.dll", clock_base_hook_syms, _countof(clock_base_hook_syms)); } - if (cfg->timezone) { + if (clock_timezone) { hook_table_apply( - NULL, + target, "kernel32.dll", clock_read_hook_syms, _countof(clock_read_hook_syms)); } - if (!cfg->writeable) { + if (!clock_writeable) { /* Install hook if this config parameter is FALSE! */ hook_table_apply( - NULL, + target, "kernel32.dll", clock_write_hook_syms, _countof(clock_write_hook_syms)); } - - return S_OK; } diff --git a/platform/clock.h b/common/platform/clock.h similarity index 81% rename from platform/clock.h rename to common/platform/clock.h index 4d67754..6d8c201 100644 --- a/platform/clock.h +++ b/common/platform/clock.h @@ -11,3 +11,4 @@ struct clock_config { }; HRESULT clock_hook_init(const struct clock_config *cfg); +void clock_hook_insert_hooks(HMODULE target); diff --git a/platform/config.c b/common/platform/config.c similarity index 84% rename from platform/config.c rename to common/platform/config.c index 1c6be62..c9777da 100644 --- a/platform/config.c +++ b/common/platform/config.c @@ -13,6 +13,7 @@ #include "platform/clock.h" #include "platform/config.h" #include "platform/dns.h" +#include "platform/epay.h" #include "platform/hwmon.h" #include "platform/hwreset.h" #include "platform/misc.h" @@ -21,6 +22,7 @@ #include "platform/pcbid.h" #include "platform/platform.h" #include "platform/vfs.h" +#include "platform/system.h" void platform_config_load(struct platform_config *cfg, const wchar_t *filename) { @@ -30,6 +32,7 @@ void platform_config_load(struct platform_config *cfg, const wchar_t *filename) amvideo_config_load(&cfg->amvideo, filename); clock_config_load(&cfg->clock, filename); dns_config_load(&cfg->dns, filename); + epay_config_load(&cfg->epay, filename); hwmon_config_load(&cfg->hwmon, filename); hwreset_config_load(&cfg->hwreset, filename); misc_config_load(&cfg->misc, filename); @@ -37,6 +40,7 @@ void platform_config_load(struct platform_config *cfg, const wchar_t *filename) netenv_config_load(&cfg->netenv, filename); nusec_config_load(&cfg->nusec, filename); vfs_config_load(&cfg->vfs, filename); + system_config_load(&cfg->system, filename); } void amvideo_config_load(struct amvideo_config *cfg, const wchar_t *filename) @@ -109,6 +113,20 @@ void dns_config_load(struct dns_config *cfg, const wchar_t *filename) cfg->aimedb, _countof(cfg->aimedb), filename); + + GetPrivateProfileStringW( + L"dns", + L"title", + L"title", + cfg->title, + _countof(cfg->title), + filename); + + cfg->replaceHost = GetPrivateProfileIntW(L"dns", L"replaceHost", 0, filename); + + cfg->startupPort = GetPrivateProfileIntW(L"dns", L"startupPort", 0, filename); + cfg->billingPort = GetPrivateProfileIntW(L"dns", L"billingPort", 0, filename); + cfg->aimedbPort = GetPrivateProfileIntW(L"dns", L"aimedbPort", 0, filename); } void hwmon_config_load(struct hwmon_config *cfg, const wchar_t *filename) @@ -317,3 +335,30 @@ void vfs_config_load(struct vfs_config *cfg, const wchar_t *filename) filename); } +void system_config_load(struct system_config *cfg, const wchar_t *filename) +{ + wchar_t name[7]; + size_t i; + + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"system", L"enable", 0, filename); + cfg->freeplay = GetPrivateProfileIntW(L"system", L"freeplay", 0, filename); + + wcscpy_s(name, _countof(name), L"dipsw0"); + + for (i = 0 ; i < 8 ; i++) { + name[5] = L'1' + i; + cfg->dipsw[i] = GetPrivateProfileIntW(L"system", name, 0, filename); + } +} + +void epay_config_load(struct epay_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"epay", L"enable", 1, filename); + cfg->hook = GetPrivateProfileIntW(L"epay", L"hook", 0, filename); +} diff --git a/platform/config.h b/common/platform/config.h similarity index 85% rename from platform/config.h rename to common/platform/config.h index 7ece41d..e945378 100644 --- a/platform/config.h +++ b/common/platform/config.h @@ -9,6 +9,7 @@ #include "platform/amvideo.h" #include "platform/clock.h" #include "platform/dns.h" +#include "platform/epay.h" #include "platform/hwmon.h" #include "platform/hwreset.h" #include "platform/misc.h" @@ -17,6 +18,7 @@ #include "platform/pcbid.h" #include "platform/platform.h" #include "platform/vfs.h" +#include "platform/system.h" void platform_config_load( struct platform_config *cfg, @@ -25,6 +27,7 @@ void platform_config_load( void amvideo_config_load(struct amvideo_config *cfg, const wchar_t *filename); void clock_config_load(struct clock_config *cfg, const wchar_t *filename); void dns_config_load(struct dns_config *cfg, const wchar_t *filename); +void epay_config_load(struct epay_config *cfg, const wchar_t *filename); void hwmon_config_load(struct hwmon_config *cfg, const wchar_t *filename); void hwreset_config_load(struct hwreset_config *cfg, const wchar_t *filename); void misc_config_load(struct misc_config *cfg, const wchar_t *filename); @@ -32,3 +35,4 @@ void netenv_config_load(struct netenv_config *cfg, const wchar_t *filename); void nusec_config_load(struct nusec_config *cfg, const wchar_t *filename); void pcbid_config_load(struct pcbid_config *cfg, const wchar_t *filename); void vfs_config_load(struct vfs_config *cfg, const wchar_t *filename); +void system_config_load(struct system_config *cfg, const wchar_t *filename); diff --git a/common/platform/dns.c b/common/platform/dns.c new file mode 100644 index 0000000..f5745f0 --- /dev/null +++ b/common/platform/dns.c @@ -0,0 +1,189 @@ +#include + +#include + +#include "hooklib/dns.h" + +#include "platform/dns.h" + +HRESULT dns_platform_hook_init(const struct dns_config *cfg) +{ + HRESULT hr; + + assert(cfg != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + if (cfg->replaceHost){ + http_hook_init(); + } + + if (cfg->startupPort || cfg->billingPort || cfg->aimedbPort){ + port_hook_init(cfg->startupPort, cfg->billingPort, cfg->aimedbPort); + } + + hr = dns_hook_push(L"tenporouter.loc", cfg->router); + + if (FAILED(hr)) { + return hr; + } + + hr = dns_hook_push(L"bbrouter.loc", cfg->router); + + if (FAILED(hr)) { + return hr; + } + + hr = dns_hook_push(L"naominet.jp", cfg->startup); + + if (FAILED(hr)) { + return hr; + } + + hr = dns_hook_push(L"anbzvarg.wc", cfg->startup); + + if (FAILED(hr)) { + return hr; + } + + hr = dns_hook_push(L"op.auth.sys-all.net", cfg->startup); + + if (FAILED(hr)) { + return hr; + } + + hr = dns_hook_push(L"at.auth.sys-all.net", cfg->startup); + + if (FAILED(hr)) { + return hr; + } + + hr = dns_hook_push(L"at.sys-all.net", cfg->startup); + + if (FAILED(hr)) { + return hr; + } + + hr = dns_hook_push(L"ib.naominet.jp", cfg->billing); + + if (FAILED(hr)) { + return hr; + } + + hr = dns_hook_push(L"vo.anbzvarg.wc", cfg->billing); + + if (FAILED(hr)) { + return hr; + } + + hr = dns_hook_push(L"aime.naominet.jp", cfg->aimedb); + + if (FAILED(hr)) { + return hr; + } + + hr = dns_hook_push(L"nvzr.anbzvarg.wc", cfg->aimedb); + + if (FAILED(hr)) { + return hr; + } + + // crossbeats REV. + hr = dns_hook_push(L"https://rev-ent.ac.capcom.jp:443", cfg->title); + + if (FAILED(hr)) { + return hr; + } + + // AimePay + hr = dns_hook_push(L"api-aime.am-all.net", cfg->startup); + + if (FAILED(hr)) { + return hr; + } + + // E-MONEY + hr = dns_hook_push(L"tasms-api-basis.thincacloud.com", cfg->startup); + + if (FAILED(hr)) { + return hr; + } + + hr = dns_hook_push(L"shop.tfps.thincacloud.com", cfg->startup); + + if (FAILED(hr)) { + return hr; + } + + // WAHLAP PowerOn + hr = dns_hook_push(L"at.sys-all.cn", cfg->startup); + + if (FAILED(hr)) { + return hr; + } + + hr = dns_hook_push(L"at.sys-allnet.cn", cfg->startup); + + if (FAILED(hr)) { + return hr; + } + + // WAHLAP WeChat AimeDB Server + hr = dns_hook_push(L"ai.sys-all.cn", cfg->aimedb); + + if (FAILED(hr)) { + return hr; + } + + hr = dns_hook_push(L"ai.sys-allnet.cn", cfg->aimedb); + + if (FAILED(hr)) { + return hr; + } + + // WAHLAP Billing + hr = dns_hook_push(L"bl.sys-all.cn", cfg->billing); + + if (FAILED(hr)) { + return hr; + } + + hr = dns_hook_push(L"bl.sys-allnet.cn", cfg->billing); + + if (FAILED(hr)) { + return hr; + } + + // if your ISP resolves bad domains, it will kill the network. These 2 + // *cannot* resolve + + hr = dns_hook_push(L"mobirouter.loc", NULL); + + if (FAILED(hr)) { + return hr; + } + + hr = dns_hook_push(L"dslrouter.loc", NULL); + + if (FAILED(hr)) { + return hr; + } + + // Disable api/polling to the original servers + + hr = dns_hook_push(L"*.amlog.sys-all.net", NULL); + + if (FAILED(hr)) { + return hr; + } + + hr = dns_hook_push(L"*.d-amlog.sys-all.net", NULL); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} diff --git a/platform/dns.h b/common/platform/dns.h similarity index 66% rename from platform/dns.h rename to common/platform/dns.h index 416fe73..1a19d26 100644 --- a/platform/dns.h +++ b/common/platform/dns.h @@ -11,6 +11,11 @@ struct dns_config { wchar_t startup[128]; wchar_t billing[128]; wchar_t aimedb[128]; + wchar_t title[128]; + bool replaceHost; + unsigned short startupPort; + unsigned short billingPort; + unsigned short aimedbPort; }; HRESULT dns_platform_hook_init(const struct dns_config *cfg); diff --git a/common/platform/epay.c b/common/platform/epay.c new file mode 100644 index 0000000..6193a2d --- /dev/null +++ b/common/platform/epay.c @@ -0,0 +1,366 @@ +#include + +#include +#include +#include +#include + +#include "hook/table.h" + +#include "hooklib/dns.h" +#include "hooklib/reg.h" + +#include "platform/epay.h" + +#include + +#include "util/dprintf.h" + +static HRESULT misc_read_thinca_adapter(void *bytes, uint32_t *nbytes); +static HRESULT misc_read_ca_loc(void *bytes, uint32_t *nbytes); +static HRESULT misc_read_ca_client_loc(void *bytes, uint32_t *nbytes); +static HRESULT misc_read_network_timeout(void *bytes, uint32_t *nbytes); +static HRESULT misc_read_pattern0(void *bytes, uint32_t *nbytes); +static HRESULT misc_read_network_timeout0(void *bytes, uint32_t *nbytes); +static HRESULT misc_read_pattern1(void *bytes, uint32_t *nbytes); +static HRESULT misc_read_network_timeout1(void *bytes, uint32_t *nbytes); + +static uint64_t thinca_initialize(struct thinca_impl * self, uint64_t val); +static uint64_t thinca_dispose(struct thinca_impl * self); +static uint64_t thinca_set_resource(struct thinca_impl * self, char * res); +static uint64_t thinca_set_pay_log(struct thinca_impl * self, uint64_t val, char * log, uint64_t val2, const char * size_lim); +static uint64_t thinca_set_client_log(struct thinca_impl * self, uint64_t val, char * log); +static uint64_t thinca_set_client_cfg(struct thinca_impl * self, char * log, uint64_t val); +static uint64_t thinca_set_goods_code(struct thinca_impl * self, char * code); +static uint64_t thinca_set_evt_handler(struct thinca_impl * self, void* handler); +static uint64_t thinca_set_cert(struct thinca_impl * self, char * cert, uint64_t val); +static uint64_t thinca_set_serial(struct thinca_impl * self, char * cert); +static uint64_t thinca_check_deal(struct thinca_impl * self, void* deal); +static uint64_t thinca_cancel(struct thinca_impl * self); +static uint64_t thinca_select(struct thinca_impl * self); +static uint64_t thinca_unk(struct thinca_impl * self, uint64_t val); +static void thinca_unk8(struct thinca_impl * self); + +static uint64_t my_ThincaPaymentGetVersion(); +static uint64_t (*next_ThincaPaymentGetVersion)(); + +static struct thinca_main* my_ThincaPaymentGetInstance(uint64_t ver); +static struct thinca_main* (*next_ThincaPaymentGetInstance)(uint64_t ver); + +static struct thinca_main* thinca_stub; + +static const struct reg_hook_val epay_adapter_keys[] = { + { + .name = L"TfpsAimeRwAdapter", + .read = misc_read_thinca_adapter, + .type = REG_SZ, + } +}; + +static const struct reg_hook_val epay_tcap_keys[] = { + { + .name = L"CaLocation", + .read = misc_read_ca_loc, + .type = REG_SZ, + }, + { + .name = L"ThincaTcapClientPath", + .read = misc_read_ca_client_loc, + .type = REG_SZ, + }, + { + .name = L"ClientNetworkTimeout", + .read = misc_read_network_timeout, + .type = REG_DWORD, + } +}; + +static const struct reg_hook_val epay_tcap_url0_keys[] = { + { + .name = L"Pattern", + .read = misc_read_pattern0, + .type = REG_SZ, + }, + { + .name = L"ClientNetworkTimeout", + .read = misc_read_network_timeout0, + .type = REG_DWORD, + } +}; + +static const struct reg_hook_val epay_tcap_url1_keys[] = { + { + .name = L"Pattern", + .read = misc_read_pattern1, + .type = REG_SZ, + }, + { + .name = L"ClientNetworkTimeout", + .read = misc_read_network_timeout1, + .type = REG_DWORD, + } +}; + +static const struct hook_symbol epay_syms[] = { + { + .name = "ThincaPaymentGetVersion", + .patch = my_ThincaPaymentGetVersion, + .link = (void **) &next_ThincaPaymentGetVersion, + .ordinal = 1, + }, + { + .name = "__imp_ThincaPaymentGetInstance", + .patch = my_ThincaPaymentGetInstance, + .link = (void **) &next_ThincaPaymentGetInstance, + .ordinal = 2, + }, + { + .name = "ThincaPaymentGetInstance", + .patch = my_ThincaPaymentGetInstance, + .link = (void **) &next_ThincaPaymentGetInstance, + .ordinal = 2, + } +}; + +HRESULT epay_apply_registry_hooks(){ + HRESULT hr = reg_hook_push_key( + HKEY_LOCAL_MACHINE, + L"SOFTWARE\\TFPaymentService\\ThincaRwAdapter", + epay_adapter_keys, + _countof(epay_adapter_keys)); + + if (FAILED(hr)) { + return hr; + } + + hr = reg_hook_push_key( + HKEY_LOCAL_MACHINE, + L"SOFTWARE\\TFPaymentService\\ThincaTcapClient", + epay_tcap_keys, + _countof(epay_tcap_keys)); + + if (FAILED(hr)) { + return hr; + } + + hr = reg_hook_push_key( + HKEY_LOCAL_MACHINE, + L"SOFTWARE\\TFPaymentService\\ThincaTcapClient\\URL0", + epay_tcap_url0_keys, + _countof(epay_tcap_url0_keys)); + + if (FAILED(hr)) { + return hr; + } + + hr = reg_hook_push_key( + HKEY_LOCAL_MACHINE, + L"SOFTWARE\\TFPaymentService\\ThincaTcapClient\\URL1", + epay_tcap_url1_keys, + _countof(epay_tcap_url1_keys)); + + return hr; +} + +HRESULT epay_hook_init(const struct epay_config *cfg) { + HRESULT hr; + assert(cfg != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + hr = epay_apply_registry_hooks(); + if (FAILED(hr)){ + return hr; + } + + dprintf("EPay: Registry initialized\n"); + + // HACK:(?) the DLLs are loaded dynamically so we just preload it and apply DNS and VFS hooks to it + HMODULE thincahttpclient = LoadLibraryA("thincahttpclient.dll"); + if (thincahttpclient != NULL){ + dns_hook_apply_hooks(thincahttpclient); + path_hook_insert_hooks(thincahttpclient); + } + HMODULE thincapayment = LoadLibraryA("ThincaPayment.dll"); + if (thincapayment != NULL){ + path_hook_insert_hooks(thincapayment); + } + HMODULE thincatcapclient = LoadLibraryA("thincatcapclient.dll"); + if (thincatcapclient != NULL){ + path_hook_insert_hooks(thincatcapclient); + } + + if (cfg->hook) { + hook_table_apply( + NULL, + "ThincaPayment.dll", + epay_syms, + _countof(epay_syms)); + + thinca_stub = (struct thinca_main *) malloc(sizeof(struct thinca_main)); + thinca_stub->impl1 = (struct thinca_impl *) malloc(sizeof(struct thinca_impl)); + + thinca_stub->impl1->unk8 = thinca_unk8; + thinca_stub->impl1->initialize = thinca_initialize; + thinca_stub->impl1->dispose = thinca_dispose; + thinca_stub->impl1->setResource = thinca_set_resource; + thinca_stub->impl1->setThincaPaymentLog = thinca_set_pay_log; + thinca_stub->impl1->setThincaEventInterface = thinca_set_evt_handler; + thinca_stub->impl1->setIcasClientLog = thinca_set_client_log; + thinca_stub->impl1->setIcasClientConfig = thinca_set_client_cfg; + thinca_stub->impl1->setGoodsCode = thinca_set_goods_code; + thinca_stub->impl1->setTerminalSerial = thinca_set_serial; + thinca_stub->impl1->setClientCertificate = thinca_set_cert; + thinca_stub->impl1->checkDeal = thinca_check_deal; + thinca_stub->impl1->cancelRequest = thinca_cancel; + thinca_stub->impl1->selectButton = thinca_select; + thinca_stub->impl1->unk220 = thinca_unk; + thinca_stub->impl1->unk228 = thinca_unk; + + dprintf("Epay: Hooks initialized\n"); + } + + return hr; +} + +static HRESULT misc_read_thinca_adapter(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_wstr(bytes, nbytes, L"aime_rw_adapterMD.dll"); +} + +static HRESULT misc_read_ca_loc(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_wstr(bytes, nbytes, L"ca.pem"); +} + +static HRESULT misc_read_ca_client_loc(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_wstr(bytes, nbytes, L"thincatcapclient.dll"); +} + +static HRESULT misc_read_network_timeout(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_u32(bytes, nbytes, 20000); +} + +static HRESULT misc_read_pattern0(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_wstr(bytes, nbytes, L".*\\.jsp"); +} + +static HRESULT misc_read_network_timeout0(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_u32(bytes, nbytes, 5000); +} + +static HRESULT misc_read_pattern1(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_wstr(bytes, nbytes, L".*(closing|remove).*"); +} + +static HRESULT misc_read_network_timeout1(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_u32(bytes, nbytes, 60000); +} + +static uint64_t thinca_initialize(struct thinca_impl * self, uint64_t val) +{ + dprintf("Epay: Thinca Initialize %lld\n", val); + return 0; +} + +static uint64_t thinca_dispose(struct thinca_impl * self) +{ + dprintf("Epay: Thinca Dispose\n"); + return 0; +} + +static uint64_t thinca_set_resource(struct thinca_impl * self, char * res) +{ + dprintf("Epay: Thinca Set Resource %s\n", res); + return 0; +} + +static uint64_t thinca_set_pay_log(struct thinca_impl * self, uint64_t val, char * log, uint64_t val2, const char * size_lim) +{ + dprintf("Epay: Thinca Set Paylog %lld | %s | %lld | %s\n", val, log, val2, size_lim); + return 0; +} + +static uint64_t thinca_set_client_log(struct thinca_impl * self, uint64_t val, char * log) +{ + dprintf("Epay: Thinca Set ICAS Client log %lld | %s\n", val, log); + return 0; +} + +static uint64_t thinca_set_client_cfg(struct thinca_impl * self, char * log, uint64_t val) +{ + dprintf("Epay: Thinca Set ICAS Client Config %s | %lld\n", log, val); + return 0; +} + +static uint64_t thinca_set_goods_code(struct thinca_impl * self, char * code) +{ + dprintf("Epay: Thinca Set Goods Code %s\n", code); + return 0; +} + +static uint64_t thinca_set_evt_handler(struct thinca_impl * self, void* handler) +{ + dprintf("Epay: Thinca Set Event Handler %p\n", handler); + return 0; +} + +static uint64_t thinca_set_cert(struct thinca_impl * self, char * cert, uint64_t val) +{ + dprintf("Epay: Thinca Set Client Cert %s | %lld\n", cert, val); + return 0; +} + +static uint64_t thinca_set_serial(struct thinca_impl * self, char * cert) +{ + dprintf("Epay: Thinca Set Terminal Serial %s\n", cert); + return 0; +} + +static uint64_t thinca_check_deal(struct thinca_impl * self, void* deal) +{ + dprintf("Epay: Thinca Check Deal %p\n", deal); + return 0; +} + +static uint64_t thinca_cancel(struct thinca_impl * self) +{ + dprintf("Epay: Thinca Cancel\n"); + return 0; +} + +static uint64_t thinca_select(struct thinca_impl * self) +{ + dprintf("Epay: Thinca Select\n"); + return 0; +} + +static uint64_t thinca_unk(struct thinca_impl * self, uint64_t val) +{ + dprintf("Epay: Thinca Unknown 220/228 %lld\n", val); + return 0; +} + +static void thinca_unk8(struct thinca_impl * self) +{ + dprintf("Epay: Thinca Unknown 8\n"); +} + +static uint64_t my_ThincaPaymentGetVersion() +{ + return 0x1040B00; +} + +static struct thinca_main* my_ThincaPaymentGetInstance(uint64_t ver) +{ + dprintf("Epay: my_ThincaPaymentGetInstance hit!\n"); + return thinca_stub; +} \ No newline at end of file diff --git a/common/platform/epay.h b/common/platform/epay.h new file mode 100644 index 0000000..70aa886 --- /dev/null +++ b/common/platform/epay.h @@ -0,0 +1,66 @@ +#pragma once + +#include +#include +#include + +#pragma pack(push,1) +struct epay_config { + bool enable; + bool hook; +}; + +/* The functions in these structs are how clients like amdaemon interface + * with thinca. We can simply replace these functions with our own stubs + * to bypass errors and such. Currently this DOES NOT allow for epay, and + * trying to do so will most likely just lead to misery. My goal isn't to + * reimplement epay, just to give amdaemon SOMETHING so we can boot properly. + */ +struct thinca_impl { + uint64_t* unk0; + void (*unk8)(struct thinca_impl *); + uint64_t (*initialize)(struct thinca_impl *, uint64_t); + uint64_t (*dispose)(struct thinca_impl *); + uint64_t (*setResource)(struct thinca_impl *, char *); + uint64_t (*setThincaPaymentLog)(struct thinca_impl *, uint64_t, char *, uint64_t, const char *); + uint64_t (*setIcasClientLog)(struct thinca_impl *, uint64_t, char *); + uint64_t (*setIcasClientConfig)(struct thinca_impl *, char *, uint64_t); + uint64_t* unk40; + uint64_t* unk48; + uint64_t (*setClientCertificate)(struct thinca_impl *, char *, uint64_t); + uint64_t (*setTerminalSerial)(struct thinca_impl *, char *); + uint64_t (*setGoodsCode)(struct thinca_impl *, char *); + uint64_t unk68; + uint64_t (*setThincaEventInterface)(struct thinca_impl *, void*); // probably a struct + uint64_t unkGap78[7]; + uint64_t (*checkDeal)(struct thinca_impl *, void *); // probably a struct + uint64_t unkGapB8[41]; + uint64_t (*cancelRequest)(struct thinca_impl *); + uint64_t (*selectButton)(struct thinca_impl *); + uint64_t unkGap210[2]; + uint64_t (*unk220)(struct thinca_impl *, uint64_t); + uint64_t (*unk228)(struct thinca_impl *, uint64_t); +}; + +/* I believe the actual struct is 0x310 bytes, so for now I'm just + * implementing what I need and hoping the rest don't cause issues + * later. AMDaemon seems to only care about impl1 and deal_thing, + * at least from what I can tell + */ +struct thinca_main { + struct thinca_impl* impl1; + struct thinca_impl* impl2; + HANDLE* mutex1; + HANDLE* mutex2; + HANDLE* mutex3; + uint64_t* unk28; + uint64_t* unk30; + uint64_t* unk38; + uint64_t* unk40; + uint64_t* deal_thing; + uint64_t filler[88]; +}; + +#pragma pack(pop) +HRESULT epay_hook_init(const struct epay_config *cfg); +HRESULT epay_apply_registry_hooks(); \ No newline at end of file diff --git a/platform/hwmon.c b/common/platform/hwmon.c similarity index 95% rename from platform/hwmon.c rename to common/platform/hwmon.c index ac125d3..cc13c7a 100644 --- a/platform/hwmon.c +++ b/common/platform/hwmon.c @@ -1,6 +1,7 @@ #include #include +#include #include #include "hook/iohook.h" @@ -10,9 +11,7 @@ #include "util/dprintf.h" #include "util/str.h" -enum { - HWMON_IOCTL_READ_CPU_TEMP = 0x80006000, -}; +#define HWMON_IOCTL_READ_CPU_TEMP CTL_CODE(0x8000, 0x800, METHOD_BUFFERED, FILE_READ_ACCESS) static HRESULT hwmon_handle_irp(struct irp *irp); static HRESULT hwmon_handle_open(struct irp *irp); diff --git a/platform/hwmon.h b/common/platform/hwmon.h similarity index 100% rename from platform/hwmon.h rename to common/platform/hwmon.h diff --git a/platform/hwreset.c b/common/platform/hwreset.c similarity index 95% rename from platform/hwreset.c rename to common/platform/hwreset.c index 51ec337..7b37f1f 100644 --- a/platform/hwreset.c +++ b/common/platform/hwreset.c @@ -1,6 +1,7 @@ #include #include +#include #include #include "hook/iohook.h" @@ -10,9 +11,7 @@ #include "util/dprintf.h" #include "util/str.h" -enum { - HWRESET_IOCTL_RESTART = 0x80002000, -}; +#define HWRESET_IOCTL_RESTART CTL_CODE(0x8000, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS) static HRESULT hwreset_handle_irp(struct irp *irp); static HRESULT hwreset_handle_open(struct irp *irp); diff --git a/platform/hwreset.h b/common/platform/hwreset.h similarity index 100% rename from platform/hwreset.h rename to common/platform/hwreset.h diff --git a/platform/meson.build b/common/platform/meson.build similarity index 90% rename from platform/meson.build rename to common/platform/meson.build index 4f0fbc9..160f8af 100644 --- a/platform/meson.build +++ b/common/platform/meson.build @@ -2,7 +2,6 @@ platform_lib = static_library( 'platform', include_directories : inc, implicit_include_directories : false, - c_pch : '../precompiled.h', dependencies : [ capnhook.get_variable('hook_dep'), shlwapi_lib, @@ -16,6 +15,8 @@ platform_lib = static_library( 'config.h', 'dns.c', 'dns.h', + 'epay.c', + 'epay.h', 'hwmon.c', 'hwmon.h', 'hwreset.c', @@ -32,5 +33,7 @@ platform_lib = static_library( 'platform.h', 'vfs.c', 'vfs.h', + 'system.c', + 'system.h', ], ) diff --git a/platform/misc.c b/common/platform/misc.c similarity index 99% rename from platform/misc.c rename to common/platform/misc.c index 040a249..31a4d16 100644 --- a/platform/misc.c +++ b/common/platform/misc.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "hook/table.h" diff --git a/platform/misc.h b/common/platform/misc.h similarity index 100% rename from platform/misc.h rename to common/platform/misc.h diff --git a/platform/netenv.c b/common/platform/netenv.c similarity index 95% rename from platform/netenv.c rename to common/platform/netenv.c index 37a9eae..4fb77d2 100644 --- a/platform/netenv.c +++ b/common/platform/netenv.c @@ -1,10 +1,15 @@ #include -#include +#include #include +#include +#include +#include +#include #include #include #include +#include #include #include @@ -372,9 +377,12 @@ static uint32_t WINAPI hook_GetBestRoute( return ERROR_INVALID_PARAMETER; } - dprintf("Netenv: GetBestRoute ip4 %x -> ip4 %x\n", - (int) _byteswap_ulong(src_ip), - (int) _byteswap_ulong(dest_ip)); + uint32_t src_addr = _byteswap_ulong(src_ip); + uint32_t dest_addr = _byteswap_ulong(dest_ip); + + dprintf("Netenv: GetBestRoute ip4 %u.%u.%u.%u -> ip4 %u.%u.%u.%u\n", + (src_addr >> 24) & 0xff, (src_addr >> 16) & 0xff, (src_addr >> 8) & 0xff, src_addr & 0xff, + (dest_addr >> 24) & 0xff, (dest_addr >> 16) & 0xff, (dest_addr >> 8) & 0xff, dest_addr & 0xff); memset(route, 0, sizeof(*route)); @@ -469,8 +477,10 @@ static uint32_t WINAPI hook_IcmpSendEcho2( return 0; } - dprintf("Netenv: Virtualized ICMP Ping to ip4 %x\n", - (int) _byteswap_ulong(DestinationAddress)); + uint32_t addr = _byteswap_ulong(DestinationAddress); + + dprintf("Netenv: Virtualized ICMP Ping to ip4 %u.%u.%u.%u\n", + (addr >> 24) & 0xff, (addr >> 16) & 0xff, (addr >> 8) & 0xff, addr & 0xff); pong = (ICMP_ECHO_REPLY *) ReplyBuffer; memset(pong, 0, sizeof(*pong)); diff --git a/platform/netenv.h b/common/platform/netenv.h similarity index 100% rename from platform/netenv.h rename to common/platform/netenv.h diff --git a/platform/nusec.c b/common/platform/nusec.c similarity index 80% rename from platform/nusec.c rename to common/platform/nusec.c index cc1030b..17420f5 100644 --- a/platform/nusec.c +++ b/common/platform/nusec.c @@ -2,6 +2,7 @@ #include #include #include +#include #include "hook/iohook.h" @@ -13,23 +14,37 @@ #include "util/dump.h" #include "util/str.h" -enum { - NUSEC_IOCTL_PING = 0x22A114, - NUSEC_IOCTL_ERASE_TRACE_LOG = 0x22E188, - NUSEC_IOCTL_ADD_PLAY_COUNT = 0x22E154, - NUSEC_IOCTL_GET_BILLING_CA_CERT = 0x22E1C4, - NUSEC_IOCTL_GET_BILLING_PUBKEY = 0x22E1C8, - NUSEC_IOCTL_GET_NEARFULL = 0x22E20C, - NUSEC_IOCTL_GET_NVRAM_AVAILABLE = 0x22E19C, - NUSEC_IOCTL_GET_NVRAM_GEOMETRY = 0x22E24C, - NUSEC_IOCTL_GET_PLAY_COUNT = 0x22E150, - NUSEC_IOCTL_GET_PLAY_LIMIT = 0x22E204, - NUSEC_IOCTL_GET_TRACE_LOG_DATA = 0x22E194, - NUSEC_IOCTL_GET_TRACE_LOG_STATE = 0x22E198, - NUSEC_IOCTL_PUT_NEARFULL = 0x22E210, - NUSEC_IOCTL_PUT_PLAY_LIMIT = 0x22E208, - NUSEC_IOCTL_PUT_TRACE_LOG_DATA = 0x22E190, -}; +#define NUSEC_IOCTL_PING CTL_CODE(0x22, 0x845, METHOD_BUFFERED, FILE_WRITE_ACCESS) +#define NUSEC_IOCTL_GET_PLAY_COUNT CTL_CODE(0x22, 0x854, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define NUSEC_IOCTL_ADD_PLAY_COUNT CTL_CODE(0x22, 0x855, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define NUSEC_IOCTL_SET_IV CTL_CODE(0x22, 0x856, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define NUSEC_IOCTL_ENCRYPT CTL_CODE(0x22, 0x857, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define NUSEC_IOCTL_DECRYPT CTL_CODE(0x22, 0x858, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define NUSEC_IOCTL_TD_RESTORE CTL_CODE(0x22, 0x861, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define NUSEC_IOCTL_ERASE_TRACE_LOG CTL_CODE(0x22, 0x862, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define NUSEC_IOCTL_TD_ERASE_USED CTL_CODE(0x22, 0x863, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define NUSEC_IOCTL_PUT_TRACE_LOG_DATA CTL_CODE(0x22, 0x864, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define NUSEC_IOCTL_GET_TRACE_LOG_DATA CTL_CODE(0x22, 0x865, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define NUSEC_IOCTL_GET_TRACE_LOG_STATE CTL_CODE(0x22, 0x866, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define NUSEC_IOCTL_GET_NVRAM_AVAILABLE CTL_CODE(0x22, 0x867, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define NUSEC_IOCTL_TD_ERASE_ALL CTL_CODE(0x22, 0x869, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define NUSEC_IOCTL_GET_BILLING_CA_CERT CTL_CODE(0x22, 0x871, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define NUSEC_IOCTL_GET_BILLING_PUBKEY CTL_CODE(0x22, 0x872, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define NUSEC_IOCTL_GET_PLAY_LIMIT CTL_CODE(0x22, 0x881, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define NUSEC_IOCTL_PUT_PLAY_LIMIT CTL_CODE(0x22, 0x882, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define NUSEC_IOCTL_GET_NEARFULL CTL_CODE(0x22, 0x883, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define NUSEC_IOCTL_PUT_NEARFULL CTL_CODE(0x22, 0x884, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define NUSEC_IOCTL_NVRAM_READ CTL_CODE(0x22, 0x891, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define NUSEC_IOCTL_NVRAM_WRITE CTL_CODE(0x22, 0x892, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define NUSEC_IOCTL_GET_NVRAM_GEOMETRY CTL_CODE(0x22, 0x893, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define NUSEC_IOCTL_AL_AUTH_START CTL_CODE(0x22, 0x8B0, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define NUSEC_IOCTL_AL_AUTH_PACKET CTL_CODE(0x22, 0x8B2, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) + +// Referenced in AMDaemon but seemingly not used, maybe leftovers? +#define NUSEC_IOCTL_UNK_843 CTL_CODE(0x22, 0x843, METHOD_BUFFERED, FILE_WRITE_ACCESS) +#define NUSEC_IOCTL_UNK_844 CTL_CODE(0x22, 0x844, METHOD_BUFFERED, FILE_WRITE_ACCESS) +#define NUSEC_IOCTL_UNK_894 CTL_CODE(0x22, 0x894, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define NUSEC_IOCTL_UNK_895 CTL_CODE(0x22, 0x895, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) struct nusec_log_record { uint8_t unknown[60]; @@ -42,6 +57,7 @@ static HRESULT nusec_handle_ioctl(struct irp *irp); static HRESULT nusec_ioctl_ping(struct irp *irp); static HRESULT nusec_ioctl_erase_trace_log(struct irp *irp); +static HRESULT nusec_ioctl_td_erase_used(struct irp *irp); static HRESULT nusec_ioctl_add_play_count(struct irp *irp); static HRESULT nusec_ioctl_get_billing_ca_cert(struct irp *irp); static HRESULT nusec_ioctl_get_billing_pubkey(struct irp *irp); @@ -208,6 +224,9 @@ static HRESULT nusec_handle_ioctl(struct irp *irp) case NUSEC_IOCTL_ERASE_TRACE_LOG: return nusec_ioctl_erase_trace_log(irp); + + case NUSEC_IOCTL_TD_ERASE_USED: + return nusec_ioctl_td_erase_used(irp); case NUSEC_IOCTL_ADD_PLAY_COUNT: return nusec_ioctl_add_play_count(irp); @@ -288,6 +307,16 @@ static HRESULT nusec_ioctl_erase_trace_log(struct irp *irp) return S_OK; } +static HRESULT nusec_ioctl_td_erase_used(struct irp *irp) +{ + dprintf("Security: %s\n", __func__); + + nusec_log_head = 0; + nusec_log_tail = 0; + + return S_OK; +} + static HRESULT nusec_ioctl_add_play_count(struct irp *irp) { uint32_t delta; diff --git a/platform/nusec.h b/common/platform/nusec.h similarity index 100% rename from platform/nusec.h rename to common/platform/nusec.h diff --git a/platform/pcbid.c b/common/platform/pcbid.c similarity index 100% rename from platform/pcbid.c rename to common/platform/pcbid.c diff --git a/platform/pcbid.h b/common/platform/pcbid.h similarity index 100% rename from platform/pcbid.h rename to common/platform/pcbid.h diff --git a/platform/platform.c b/common/platform/platform.c similarity index 83% rename from platform/platform.c rename to common/platform/platform.c index 218204c..c882760 100644 --- a/platform/platform.c +++ b/common/platform/platform.c @@ -5,6 +5,7 @@ #include "platform/amvideo.h" #include "platform/clock.h" #include "platform/dns.h" +#include "platform/epay.h" #include "platform/hwmon.h" #include "platform/misc.h" #include "platform/netenv.h" @@ -12,6 +13,7 @@ #include "platform/pcbid.h" #include "platform/platform.h" #include "platform/vfs.h" +#include "platform/system.h" HRESULT platform_hook_init( const struct platform_config *cfg, @@ -74,7 +76,19 @@ HRESULT platform_hook_init( return hr; } - hr = vfs_hook_init(&cfg->vfs); + hr = vfs_hook_init(&cfg->vfs, game_id); + + if (FAILED(hr)) { + return hr; + } + + hr = epay_hook_init(&cfg->epay); + + if (FAILED(hr)) { + return hr; + } + + hr = system_init(&cfg->system, &cfg->vfs); if (FAILED(hr)) { return hr; diff --git a/platform/platform.h b/common/platform/platform.h similarity index 87% rename from platform/platform.h rename to common/platform/platform.h index 69c65e2..0b69f12 100644 --- a/platform/platform.h +++ b/common/platform/platform.h @@ -5,6 +5,7 @@ #include "platform/amvideo.h" #include "platform/clock.h" #include "platform/dns.h" +#include "platform/epay.h" #include "platform/hwmon.h" #include "platform/hwreset.h" #include "platform/misc.h" @@ -12,11 +13,13 @@ #include "platform/nusec.h" #include "platform/pcbid.h" #include "platform/vfs.h" +#include "platform/system.h" struct platform_config { struct amvideo_config amvideo; struct clock_config clock; struct dns_config dns; + struct epay_config epay; struct hwmon_config hwmon; struct hwreset_config hwreset; struct misc_config misc; @@ -24,6 +27,7 @@ struct platform_config { struct netenv_config netenv; struct nusec_config nusec; struct vfs_config vfs; + struct system_config system; }; HRESULT platform_hook_init( diff --git a/common/platform/system.c b/common/platform/system.c new file mode 100644 index 0000000..c9dd1fd --- /dev/null +++ b/common/platform/system.c @@ -0,0 +1,174 @@ +#include "platform/system.h" + +#include +#include +#include +#include +#include + +#include "platform/vfs.h" +#include "util/crc.h" +#include "util/dprintf.h" +#include "util/str.h" + +#define DATA_SIZE 503 +#define BLOCK_SIZE (sizeof(uint32_t) + 4 + 1 + DATA_SIZE) + +#pragma pack(push, 1) + +typedef struct { + uint32_t checksum; + char padding[6]; + uint8_t freeplay; + char data[DATA_SIZE - 2]; +} CreditBlock; + +typedef struct { + uint32_t checksum; + char padding[4]; + uint8_t dip_switches; + char data[DATA_SIZE]; +} DipSwBlock; + +typedef struct { + CreditBlock credit_block; + DipSwBlock dip_switch_block; + char *data; +} SystemInfo; + +#pragma pack(pop) + +static SystemInfo system_info; + +static struct system_config system_config; +static struct vfs_config vfs_config; + +static void system_read_sysfile(const wchar_t *sys_file); +static void system_save_sysfile(const wchar_t *sys_file); + +HRESULT system_init(const struct system_config *cfg, + const struct vfs_config *vfs_cfg) { + HRESULT hr; + wchar_t sys_file_path[MAX_PATH]; + + assert(cfg != NULL); + assert(vfs_cfg != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + memcpy(&system_config, cfg, sizeof(*cfg)); + + sys_file_path[0] = L'\0'; + // concatenate vfs_config.amfs with L"sysfile.dat" + wcsncpy(sys_file_path, vfs_cfg->amfs, MAX_PATH); + wcsncat(sys_file_path, L"\\sysfile.dat", MAX_PATH); + + system_read_sysfile(sys_file_path); + + // now write the system_config.system to the dip_switch_block + system_save_sysfile(sys_file_path); + + return S_OK; +} + +static void system_read_sysfile(const wchar_t *sys_file) { + FILE *f = _wfopen(sys_file, L"r"); + + if (f == NULL) { + dprintf( + "System: First run detected, system settings can only be applied " + "AFTER the first run\n"); + return; + } + + fseek(f, 0, SEEK_END); + long file_size = ftell(f); + fseek(f, 0, SEEK_SET); + + if (file_size != 0x6000) { + dprintf("System: Invalid sysfile.dat file size\n"); + fclose(f); + + return; + } + + system_info.data = malloc(file_size); + fread(system_info.data, 1, file_size, f); + fclose(f); + + // copy the credit_block and dip_switch_block from the sysfile.dat + memcpy(&system_info.credit_block, system_info.data, BLOCK_SIZE); + memcpy(&system_info.dip_switch_block, system_info.data + 0x2800, + BLOCK_SIZE); +} + +static void system_save_sysfile(const wchar_t *sys_file) { + char block[BLOCK_SIZE]; + DWORD sysfile_bytes_written = 0; + uint8_t system = 0; + uint8_t freeplay = 0; + + // open the sysfile.dat for writing + HANDLE h_sysfile = CreateFileW(sys_file, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + + if (h_sysfile == INVALID_HANDLE_VALUE) { + return; + } + + // write the system_config.system to the dip_switch_block + for (int i = 0; i < 8; i++) { + // print the dip switch configuration with labels if present + if (system_config.dipsw_config[i].label[0] != L'\0') { + dprintf("System: %ls: %ls\n", system_config.dipsw_config[i].label, + system_config.dipsw[i] ? system_config.dipsw_config[i].on + : system_config.dipsw_config[i].off); + } else if (system_config.dipsw[i]) { + dprintf("System: DipSw%d=1 set\n", i + 1); + } + + // set the system variable to the dip switch configuration + if (system_config.dipsw[i]) { + system |= (1 << i); + } + } + + if (system_config.freeplay) { + // print that freeplay is enabled + dprintf("System: Freeplay enabled\n"); + freeplay = 1; + } + + // set the new credit block + system_info.credit_block.freeplay = freeplay; + // set the new dip_switch_block + system_info.dip_switch_block.dip_switches = system; + + // calculate the new checksum, skip the old crc32 value + // which is at the beginning of the block, thats's why the +4 + // conver the struct to chars in order for the crc32 calculation to work + system_info.credit_block.checksum = + crc32((char *)&system_info.credit_block + 4, BLOCK_SIZE - 4, 0); + system_info.dip_switch_block.checksum = + crc32((char *)&system_info.dip_switch_block + 4, BLOCK_SIZE - 4, 0); + + // build the new credit block + memcpy(block, (char *)&system_info.credit_block, BLOCK_SIZE); + + memcpy(system_info.data, block, BLOCK_SIZE); + memcpy(system_info.data + 0x3000, block, BLOCK_SIZE); + + // build the new dip switch block + memcpy(block, (char *)&system_info.dip_switch_block, BLOCK_SIZE); + + memcpy(system_info.data + 0x2800, block, BLOCK_SIZE); + memcpy(system_info.data + 0x5800, block, BLOCK_SIZE); + + WriteFile(h_sysfile, system_info.data, 0x6000, &sysfile_bytes_written, NULL); + CloseHandle(h_sysfile); + + if (sysfile_bytes_written != 0x6000) { + dprintf("System: Only 0x%04lX bytes written out of 0x6000!\n", sysfile_bytes_written); + } +} diff --git a/common/platform/system.h b/common/platform/system.h new file mode 100644 index 0000000..e69ce90 --- /dev/null +++ b/common/platform/system.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include +#include + +#include "platform/vfs.h" + + +struct dipsw_config +{ + wchar_t label[MAX_PATH]; + wchar_t on[MAX_PATH]; + wchar_t off[MAX_PATH]; +}; + + +struct system_config { + bool enable; + bool freeplay; + bool dipsw[8]; + struct dipsw_config dipsw_config[8]; +}; + +HRESULT system_init(const struct system_config *cfg, const struct vfs_config *vfs_cfg); diff --git a/platform/vfs.c b/common/platform/vfs.c similarity index 69% rename from platform/vfs.c rename to common/platform/vfs.c index d356d7f..e16389a 100644 --- a/platform/vfs.c +++ b/common/platform/vfs.c @@ -9,6 +9,9 @@ #include "hooklib/path.h" #include "hooklib/reg.h" +#include "hook/procaddr.h" +#include "hook/table.h" + #include "platform/vfs.h" #include "util/dprintf.h" @@ -20,6 +23,10 @@ static HRESULT vfs_path_hook_nthome( const wchar_t *src, wchar_t *dest, size_t *count); +static HRESULT vfs_path_hook_w10home( + const wchar_t *src, + wchar_t *dest, + size_t *count); static HRESULT vfs_path_hook_option( const wchar_t *src, wchar_t *dest, @@ -27,10 +34,33 @@ static HRESULT vfs_path_hook_option( static HRESULT vfs_reg_read_amfs(void *bytes, uint32_t *nbytes); static HRESULT vfs_reg_read_appdata(void *bytes, uint32_t *nbytes); +static wchar_t* hook_System_getAppRootPath(); +static wchar_t* (*next_System_getAppRootPath)(); + +static wchar_t* hook_AppImage_getOptionMountRootPath(); +static wchar_t* (*next_AppImage_getOptionMountRootPath)(); + +static const struct hook_symbol amdaemon_syms[] = { + { + .name = "System_getAppRootPath", + .patch = hook_System_getAppRootPath, + .link = (void **) &next_System_getAppRootPath, + }, + { + .name = "AppImage_getOptionMountRootPath", + .patch = hook_AppImage_getOptionMountRootPath, + .link = (void **) &next_AppImage_getOptionMountRootPath, + }, +}; + +static wchar_t game[5] = {0}; static wchar_t vfs_nthome_real[MAX_PATH]; static const wchar_t vfs_nthome[] = L"C:\\Documents and Settings\\AppUser"; static const size_t vfs_nthome_len = _countof(vfs_nthome) - 1; +static const wchar_t vfs_w10home[] = L"C:\\Users\\AppUser"; +static const size_t vfs_w10home_len = _countof(vfs_w10home) - 1; + static const wchar_t vfs_option[] = L"C:\\Mount\\Option"; static const size_t vfs_option_len = _countof(vfs_option) - 1; @@ -48,7 +78,7 @@ static const struct reg_hook_val vfs_reg_vals[] = { static struct vfs_config vfs_config; -HRESULT vfs_hook_init(const struct vfs_config *config) +HRESULT vfs_hook_init(const struct vfs_config *config, const char* game_id) { wchar_t temp[MAX_PATH]; size_t nthome_len; @@ -61,6 +91,8 @@ HRESULT vfs_hook_init(const struct vfs_config *config) return S_FALSE; } + mbstowcs(game, game_id, 4); + if (config->amfs[0] == L'\0') { dprintf("Vfs: FATAL: AMFS path not specified in INI file\n"); @@ -75,6 +107,20 @@ HRESULT vfs_hook_init(const struct vfs_config *config) if (config->option[0] == L'\0') { dprintf("Vfs: WARNING: OPTION path not specified in INI file\n"); + } else if (!PathFileExistsW(config->option)) { + dprintf("Vfs: FATAL: OPTION path does not exist\n"); + dprintf(" Configured: \"%ls\"\n", config->option); + GetFullPathNameW(config->option, _countof(temp), temp, NULL); + dprintf(" Expanded: \"%ls\"\n", temp); + + return E_FAIL; + } else if (!(GetFileAttributesW(config->option) & FILE_ATTRIBUTE_DIRECTORY)) { + dprintf("Vfs: FATAL: OPTION path doesn't point to a directory\n"); + dprintf(" Configured: \"%ls\"\n", config->option); + GetFullPathNameW(config->option, _countof(temp), temp, NULL); + dprintf(" Expanded: \"%ls\"\n", temp); + + return E_FAIL; } home_ok = GetEnvironmentVariableW( @@ -144,6 +190,12 @@ HRESULT vfs_hook_init(const struct vfs_config *config) return hr; } + hr = path_hook_push(vfs_path_hook_w10home); + + if (FAILED(hr)) { + return hr; + } + if (vfs_config.option[0] != L'\0') { hr = path_hook_push(vfs_path_hook_option); @@ -162,6 +214,13 @@ HRESULT vfs_hook_init(const struct vfs_config *config) return hr; } + proc_addr_table_push( + NULL, + "amdaemon_api.dll", + amdaemon_syms, + _countof(amdaemon_syms) + ); + return S_OK; } @@ -264,11 +323,12 @@ static HRESULT vfs_path_hook(const wchar_t *src, wchar_t *dest, size_t *count) const wchar_t *redir; size_t required; size_t redir_len; + size_t src_len; assert(src != NULL); assert(count != NULL); - if (src[0] == L'\0' || src[1] != L':' || !path_is_separator_w(src[2])) { + if (src[0] == L'\0' || src[1] != L':') { return S_FALSE; } @@ -289,10 +349,15 @@ static HRESULT vfs_path_hook(const wchar_t *src, wchar_t *dest, size_t *count) return S_FALSE; } + /* GetFileAttributesW would request the src "E:", so fix the src_len in + in order to redirect the drive letter successfully */ + + src_len = path_is_separator_w(src[2]) ? 3 : 2; + /* Cut off \, replace with redir path, count NUL terminator */ redir_len = wcslen(redir); - required = wcslen(src) - 3 + redir_len + 1; + required = wcslen(src) - src_len + redir_len + 1; if (dest != NULL) { if (required > *count) { @@ -300,7 +365,7 @@ static HRESULT vfs_path_hook(const wchar_t *src, wchar_t *dest, size_t *count) } wcscpy_s(dest, *count, redir); - wcscpy_s(dest + redir_len, *count - redir_len, src + 3); + wcscpy_s(dest + redir_len, *count - redir_len, src + src_len); } *count = required; @@ -355,6 +420,53 @@ static HRESULT vfs_path_hook_nthome( return S_OK; } +static HRESULT vfs_path_hook_w10home( + const wchar_t *src, + wchar_t *dest, + size_t *count) +{ + size_t required; + size_t redir_len; + size_t shift; + + assert(src != NULL); + assert(count != NULL); + + /* Case-insensitive check to see if src starts with vfs_w10home */ + + if (path_compare_w(src, vfs_w10home, vfs_w10home_len) != 0) { + return S_FALSE; + } + + /* Check if the character after vfs_w10home is a separator or the end of + the string */ + + if (!path_is_separator_w(src[vfs_w10home_len]) && + src[vfs_w10home_len] != L'\0') + { + return S_FALSE; + } + + /* Cut off the matched \, add the replaced prefix, count NUL */ + + shift = path_is_separator_w(src[vfs_w10home_len]) ? 1 : 0; + redir_len = wcslen(vfs_nthome_real); + required = wcslen(src) - vfs_w10home_len - shift + redir_len + 1; + + if (dest != NULL) { + if (required > *count) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + wcscpy_s(dest, *count, vfs_nthome_real); + wcscpy_s(dest + redir_len, *count - redir_len, src + vfs_w10home_len + shift); + } + + *count = required; + + return S_OK; +} + static HRESULT vfs_path_hook_option( const wchar_t *src, wchar_t *dest, @@ -404,10 +516,28 @@ static HRESULT vfs_path_hook_option( static HRESULT vfs_reg_read_amfs(void *bytes, uint32_t *nbytes) { - return reg_hook_read_wstr(bytes, nbytes, vfs_config.amfs); + return reg_hook_read_wstr(bytes, nbytes, L"E:\\"); } static HRESULT vfs_reg_read_appdata(void *bytes, uint32_t *nbytes) { - return reg_hook_read_wstr(bytes, nbytes, vfs_config.appdata); + return reg_hook_read_wstr(bytes, nbytes, L"Y:\\"); +} + +static wchar_t* hook_System_getAppRootPath() +{ + wchar_t *path = malloc(sizeof(wchar_t) * MAX_PATH); + wcscpy_s(path, MAX_PATH, vfs_config.appdata); + wcscat_s(path, MAX_PATH, game); + wcscat_s(path, MAX_PATH, L"\\"); + + return path; +} + +static wchar_t* hook_AppImage_getOptionMountRootPath() +{ + wchar_t *path = malloc(sizeof(wchar_t) * MAX_PATH); + wcscpy_s(path, MAX_PATH, vfs_config.option); + + return path; } diff --git a/platform/vfs.h b/common/platform/vfs.h similarity index 72% rename from platform/vfs.h rename to common/platform/vfs.h index 8767305..a7e513f 100644 --- a/platform/vfs.h +++ b/common/platform/vfs.h @@ -12,4 +12,4 @@ struct vfs_config { wchar_t option[MAX_PATH]; }; -HRESULT vfs_hook_init(const struct vfs_config *config); +HRESULT vfs_hook_init(const struct vfs_config *config, const char* game_id); diff --git a/common/unityhook/config.c b/common/unityhook/config.c new file mode 100644 index 0000000..fe4e062 --- /dev/null +++ b/common/unityhook/config.c @@ -0,0 +1,16 @@ +#include + +#include "config.h" + +void unity_config_load(struct unity_config *cfg, const wchar_t *filename) { + cfg->enable = GetPrivateProfileIntW(L"unity", L"enable", 1, filename); + + GetPrivateProfileStringW( + L"unity", + L"targetAssembly", + L"", + cfg->target_assembly, + _countof(cfg->target_assembly), + filename + ); +} diff --git a/common/unityhook/config.h b/common/unityhook/config.h new file mode 100644 index 0000000..6857c4b --- /dev/null +++ b/common/unityhook/config.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +#include + +struct unity_config { + bool enable; + wchar_t target_assembly[MAX_PATH]; +}; + +void unity_config_load(struct unity_config *cfg, const wchar_t *filename); diff --git a/common/unityhook/doorstop.c b/common/unityhook/doorstop.c new file mode 100644 index 0000000..04dee02 --- /dev/null +++ b/common/unityhook/doorstop.c @@ -0,0 +1,187 @@ +// A simplified version of NeighTools' UnityDoorstop, allowing mod loaders +// like BepInEx to be loaded into Unity games. +// +// SPDX-License-Identifier: CC0 +// https://github.com/NeighTools/UnityDoorstop +#include +#include +#include +#include + +#include +#include + +#include "hook/procaddr.h" +#include "util/dprintf.h" + +#include "doorstop.h" +#include "mono.h" +#include "util.h" + +static void * my_mono_jit_init_version(const char *root_domain_name, const char *runtime_version); +void doorstop_invoke(void *domain); + +static char module_name[MAX_PATH]; +static bool doorstop_hook_initted; +static struct unity_config unity_config; +static struct hook_symbol unity_mono_syms[] = { +{ + .name = "mono_jit_init_version", + .patch = my_mono_jit_init_version, + } +}; + +void doorstop_mono_hook_init(const struct unity_config *cfg, HINSTANCE module) { + if (doorstop_hook_initted || cfg->target_assembly[0] == 0) { + return; + } + + GetModuleBaseNameA(GetCurrentProcess(), module, module_name, MAX_PATH); + + memcpy(&unity_config, cfg, sizeof(*cfg)); + load_mono_functions(module); + proc_addr_table_push(NULL, module_name, unity_mono_syms, _countof(unity_mono_syms)); + + doorstop_hook_initted = true; +} + +static void * my_mono_jit_init_version(const char *root_domain_name, const char *runtime_version) { + dprintf("Unity: Starting Mono domain \"%s\"\n", root_domain_name); + + SetEnvironmentVariableW(L"DOORSTOP_DLL_SEARCH_DIRS", widen(mono_assembly_getrootdir())); + + void* domain = mono_jit_init_version(root_domain_name, runtime_version); + + doorstop_invoke(domain); + return domain; +} + +void doorstop_invoke(void* domain) { + if (GetEnvironmentVariableW(L"DOORSTOP_INITIALIZED", NULL, 0) != 0) { + dprintf("Unity: Doorstop is already initialized.\n"); + return; + } + + SetEnvironmentVariableW(L"DOORSTOP_INITIALIZED", L"TRUE"); + + mono_thread_set_main(mono_thread_current()); + + if (mono_domain_set_config) { +#define CONFIG_EXT L".config" + + wchar_t config_path[MAX_PATH]; + size_t config_path_len = GetModuleFileNameW(NULL, config_path, MAX_PATH); + wchar_t *folder_name = wcsdup(config_path); + + PathCchRemoveFileSpec(folder_name, config_path_len + 1); + wmemcpy(config_path + config_path_len, CONFIG_EXT, sizeof(CONFIG_EXT) / sizeof(CONFIG_EXT[0])); + + char *config_path_n = narrow(config_path); + char *folder_name_n = narrow(folder_name); + + dprintf("Unity: Setting config paths: base dir: %s; config path: %s\n", folder_name_n, config_path_n); + + mono_domain_set_config(domain, folder_name_n, config_path_n); + + free(folder_name); + free(config_path_n); + free(folder_name_n); + +#undef CONFIG_EXT + } + + SetEnvironmentVariableW(L"DOORSTOP_INVOKE_DLL_PATH", unity_config.target_assembly); + + char *assembly_dir = mono_assembly_getrootdir(); + dprintf("Unity: Assembly directory: %s\n", assembly_dir); + + SetEnvironmentVariableA("DOORSTOP_MANAGED_FOLDER_DIR", assembly_dir); + + wchar_t app_path[MAX_PATH]; + GetModuleFileNameW(NULL, app_path, MAX_PATH); + SetEnvironmentVariableW(L"DOORSTOP_PROCESS_PATH", app_path); + + char* dll_path = narrow(unity_config.target_assembly); + + dprintf("Unity: Loading assembly: %s\n", dll_path); + + void* assembly = mono_domain_assembly_open(domain, dll_path); + + if (!assembly) { + dprintf("Unity: Failed to load assembly\n"); + free(dll_path); + return; + } + + void *image = mono_assembly_get_image(assembly); + + if (!image) { + dprintf("Unity: Assembly image doesn't exist\n"); + free(dll_path); + return; + } + + // BepInEx 5.4.23 has upgrade its doorstop version, + // which forces entrypoint to Doorstop.Entrypoint:Start + + void *desc = mono_method_desc_new("Doorstop.Entrypoint:Start", TRUE); + void *method = mono_method_desc_search_in_image(desc, image); + + if (!method) { + // Fallback to old entrypoint definition. + + desc = mono_method_desc_new("*:Main", FALSE); + method = mono_method_desc_search_in_image(desc, image); + } + + if (!method) { + dprintf("Unity: Assembly does not have a valid entrypoint.\n"); + free(dll_path); + return; + } + + void *signature = mono_method_signature(method); + UINT32 params = mono_signature_get_param_count(signature); + void **args = NULL; + + if (params == 1) { + // If there is a parameter, it's most likely a string[]. + void *args_array = mono_array_new(domain, mono_get_string_class(), 0); + args = malloc(sizeof(void*) * 1); + args[0] = args_array; + } + + dprintf("Unity: Invoking method %p\n", method); + + void *exc = NULL; + mono_runtime_invoke(method, NULL, args, &exc); + + if (exc) { + dprintf("Unity: Error invoking method!\n"); + + void *ex_class = mono_get_exception_class(); + void *to_string_desc = mono_method_desc_new("*:ToString()", FALSE); + void* to_string_method = mono_method_desc_search_in_class(to_string_desc, ex_class); + + mono_method_desc_free(to_string_desc); + + if (to_string_method) { + void* real_to_string_method = mono_object_get_virtual_method(exc, to_string_method); + void* exc2 = NULL; + void* str = mono_runtime_invoke(real_to_string_method, exc, NULL, &exc2); + + if (!exc2) { + char* exc_str = mono_string_to_utf8(str); + dprintf("Unity: Error message: %s\n", exc_str); + } + } + } + + mono_method_desc_free(desc); + free(dll_path); + + if (args) { + free(args); + args = NULL; + } +} diff --git a/common/unityhook/doorstop.h b/common/unityhook/doorstop.h new file mode 100644 index 0000000..2a44f33 --- /dev/null +++ b/common/unityhook/doorstop.h @@ -0,0 +1,5 @@ +#pragma once + +#include "config.h" + +void doorstop_mono_hook_init(const struct unity_config *cfg, HINSTANCE module); \ No newline at end of file diff --git a/common/unityhook/hook.c b/common/unityhook/hook.c new file mode 100644 index 0000000..33070a0 --- /dev/null +++ b/common/unityhook/hook.c @@ -0,0 +1,165 @@ +#include +#include +#include +#include + +#include "platform/clock.h" + +#include "hook/table.h" +#include "hook/procaddr.h" +#include "hook/iohook.h" + +#include "hooklib/dll.h" +#include "hooklib/path.h" +#include "hooklib/printer.h" +#include "hooklib/reg.h" +#include "hooklib/touch.h" +#include "hooklib/serial.h" + +#include "util/dprintf.h" + +#include "doorstop.h" +#include "hook.h" + +static bool unity_hook_initted; +static struct unity_config unity_config; + +static const wchar_t *target_modules[] = { + L"mono.dll", + L"mono-2.0-bdwgc.dll", + L"cri_ware_unity.dll", + L"amdaemon_api.dll", + L"SerialPortAPI.dll", + L"C300usb.dll", + L"C300FWDLusb.dll", + L"apmled.dll", + L"HKBSys_api.dll", + L"amptw.dll" +}; + +static const size_t target_modules_len = _countof(target_modules); + +static void dll_hook_insert_hooks(HMODULE target); + +static unity_hook_callback_func hook_load_callback; + +static HMODULE WINAPI hook_LoadLibraryW(const wchar_t *name); +static HMODULE (WINAPI *next_LoadLibraryW)(const wchar_t *name); +static HMODULE WINAPI hook_LoadLibraryExW(const wchar_t *name, HANDLE hFile, DWORD dwFlags); +static HMODULE (WINAPI *next_LoadLibraryExW)(const wchar_t *name, HANDLE hFile, DWORD dwFlags); + +static const struct hook_symbol unity_kernel32_syms[] = { + { + .name = "LoadLibraryW", + .patch = hook_LoadLibraryW, + .link = (void **) &next_LoadLibraryW, + }, { + .name = "LoadLibraryExW", + .patch = hook_LoadLibraryExW, + .link = (void **) &next_LoadLibraryExW, + } +}; + + +void unity_hook_init(const struct unity_config *cfg, HINSTANCE self, unity_hook_callback_func callback) { + assert(cfg != NULL); + + if (!cfg->enable) { + return; + } + + if (unity_hook_initted) { + return; + } + + memcpy(&unity_config, cfg, sizeof(*cfg)); + dll_hook_insert_hooks(NULL); + + hook_load_callback = callback; + + unity_hook_initted = true; + dprintf("Unity: Hook enabled.\n"); +} + +static void dll_hook_insert_hooks(HMODULE target) { + hook_table_apply( + target, + "kernel32.dll", + unity_kernel32_syms, + _countof(unity_kernel32_syms)); +} + +static HMODULE WINAPI hook_LoadLibraryExW(const wchar_t *name, HANDLE hFile, DWORD dwFlags) +{ + // dprintf("Unity: LoadLibraryExW %ls\n", name); + return hook_LoadLibraryW(name); +} + +static HMODULE WINAPI hook_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); + + // mono entrypoint for injecting target_assembly + if (GetProcAddress(result, "mono_jit_init_version")) { + doorstop_mono_hook_init(&unity_config, result); + } + + for (size_t i = 0; i < target_modules_len; i++) { + target_module = target_modules[i]; + target_module_len = wcslen(target_module); + + // Check if the newly loaded library is at least the length of + // the name of the target module + if (name_len < target_module_len) { + continue; + } + + name_end = &name[name_len - target_module_len]; + + // Check if the name of the newly loaded library is one of the + // modules the path hooks should be injected into + if (_wcsicmp(name_end, target_module) != 0) { + continue; + } + + dprintf("Unity: Loaded %S\n", target_module); + + dll_hook_insert_hooks(result); + path_hook_insert_hooks(result); + + reg_hook_insert_hooks(result); + clock_hook_insert_hooks(result); + proc_addr_insert_hooks(result); + if (hook_load_callback != NULL){ + hook_load_callback(result, target_module); + } + + // Not needed? + // serial_hook_apply_hooks(result); + // Unity will crash during option loading when we hook this twice + // iohook_apply_hooks(result); + } + } + + return result; +} diff --git a/common/unityhook/hook.h b/common/unityhook/hook.h new file mode 100644 index 0000000..a7c75e1 --- /dev/null +++ b/common/unityhook/hook.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +#include "config.h" + +typedef void (*unity_hook_callback_func)(HMODULE, const wchar_t*); + +void unity_hook_init(const struct unity_config *cfg, HINSTANCE self, unity_hook_callback_func callback); diff --git a/common/unityhook/meson.build b/common/unityhook/meson.build new file mode 100644 index 0000000..f541c1e --- /dev/null +++ b/common/unityhook/meson.build @@ -0,0 +1,19 @@ +unityhook_lib = static_library( + 'unityhook', + include_directories: inc, + implicit_include_directories: false, + dependencies: [ + capnhook.get_variable('hook_dep'), + pathcch_lib + ], + sources: [ + 'mono.h', + 'config.c', + 'config.h', + 'doorstop.c', + 'doorstop.h', + 'hook.c', + 'hook.h', + 'util.h' + ], +) diff --git a/common/unityhook/mono.h b/common/unityhook/mono.h new file mode 100644 index 0000000..39fdc01 --- /dev/null +++ b/common/unityhook/mono.h @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: CC0 +// https://github.com/NeighTools/UnityDoorstop +#pragma once + +#include + +// Here we define the pointers to some functions within mono.dll +// Note to C learners: these are not signature definitions, but rather "variable" +// definitions with the function pointer type. + +// Note: we use void* instead of the real intented structs defined in mono API +// This way we don't need to include or define any of Mono's structs, which saves space +// This, obviously, comes with a drawback of not being able to easily access the contents of the structs + +void * (*mono_thread_current)(); +void (*mono_thread_set_main)(void *); + +void *(*mono_jit_init_version)(const char *root_domain_name, const char *runtime_version); +void *(*mono_domain_assembly_open)(void *domain, const char *name); +void *(*mono_assembly_get_image)(void *assembly); +void *(*mono_runtime_invoke)(void *method, void *obj, void **params, void **exc); + +void *(*mono_method_desc_new)(const char *name, int include_namespace); +void* (*mono_method_desc_search_in_image)(void* desc, void* image); +void *(*mono_method_desc_search_in_class)(void *desc, void *klass); +void (*mono_method_desc_free)(void *desc); +void *(*mono_method_signature)(void *method); +UINT32 (*mono_signature_get_param_count)(void *sig); + +void (*mono_domain_set_config)(void *domain, char *base_dir, char *config_file_name); +void *(*mono_array_new)(void *domain, void *eclass, uintptr_t n); +void *(*mono_get_string_class)(); + +char *(*mono_assembly_getrootdir)(); + +// Additional funcs to bootstrap custom MONO +void (*mono_set_dirs)(const char* assembly_dir, const char* config_dir); +void (*mono_config_parse)(const char* filename); +void (*mono_set_assemblies_path)(const char* path); +void *(*mono_object_to_string)(void* obj, void** exc); +char *(*mono_string_to_utf8)(void* s); + +void *(*mono_image_open_from_data_with_name)(void *data, DWORD data_len, int need_copy, void *status, int refonly, + const char *name); + +void* (*mono_get_exception_class)(); +void* (*mono_object_get_virtual_method)(void* obj_raw, void* method); + +void* (*mono_jit_parse_options)(int argc, const char** argv); + +typedef enum { + MONO_DEBUG_FORMAT_NONE, + MONO_DEBUG_FORMAT_MONO, + /* Deprecated, the mdb debugger is not longer supported. */ + MONO_DEBUG_FORMAT_DEBUGGER +} MonoDebugFormat; + +void* (*mono_debug_init)(MonoDebugFormat format); +void* (*mono_debug_domain_create)(void* domain); + +/** +* \brief Loads Mono C API function pointers so that the above definitions can be called. +* \param mono_lib Mono.dll module. +*/ +void load_mono_functions(HMODULE mono_lib) { + // Enjoy the fact that C allows such sloppy casting + // In C++ you would have to cast to the precise function pointer type +#define GET_MONO_PROC(name) name = (void*)GetProcAddress(mono_lib, #name) + + // Find and assign all our functions that we are going to use + GET_MONO_PROC(mono_domain_assembly_open); + GET_MONO_PROC(mono_assembly_get_image); + GET_MONO_PROC(mono_runtime_invoke); + GET_MONO_PROC(mono_jit_init_version); + GET_MONO_PROC(mono_method_desc_new); + GET_MONO_PROC(mono_method_desc_search_in_class); + GET_MONO_PROC(mono_method_desc_search_in_image); + GET_MONO_PROC(mono_method_desc_free); + GET_MONO_PROC(mono_method_signature); + GET_MONO_PROC(mono_signature_get_param_count); + GET_MONO_PROC(mono_array_new); + GET_MONO_PROC(mono_get_string_class); + GET_MONO_PROC(mono_assembly_getrootdir); + GET_MONO_PROC(mono_thread_current); + GET_MONO_PROC(mono_thread_set_main); + GET_MONO_PROC(mono_domain_set_config); + GET_MONO_PROC(mono_set_dirs); + GET_MONO_PROC(mono_config_parse); + GET_MONO_PROC(mono_set_assemblies_path); + GET_MONO_PROC(mono_object_to_string); + GET_MONO_PROC(mono_string_to_utf8); + GET_MONO_PROC(mono_image_open_from_data_with_name); + GET_MONO_PROC(mono_get_exception_class); + GET_MONO_PROC(mono_object_get_virtual_method); + GET_MONO_PROC(mono_jit_parse_options); + GET_MONO_PROC(mono_debug_init); + GET_MONO_PROC(mono_debug_domain_create); + +#undef GET_MONO_PROC +} \ No newline at end of file diff --git a/common/unityhook/util.h b/common/unityhook/util.h new file mode 100644 index 0000000..9ad0a80 --- /dev/null +++ b/common/unityhook/util.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +wchar_t *widen(const char *str) { + const int reqsz = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); + wchar_t *result = malloc(reqsz * sizeof(wchar_t)); + + MultiByteToWideChar(CP_UTF8, 0, str, -1, result, reqsz); + return result; +} + +char *narrow(const wchar_t *str) { + const int reqsz = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL); + char *result = malloc(reqsz * sizeof(char)); + + WideCharToMultiByte(CP_UTF8, 0, str, -1, result, reqsz, NULL, NULL); + return result; +} + diff --git a/util/async.c b/common/util/async.c similarity index 99% rename from util/async.c rename to common/util/async.c index fd6a1cd..449550c 100644 --- a/util/async.c +++ b/common/util/async.c @@ -1,4 +1,4 @@ -/* NTSTATUS chicanery. See precompiled.h */ +/* NTSTATUS chicanery. */ #define WIN32_NO_STATUS #include #undef WIN32_NO_STATUS diff --git a/util/async.h b/common/util/async.h similarity index 100% rename from util/async.h rename to common/util/async.h diff --git a/util/crc.c b/common/util/crc.c similarity index 100% rename from util/crc.c rename to common/util/crc.c diff --git a/util/crc.h b/common/util/crc.h similarity index 100% rename from util/crc.h rename to common/util/crc.h diff --git a/util/dll-bind.c b/common/util/dll-bind.c similarity index 100% rename from util/dll-bind.c rename to common/util/dll-bind.c diff --git a/util/dll-bind.h b/common/util/dll-bind.h similarity index 100% rename from util/dll-bind.h rename to common/util/dll-bind.h diff --git a/util/dprintf.c b/common/util/dprintf.c similarity index 100% rename from util/dprintf.c rename to common/util/dprintf.c diff --git a/util/dprintf.h b/common/util/dprintf.h similarity index 100% rename from util/dprintf.h rename to common/util/dprintf.h diff --git a/util/dump.c b/common/util/dump.c similarity index 100% rename from util/dump.c rename to common/util/dump.c diff --git a/util/dump.h b/common/util/dump.h similarity index 100% rename from util/dump.h rename to common/util/dump.h diff --git a/common/util/env.c b/common/util/env.c new file mode 100644 index 0000000..e88ee8a --- /dev/null +++ b/common/util/env.c @@ -0,0 +1,10 @@ +#include "env.h" + +const wchar_t* get_config_path() { + static wchar_t path[MAX_PATH]; + if (!GetEnvironmentVariableW(L"SEGATOOLS_CONFIG_PATH", path, MAX_PATH)) { + return L".\\segatools.ini"; + } + + return path; +} diff --git a/common/util/env.h b/common/util/env.h new file mode 100644 index 0000000..c62194a --- /dev/null +++ b/common/util/env.h @@ -0,0 +1,4 @@ +#pragma once +#include + +const wchar_t* get_config_path(); diff --git a/common/util/get_function_ordinal.c b/common/util/get_function_ordinal.c new file mode 100644 index 0000000..470bf7f --- /dev/null +++ b/common/util/get_function_ordinal.c @@ -0,0 +1,34 @@ +#include "get_function_ordinal.h" + +DWORD get_function_ordinal(const char* dllName, const char* functionName) { + HMODULE hModule = LoadLibraryA(dllName); + if (!hModule) { + dprintf("Failed to load DLL: %s\n", dllName); + return 0; + } + + ULONG size; + PIMAGE_EXPORT_DIRECTORY exportDir = (PIMAGE_EXPORT_DIRECTORY)ImageDirectoryEntryToData( + hModule, TRUE, IMAGE_DIRECTORY_ENTRY_EXPORT, &size); + if (!exportDir) { + dprintf("Failed to get export table\n"); + FreeLibrary(hModule); + return 0; + } + + DWORD* functionNames = (DWORD*)((BYTE*)hModule + exportDir->AddressOfNames); + WORD* ordinals = (WORD*)((BYTE*)hModule + exportDir->AddressOfNameOrdinals); + + for (DWORD i = 0; i < exportDir->NumberOfNames; ++i) { + char* name = (char*)((BYTE*)hModule + functionNames[i]); + if (strcmp(name, functionName) == 0) { + DWORD ordinal = ordinals[i] + exportDir->Base; + FreeLibrary(hModule); + return ordinal; + } + } + + dprintf("Function not found: %s\n", functionName); + FreeLibrary(hModule); + return 0; +} \ No newline at end of file diff --git a/common/util/get_function_ordinal.h b/common/util/get_function_ordinal.h new file mode 100644 index 0000000..56779c4 --- /dev/null +++ b/common/util/get_function_ordinal.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include +#include "dprintf.h" + +DWORD get_function_ordinal(const char* dllName, const char* functionName); \ No newline at end of file diff --git a/util/lib.c b/common/util/lib.c similarity index 96% rename from util/lib.c rename to common/util/lib.c index bd7a5eb..42f5c66 100644 --- a/util/lib.c +++ b/common/util/lib.c @@ -1,6 +1,7 @@ #include #include +#include wchar_t *module_file_name(HMODULE module) { diff --git a/util/lib.h b/common/util/lib.h similarity index 100% rename from util/lib.h rename to common/util/lib.h diff --git a/util/meson.build b/common/util/meson.build similarity index 74% rename from util/meson.build rename to common/util/meson.build index 575d123..58782d3 100644 --- a/util/meson.build +++ b/common/util/meson.build @@ -2,9 +2,9 @@ util_lib = static_library( 'util', include_directories : inc, implicit_include_directories : false, - c_pch : '../precompiled.h', dependencies : [ capnhook.get_variable('hook_dep'), + imagehlp_lib, ], sources : [ 'async.c', @@ -17,9 +17,15 @@ util_lib = static_library( 'dprintf.h', 'dump.c', 'dump.h', + 'get_function_ordinal.c', + 'get_function_ordinal.h', 'lib.c', 'lib.h', + 'slurp.c', + 'slurp.h', 'str.c', 'str.h', + 'env.c', + 'env.h', ], ) diff --git a/common/util/slurp.c b/common/util/slurp.c new file mode 100644 index 0000000..1af73a4 --- /dev/null +++ b/common/util/slurp.c @@ -0,0 +1,67 @@ +#include +#include +#include +/* + * 'slurp' reads the file identified by 'path' into a character buffer + * pointed at by 'buf', optionally adding a terminating NUL if + * 'add_nul' is true. On success, the size of the file is returned; on + * failure, -1 is returned and ERRNO is set by the underlying system + * or library call that failed. + * + * WARNING: 'slurp' malloc()s memory to '*buf' which must be freed by + * the caller. + */ +long wslurp(const wchar_t* path, char **buf, bool add_nul) +{ + FILE *fp; + size_t fsz; + long off_end; + int rc; + + /* Open the file */ + fp = _wfopen(path, L"rb"); + if( NULL == fp ) { + return -1L; + } + + /* Seek to the end of the file */ + rc = fseek(fp, 0L, SEEK_END); + if( 0 != rc ) { + return -1L; + } + + /* Byte offset to the end of the file (size) */ + if( 0 > (off_end = ftell(fp)) ) { + return -1L; + } + fsz = (size_t)off_end; + + /* Allocate a buffer to hold the whole file */ + *buf = malloc( fsz+(int)add_nul ); + if( NULL == *buf ) { + return -1L; + } + + /* Rewind file pointer to start of file */ + rewind(fp); + + /* Slurp file into buffer */ + if( fsz != fread(*buf, 1, fsz, fp) ) { + free(*buf); + return -1L; + } + + /* Close the file */ + if( EOF == fclose(fp) ) { + free(*buf); + return -1L; + } + + if( add_nul ) { + /* Make sure the buffer is NUL-terminated, just in case */ + buf[fsz] = '\0'; + } + + /* Return the file size */ + return (long)fsz; +} \ No newline at end of file diff --git a/common/util/slurp.h b/common/util/slurp.h new file mode 100644 index 0000000..407a2d8 --- /dev/null +++ b/common/util/slurp.h @@ -0,0 +1,12 @@ +#include +/* + * 'slurp' reads the file identified by 'path' into a character buffer + * pointed at by 'buf', optionally adding a terminating NUL if + * 'add_nul' is true. On success, the size of the file is returned; on + * failure, -1 is returned and ERRNO is set by the underlying system + * or library call that failed. + * + * WARNING: 'slurp' malloc()s memory to '*buf' which must be freed by + * the caller. + */ +long wslurp(const wchar_t* path, char **buf, bool add_nul); \ No newline at end of file diff --git a/util/str.c b/common/util/str.c similarity index 100% rename from util/str.c rename to common/util/str.c diff --git a/util/str.h b/common/util/str.h similarity index 100% rename from util/str.h rename to common/util/str.h diff --git a/cxbhook/network.c b/cxbhook/network.c deleted file mode 100644 index e9a832a..0000000 --- a/cxbhook/network.c +++ /dev/null @@ -1,13 +0,0 @@ -#include -#include -#include - -#include "cxbhook/network.h" - -#include "util/dprintf.h" - -HRESULT network_hook_init(struct network_config *cfg) -{ - dprintf("Network: Init\n"); - return S_OK; -} \ No newline at end of file diff --git a/cxbhook/network.h b/cxbhook/network.h deleted file mode 100644 index a2ae7ce..0000000 --- a/cxbhook/network.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include -#include -#include - -struct network_config { - bool enable; - bool disable_ssl; - char title_server[PATH_MAX]; -}; - -HRESULT network_hook_init(struct network_config *cfg); \ No newline at end of file diff --git a/dist/carol/start.bat b/dist/carol/launch.bat similarity index 80% rename from dist/carol/start.bat rename to dist/carol/launch.bat index 0fab950..4e75aa0 100644 --- a/dist/carol/start.bat +++ b/dist/carol/launch.bat @@ -2,10 +2,10 @@ 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. diff --git a/dist/carol/segatools.ini b/dist/carol/segatools.ini index d43cfe0..b760c6f 100644 --- a/dist/carol/segatools.ini +++ b/dist/carol/segatools.ini @@ -1,23 +1,56 @@ +; ----------------------------------------------------------------------------- +; Path settings +; ----------------------------------------------------------------------------- + [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) +; Insert the path to the game Option directory here (contains MOV1, PAR0, +; PAR1, RES0 and RES1 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= +; ----------------------------------------------------------------------------- +; Device settings +; ----------------------------------------------------------------------------- + +[aime] +; Enable Aime card reader assembly emulation. Disable to use a real SEGA Aime +; reader. +enable=1 +aimePath=DEVICE\aime.txt + +; ----------------------------------------------------------------------------- +; Network settings +; ----------------------------------------------------------------------------- + [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. +; 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 +; The final octet of the local host's IP address on the virtualized subnet (so, +; if the keychip subnet is `192.168.32.0` and this value is set to `11`, then the +; local host's virtualized LAN IP is `192.168.32.11`). +addrSuffix=11 + +; ----------------------------------------------------------------------------- +; Board settings +; ----------------------------------------------------------------------------- [gpio] +; Emulated Nu DIP switch for Distribution Server setting. +; +; If multiple machines are present on the same LAN then set this to 1 on +; exactly one machine and set this to 0 on all others. dipsw1=1 [keychip] @@ -26,23 +59,47 @@ dipsw1=1 ; that subnet must start with 192.168. subnet=192.168.126.0 +; ----------------------------------------------------------------------------- +; Misc. hooks settings +; ----------------------------------------------------------------------------- + [gfx] +; Enables the graphics hook. +enable=1 ; 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 +; Enable DPI awareness for the game process, preventing Windows from stretching the game window if a DPI scaling higher than 100% is used +dpiAware=1 + +; ----------------------------------------------------------------------------- +; Custom IO settings +; ----------------------------------------------------------------------------- [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= +; ----------------------------------------------------------------------------- +; 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. + [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 +; Test button virtual-key code. Default is the F1 key. +test=0x70 +; Service button virtual-key code. Default is the F2 key. +service=0x71 +; Keyboard button to increment coin counter. Default is the F3 key. +coin=0x72 diff --git a/dist/chuni/start.bat b/dist/chuni/launch.bat similarity index 96% rename from dist/chuni/start.bat rename to dist/chuni/launch.bat index 0c942e9..a1c54ef 100644 --- a/dist/chuni/start.bat +++ b/dist/chuni/launch.bat @@ -3,7 +3,9 @@ pushd %~dp0 start /min inject -d -k chunihook.dll aimeReaderHost.exe -p 12 + inject -d -k chunihook.dll chuniApp.exe + taskkill /f /im aimeReaderHost.exe > nul 2>&1 echo. diff --git a/dist/chuni/segatools.ini b/dist/chuni/segatools.ini index 7385546..3f32f5b 100644 --- a/dist/chuni/segatools.ini +++ b/dist/chuni/segatools.ini @@ -1,3 +1,7 @@ +; ----------------------------------------------------------------------------- +; Path settings +; ----------------------------------------------------------------------------- + [vfs] ; Insert the path to the game AMFS directory here (contains ICF1 and ICF2) amfs= @@ -8,6 +12,20 @@ option= ; NOTE: This has nothing to do with Windows %APPDATA%. appdata= +; ----------------------------------------------------------------------------- +; Device settings +; ----------------------------------------------------------------------------- + +[aime] +; Enable Aime card reader assembly emulation. Disable to use a real SEGA Aime +; reader. +enable=1 +aimePath=DEVICE\aime.txt + +; ----------------------------------------------------------------------------- +; Network settings +; ----------------------------------------------------------------------------- + [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. @@ -18,6 +36,14 @@ default=127.0.0.1 ; Chunithm is extremely picky about its LAN environment, so leaving this ; setting enabled is strongly recommended. enable=1 +; The final octet of the local host's IP address on the virtualized subnet (so, +; if the keychip subnet is `192.168.32.0` and this value is set to `11`, then the +; local host's virtualized LAN IP is `192.168.32.11`). +addrSuffix=11 + +; ----------------------------------------------------------------------------- +; Board settings +; ----------------------------------------------------------------------------- [keychip] ; The /24 LAN subnet that the emulated keychip will tell the game to expect. @@ -25,13 +51,72 @@ enable=1 ; that subnet must start with 192.168. subnet=192.168.139.0 +; ----------------------------------------------------------------------------- +; Misc. hooks settings +; ----------------------------------------------------------------------------- + [gfx] +; Enables the graphics hook. +enable=1 ; 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 +; Enable DPI awareness for the game process, preventing Windows from stretching the game window if a DPI scaling higher than 100% is used +dpiAware=1 + +; ----------------------------------------------------------------------------- +; LED settings +; ----------------------------------------------------------------------------- + +[led15093] +; Enable emulation of the 15093-06 controlled lights, which handle the air tower +; RGBs and the rear LED panel (billboard) on the cabinet. +enable=1 + +[led] +; Output billboard LED strip data to a named pipe called "\\.\pipe\chuni_ledstrip" +cabLedOutputPipe=1 +; Output billboard LED strip data to serial +cabLedOutputSerial=0 + +; Output slider LED data to the named pipe +controllerLedOutputPipe=1 +; Output slider LED data to the serial port +controllerLedOutputSerial=0 +; Use the OpeNITHM protocol for serial LED output +controllerLedOutputOpeNITHM=0 + +; Serial port to send data to if using serial output. Default is COM5. +;serialPort=COM5 +; Baud rate for serial data (set to 115200 if using OpeNITHM) +;serialBaud=921600 + +; Data output a sequence of bytes, with JVS-like framing. +; Each "packet" starts with 0xE0 as a sync. To avoid E0 appearing elsewhere, +; 0xD0 is used as an escape character -- if you receive D0 in the output, ignore +; it and use the next sent byte plus one instead. +; +; After the sync is one byte for the board number that was updated, followed by +; the red, green and blue values for each LED. +; +; Board 0 has 53 LEDs: +; [0]-[49]: snakes through left half of billboard (first column starts at top) +; [50]-[52]: left side partition LEDs +; +; Board 1 has 63 LEDs: +; [0]-[59]: right half of billboard (first column starts at bottom) +; [60]-[62]: right side partition LEDs +; +; Board 2 is the slider and has 31 LEDs: +; [0]-[31]: slider LEDs right to left BRG, alternating between keys and dividers + + +; ----------------------------------------------------------------------------- +; Custom IO settings +; ----------------------------------------------------------------------------- [aimeio] ; To use a custom card reader IO DLL enter its path here. @@ -56,12 +141,26 @@ path= ; world. An improved solution will be provided later. [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 +; Test button virtual-key code. Default is the F1 key. +test=0x70 +; Service button virtual-key code. Default is the F2 key. +service=0x71 +; Keyboard button to increment coin counter. Default is the F3 key. +coin=0x72 +; Set to 0 for enable separate ir control. Deafult is space key. +ir=0x20 + +[ir] +; Uncomment and complete the following sequence of settings to configure a +; custom ir-cappable controller if you have one. +;ir6=0x53 +; ... etc ... +;ir1=0x53 + +[slider] +; Enable slider emulation. If you have real AC slider, set this to 0. +; Slider serial port must be COM1. +;enable=1 ; Key bindings for each of the 32 touch cells. The default key map, depicted ; in left-to-right order, is as follows: @@ -73,8 +172,8 @@ coin=0x33 ; ; Uncomment and complete the following sequence of settings to configure a ; custom high-precision touch strip controller if you have one. -[slider] -;cell32=0x53 -;cell31=0x53 -;cell30=0x53 +;cell1=0x53 +;cell2=0x53 ; ... etc ... +;cell31=0x53 +;cell32=0x53 diff --git a/dist/chusan/config_hook.json b/dist/chusan/config_hook.json new file mode 100644 index 0000000..626eb7c --- /dev/null +++ b/dist/chusan/config_hook.json @@ -0,0 +1,6 @@ +{ + "allnet_auth": + { + "type": "1.0" + } +} diff --git a/dist/chusan/launch.bat b/dist/chusan/launch.bat new file mode 100644 index 0000000..bf3f195 --- /dev/null +++ b/dist/chusan/launch.bat @@ -0,0 +1,12 @@ +@echo off + +pushd %~dp0 + +start "AM Daemon" /min inject_x64 -d -k chusanhook_x64.dll amdaemon.exe -c config_common.json config_server.json config_client.json config_cvt.json config_sp.json config_hook.json +inject_x86 -d -k chusanhook_x86.dll chusanApp.exe + +taskkill /f /im amdaemon.exe > nul 2>&1 + +echo. +echo Game processes have terminated +pause \ No newline at end of file diff --git a/dist/chusan/segatools.ini b/dist/chusan/segatools.ini new file mode 100644 index 0000000..1fb1371 --- /dev/null +++ b/dist/chusan/segatools.ini @@ -0,0 +1,210 @@ +; ----------------------------------------------------------------------------- +; Path settings +; ----------------------------------------------------------------------------- + +[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= + +; ----------------------------------------------------------------------------- +; Device settings +; ----------------------------------------------------------------------------- + +[aime] +; Enable Aime card reader assembly emulation. Disable to use a real SEGA Aime +; reader. +enable=1 +aimePath=DEVICE\aime.txt +; Enable high baud rate. +;highBaud=1 + +[vfd] +; Enable VFD emulation. Disable to use a real VFD +; GP1232A02A FUTABA assembly. +enable=1 + +; ----------------------------------------------------------------------------- +; Network settings +; ----------------------------------------------------------------------------- + +[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 + +; The final octet of the local host's IP address on the virtualized subnet (so, +; if the keychip subnet is `192.168.32.0` and this value is set to `11`, then the +; local host's virtualized LAN IP is `192.168.32.11`). +addrSuffix=11 + +; ----------------------------------------------------------------------------- +; Board settings +; ----------------------------------------------------------------------------- + +[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.139.0 + +[system] +; Enable ALLS system settings. +enable=1 + +; Enable freeplay mode. This will disable the coin slot and set the game to +; freeplay. Keep in mind that some game modes (e.g. Freedom/Time Modes) will not +; allow you to start a game in freeplay mode. +freeplay=0 + +; LAN Install: If multiple machines are present on the same LAN then set +; this to 1 on exactly one machine and set this to 0 on all others. +dipsw1=1 +; Monitor type: 0 = 120FPS, 1 = 60FPS +dipsw2=1 +; Cab type: 0 = SP, 1 = CVT. SP will enable VFD and eMoney. This setting will switch +; the LED 837-15093-06 COM port and the AiMe reder hardware generation as well. +dipsw3=1 + +; ----------------------------------------------------------------------------- +; Misc. hooks settings +; ----------------------------------------------------------------------------- + +[gfx] +; Enables the graphics hook. +enable=1 +; Force the game to run windowed. +windowed=1 +; Add a frame to the game window if running windowed. +framed=0 +; Select the monitor to run the game on. (Fullscreen only, 0 =primary screen) +monitor=0 +; Enable DPI awareness for the game process, preventing Windows from stretching the game window if a DPI scaling higher than 100% is used +dpiAware=1 + +; ----------------------------------------------------------------------------- +; LED settings +; ----------------------------------------------------------------------------- + +[led15093] +; Enable emulation of the 15093-06 controlled lights, which handle the air tower +; RGBs and the rear LED panel (billboard) on the cabinet. +enable=1 + +[led] +; Output billboard LED strip data to a named pipe called "\\.\pipe\chuni_led" +cabLedOutputPipe=1 +; Output billboard LED strip data to serial +cabLedOutputSerial=0 + +; Output slider LED data to the named pipe +controllerLedOutputPipe=1 +; Output slider LED data to the serial port +controllerLedOutputSerial=0 +; Use the OpeNITHM protocol for serial LED output +controllerLedOutputOpeNITHM=0 + +; Serial port to send data to if using serial output. Default is COM5. +;serialPort=COM5 +; Baud rate for serial data (set to 115200 if using OpeNITHM) +;serialBaud=921600 + +; Data output a sequence of bytes, with JVS-like framing. +; Each "packet" starts with 0xE0 as a sync. To avoid E0 appearing elsewhere, +; 0xD0 is used as an escape character -- if you receive D0 in the output, ignore +; it and use the next sent byte plus one instead. +; +; After the sync is one byte for the board number that was updated, followed by +; the red, green and blue values for each LED. +; +; Board 0 has 53 LEDs: +; [0]-[49]: snakes through left half of billboard (first column starts at top) +; [50]-[52]: left side partition LEDs +; +; Board 1 has 63 LEDs: +; [0]-[59]: right half of billboard (first column starts at bottom) +; [60]-[62]: right side partition LEDs +; +; Board 2 is the slider and has 31 LEDs: +; [0]-[31]: slider LEDs right to left BRG, alternating between keys and dividers + + +; ----------------------------------------------------------------------------- +; Custom IO settings +; ----------------------------------------------------------------------------- + +[aimeio] +; To use a custom card reader IO DLL (x64) enter its path here. +; Leave empty if you want to use Segatools built-in keyboard input. +path= + +[chuniio] +; Uncomment this if you have custom chuniio implementation comprised of a single 32bit DLL. +; (will use chu2to3 engine internally) +;path= + +; Uncomment both of these if you have custom chuniio implementation comprised of two DLLs. +; x86 chuniio to path32, x64 to path64. Both are necessary. +;path32= +;path64= + +; ----------------------------------------------------------------------------- +; 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. + +[io3] +; Test button virtual-key code. Default is the F1 key. +test=0x70 +; Service button virtual-key code. Default is the F2 key. +service=0x71 +; Keyboard button to increment coin counter. Default is the F3 key. +coin=0x72 +; Set to 0 for enable separate ir control. Deafult is space key. +ir=0x20 + +[ir] +; Uncomment and complete the following sequence of settings to configure a +; custom ir-cappable controller if you have one. +;ir6=0x53 +; ... etc ... +;ir1=0x53 + +[slider] +; Enable slider emulation. If you have real AC slider, set this to 0. +; Slider serial port must be COM1. +;enable=1 + +; Key bindings for each of the 32 touch cells. The default key map, depicted +; in left-to-right order, is as follows: +; +; SSSSDDDDFFFFGGGGHHHHJJJJKKKKLLLL +; +; Touch cells are numbered FROM RIGHT TO LEFT! starting from 1. This is in +; order to match the numbering used in the operator menu and service manual. +; +; Uncomment and complete the following sequence of settings to configure a +; custom high-precision touch strip controller if you have one. +;cell1=0x53 +;cell2=0x53 +; ... etc ... +;cell31=0x53 +;cell32=0x53 diff --git a/dist/cm/config_hook.json b/dist/cm/config_hook.json new file mode 100644 index 0000000..5723db7 --- /dev/null +++ b/dist/cm/config_hook.json @@ -0,0 +1,9 @@ +{ + "credit" : + { + "coin_selector_AS6DB" : + { + "enable" : false + } + } +} diff --git a/dist/cm/launch.bat b/dist/cm/launch.bat new file mode 100644 index 0000000..570fb9c --- /dev/null +++ b/dist/cm/launch.bat @@ -0,0 +1,12 @@ +@echo off + +pushd %~dp0 + +start "AM Daemon" /min inject -d -k cmhook.dll amdaemon.exe -c config_common.json config_server.json config_client.json config_hook.json +inject -d -k cmhook.dll CardMaker.exe -screen-fullscreen 0 -popupwindow -screen-width 1080 -screen-height 1920 + +taskkill /f /im amdaemon.exe > nul 2>&1 + +echo. +echo Game processes have terminated +pause \ No newline at end of file diff --git a/dist/cm/segatools.ini b/dist/cm/segatools.ini new file mode 100644 index 0000000..cd42cd6 --- /dev/null +++ b/dist/cm/segatools.ini @@ -0,0 +1,111 @@ +; ----------------------------------------------------------------------------- +; Path settings +; ----------------------------------------------------------------------------- + +[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= + +; ----------------------------------------------------------------------------- +; Device settings +; ----------------------------------------------------------------------------- + +[aime] +; Enable Aime card reader assembly emulation. Disable to use a real SEGA Aime +; reader. +enable=1 +aimePath=DEVICE\aime.txt + +[vfd] +; Enable VFD emulation. Disable to use a real VFD +; GP1232A02A FUTABA assembly. +enable=1 + +; ----------------------------------------------------------------------------- +; Network settings +; ----------------------------------------------------------------------------- + +[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. +; SEGA games are somewhat picky about their LAN environment, so leaving this +; setting enabled is recommended. +enable=1 + +; ----------------------------------------------------------------------------- +; Board settings +; ----------------------------------------------------------------------------- + +[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.165.0 + +[system] +; Enable ALLS system settings. +enable=1 + +; LAN Install: If multiple machines are present on the same LAN then set +; this to 0 on exactly one machine and set this to 1 on all others. +dipsw1=0 + +; ----------------------------------------------------------------------------- +; Misc. hooks settings +; ----------------------------------------------------------------------------- + +[touch] +; Enable/Disable WinTouch emulation +enable=0 + +[unity] +; Path to a .NET DLL that should run before the game. Useful for loading +; modding frameworks such as BepInEx. +targetAssembly= + +[printer] +; Sinfonia CHC-C330 printer emulation setting. +enable=1 +; Change the printer serial number here. +serial_no="5A-A123" +; Insert the path to the image output directory here. +printerOutPath="DEVICE\print" + +; ----------------------------------------------------------------------------- +; Custom IO settings +; ----------------------------------------------------------------------------- + +[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= + +; ----------------------------------------------------------------------------- +; Input settings +; ----------------------------------------------------------------------------- + +; Keyboard bindings are 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 F1 key. +test=0x70 +; Service button virtual-key code. Default is the F2 key. +service=0x71 +; Keyboard button to increment coin counter. Default is the F3 key. +coin=0x72 diff --git a/dist/cxb/start.bat b/dist/cxb/launch.bat similarity index 100% rename from dist/cxb/start.bat rename to dist/cxb/launch.bat diff --git a/dist/cxb/segatools.ini b/dist/cxb/segatools.ini index e0491ff..436ef01 100644 --- a/dist/cxb/segatools.ini +++ b/dist/cxb/segatools.ini @@ -1,3 +1,7 @@ +; ----------------------------------------------------------------------------- +; Path settings +; ----------------------------------------------------------------------------- + [vfs] ; Make sure theses are full paths and not relative or you will have a bad time ; Insert the path to the game AMFS directory here (contains ICF1 and ICF2) @@ -9,40 +13,54 @@ option= ; NOTE: This has nothing to do with Windows %APPDATA%. appdata= +; ----------------------------------------------------------------------------- +; Device settings +; ----------------------------------------------------------------------------- + +[aime] +; Aime reader emulation +enable=1 +; CXB is stupid, so we have to make the paths go back two directories. This +; will load the file from "resource\DEVICE\aime.txt". +aimePath=../DEVICE/aime.txt + +[led] +; Emulation for the LED board. Currently it's just dummy responses, +; but if somebody wants to make their keyboard or whatever light +; up with the game they can +enable=1 + +; ----------------------------------------------------------------------------- +; Network settings +; ----------------------------------------------------------------------------- + [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 +; Set the title server hostname or IP address here, as the title server +; is hardcoded in the game. +title=https://127.0.0.1:9002 + [netenv] ; Simulate an ideal LAN environment. This may interfere with head-to-head play. ; Crossbeats is extremely picky about its LAN environment, so leaving this ; setting enabled is strongly recommended. enable=1 +; ----------------------------------------------------------------------------- +; Board settings +; ----------------------------------------------------------------------------- + [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 +subnet=192.168.150.0 billingCa=../DEVICE/ca.crt billingPub=../DEVICE/billing.pub -billingType=2 - -[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 - -[aime] -; Aime reader emulation -; CXB is stupid, so we have to make the paths go back one -enable=1 -aimePath=../DEVICE/aime.txt -felicaPath=../DEVICE/felica.txt +billingType=0 [eeprom] ; See above @@ -52,21 +70,58 @@ path=../DEVICE/eeprom.bin ; See above path=../DEVICE/sram.bin -[led] -; Emulation for the LED board. Currently it's just dummy responses, -; but if somebody wants to make their keyboard or whatever light -; up with the game they can +; ----------------------------------------------------------------------------- +; Misc. hooks settings +; ----------------------------------------------------------------------------- + +[gfx] +; Enables the graphics hook. enable=1 +; 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 +; Enable DPI awareness for the game process, preventing Windows from stretching the game window if a DPI scaling higher than 100% is used +dpiAware=1 + +; ----------------------------------------------------------------------------- +; Custom IO settings +; ----------------------------------------------------------------------------- + +[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= + +[cxbio] +; To use a custom crossbeats REV. DLL enter its path here. +; Leave empty if you want to use Segatools built-in keyboard input. +path= + +; ----------------------------------------------------------------------------- +; 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. [revio] ; Enable emulation of the rev IO board -enabe=1 -; 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 +enable=1 +; Test button virtual-key code. Default is the F1 key. +test=0x70 +; Service button virtual-key code. Default is the F2 key. +service=0x71 +; Keyboard button to increment coin counter. Default is the F3 key. +coin=0x72 + ; Menu up key. Default is up arrow. up=0x26 ; Menu down key. Default is down arrow. diff --git a/dist/diva/start.bat b/dist/diva/launch.bat similarity index 100% rename from dist/diva/start.bat rename to dist/diva/launch.bat diff --git a/dist/diva/segatools.ini b/dist/diva/segatools.ini index e2608c0..744a55f 100644 --- a/dist/diva/segatools.ini +++ b/dist/diva/segatools.ini @@ -1,13 +1,31 @@ +; ----------------------------------------------------------------------------- +; Path settings +; ----------------------------------------------------------------------------- + [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) +; Insert the path to the game Option (mdata) directory here (contains Mxxx 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= +; ----------------------------------------------------------------------------- +; Device settings +; ----------------------------------------------------------------------------- + +[aime] +; Enable Aime card reader assembly emulation. Disable to use a real SEGA Aime +; reader. +enable=1 +aimePath=DEVICE\aime.txt + +; ----------------------------------------------------------------------------- +; Network settings +; ----------------------------------------------------------------------------- + [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. @@ -18,15 +36,76 @@ default=127.0.0.1 ; Chunithm is extremely picky about its LAN environment, so leaving this ; setting enabled is strongly recommended. enable=1 +; The final octet of the local host's IP address on the virtualized subnet (so, +; if the keychip subnet is `192.168.32.0` and this value is set to `11`, then the +; local host's virtualized LAN IP is `192.168.32.11`). +addrSuffix=11 + +; ----------------------------------------------------------------------------- +; Board settings +; ----------------------------------------------------------------------------- [gpio] +; Emulated Nu DIP switch for Distribution Server setting. +; +; If multiple machines are present on the same LAN then set this to 1 on +; exactly one machine and set this to 0 on all others. 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 +subnet=192.168.78.0 + +; ----------------------------------------------------------------------------- +; Misc. hooks settings +; ----------------------------------------------------------------------------- + +[gfx] +; Enables the graphics hook. +enable=1 +; Force the game to run windowed. +windowed=1 +; Add a frame to the game window if running windowed. +framed=0 +; Enable DPI awareness for the game process, preventing Windows from stretching the game window if a DPI scaling higher than 100% is used +dpiAware=1 + + +; ----------------------------------------------------------------------------- +; Custom IO settings +; ----------------------------------------------------------------------------- + +[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= + +[divaio] +; To use a custom Project DIVA Arcade IO DLL enter its path here. +; Leave empty if you want to use Segatools built-in gamepad/wheel input. +path= + +; ----------------------------------------------------------------------------- +; 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. + +[io3] +; Test button virtual-key code. Default is the F1 key. +test=0x70 +; Service button virtual-key code. Default is the F2 key. +service=0x71 +; Keyboard button to increment coin counter. Default is the F3 key. +coin=0x72 [slider] cell1=0x51 diff --git a/dist/fgo/launch.bat b/dist/fgo/launch.bat new file mode 100644 index 0000000..1b76197 --- /dev/null +++ b/dist/fgo/launch.bat @@ -0,0 +1,11 @@ +@echo off + +pushd %~dp0 + +inject -d -k fgohook.dll ago.exe + +taskkill /f /im amdaemon.exe > nul 2>&1 + +echo. +echo Game processes have terminated +pause \ No newline at end of file diff --git a/dist/fgo/segatools.ini b/dist/fgo/segatools.ini new file mode 100644 index 0000000..4b7ac9f --- /dev/null +++ b/dist/fgo/segatools.ini @@ -0,0 +1,197 @@ +; ----------------------------------------------------------------------------- +; Path settings +; ----------------------------------------------------------------------------- + +[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= + +; ----------------------------------------------------------------------------- +; Device settings +; ----------------------------------------------------------------------------- + +[aime] +; Enable Aime card reader assembly emulation. Disable to use a real SEGA Aime +; reader. +enable=1 +aimePath=DEVICE\aime.txt + +[vfd] +; Enable VFD emulation. Disable to use a real VFD +; GP1232A02A FUTABA assembly. +enable=1 + +[deckReader] +; 837-15345 RFID deck reader emulation setting. +enable=1 + +[ftdi] +; FTDI serial to usb adapter emulation for CABINET LED. +enable=1 +; COM port number where the LED board is connected to. +portNo=17 + +[led15093] +; 837-15093-06 LED board emulation setting. +enable=1 +; COM port number for the LED board. Has to be the same as the FTDI port. +portNo1=17 + +; ----------------------------------------------------------------------------- +; Network settings +; ----------------------------------------------------------------------------- + +[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. +; SEGA games are somewhat picky about their LAN environment, so leaving this +; setting enabled is recommended. +enable=1 +; The final octet of the local host's IP address on the virtualized subnet (so, +; if the keychip subnet is `192.168.32.0` and this value is set to `11`, then the +; local host's virtualized LAN IP is `192.168.32.11`). +addrSuffix=11 + +; ----------------------------------------------------------------------------- +; Board settings +; ----------------------------------------------------------------------------- + +[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.167.0 + +[system] +; Enable ALLS system settings. +enable=1 + +; Enable freeplay mode. This will disable the coin slot and set the game to +; freeplay. Keep in mind that some game modes (e.g. Freedom/Time Modes) will not +; allow you to start a game in freeplay mode. +freeplay=0 + +; ----------------------------------------------------------------------------- +; Misc. hook settings +; ----------------------------------------------------------------------------- + +[gfx] +; Enables the graphics hook. +enable=1 +; Force the game to run windowed. +windowed=0 +; Add a frame to the game window if running windowed. +framed=0 +; Enable DPI awareness for the game process, preventing Windows from stretching the game window if a DPI scaling higher than 100% is used +dpiAware=1 + +[touch] +; WinTouch emulation setting. +enable=1 +remap=1 +cursor=1 + +[printer] +; Sinfonia CHC-C330 printer emulation setting. +enable=1 +; Change the printer serial number here. +serial_no="5A-A123" +; Insert the path to the image output directory here. +printerOutPath="DEVICE\print" +; Rotate all printed images by 180 degrees. +rotate180=1 + +; ----------------------------------------------------------------------------- +; Custom IO settings +; ----------------------------------------------------------------------------- + +[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= + +[fgoio] +; To use a custom Fate/Grand Order Arcade IO DLL enter its path here. +; Leave empty if you want to use Segatools built-in gamepad input. +path= + +; ----------------------------------------------------------------------------- +; 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 F1 key. +test=0x70 +; Service button virtual-key code. Default is the F2 key. +service=0x71 +; Keyboard button to increment coin counter. Default is the F3 key. +coin=0x72 + +; .·:'''''''''''''''''''''''''''''''''''''''''''''':·. +; : : ______ / \ [] : : +; : : | | _____ \ / Coin : : +; : : |______| { (0) } /--\ Attack. : : +; : : DECK \ / / \ : : +; : : | | > < : : +; : : | | \ / ___ : : +; : : | | \--/ | | : : +; : : JOY Noble. | | : : +; : : |___| : : +; : : AIME. : : +; '·:..............................................:·' +; + +; Select the input mode. "xinput" for controller, "keyboard" for keyboard. +mode=xinput + +[xinput] +; XInput bindings +; +; Left Stick Joystick +; Left Stick Click Reset Camera +; Left Trigger Dash +; Left Shoulder Switch Target +; A/B Attack +; X/Y Noble Phantasm + +; Configure deadzones for the left thumbsticks. +; The default value for the left stick is 7849, max value is 32767. +stickDeadzone=7849 + +[keyboard] +; Keyboard bindings: + +; Stick controls (default: WASD) +up=0x57 +left=0x41 +down=0x53 +right=0x44 + +; Attack (default: Space) +attack=0x20 +; Dash (default: LSHIFT) +dash=0xa0 +; Change Target (default: J) +target=0x4A +; Re-center camera (default: K) +camera=0x4B +; Noble Phantasm (default: L) +np=0x4C diff --git a/dist/idac/config_hook.json b/dist/idac/config_hook.json new file mode 100644 index 0000000..3758b3d --- /dev/null +++ b/dist/idac/config_hook.json @@ -0,0 +1,9 @@ +{ + "network" : + { + "property" : + { + "dhcp" : true + } + } +} diff --git a/dist/idac/launch.bat b/dist/idac/launch.bat new file mode 100644 index 0000000..eb1b103 --- /dev/null +++ b/dist/idac/launch.bat @@ -0,0 +1,40 @@ +@echo off + +pushd %~dp0 + +set AMDAEMON_CFG=config_common.json ^ +config_ex.json ^ +config_jp.json ^ +config_laninstall_client_ex.json ^ +config_laninstall_client_jp.json ^ +config_laninstall_server_ex.json ^ +config_laninstall_server_jp.json ^ +config_aime_high_ex.json ^ +config_aime_high_jp.json ^ +config_aime_normal_ex.json ^ +config_aime_normal_jp.json ^ +config_seat_1_ex.json ^ +config_seat_1_jp.json ^ +config_seat_2_ex.json ^ +config_seat_2_jp.json ^ +config_seat_3_ex.json ^ +config_seat_3_jp.json ^ +config_seat_4_ex.json ^ +config_seat_4_jp.json ^ +config_seat_single_ex.json ^ +config_seat_single_jp.json ^ +config_hook.json + +start "AM Daemon" /min inject -d -k idachook.dll amdaemon.exe -c %AMDAEMON_CFG% + +rem JP +rem inject -d -k idachook.dll ..\WindowsNoEditor\GameProject\Binaries\Win64\GameProject-Win64-Shipping.exe -culture=ja launch=Cabinet ABSLOG="..\..\..\..\..\Userdata\GameProject.log" -Master -UserDir="..\..\..\Userdata" -NotInstalled -UNATTENDED + +rem EXP +inject -d -k idachook.dll ..\WindowsNoEditor\GameProject\Binaries\Win64\GameProject-Win64-Shipping.exe -culture=en launch=Cabinet ABSLOG="..\..\..\..\..\Userdata\GameProject.log" -Master -UserDir="..\..\..\Userdata" -NotInstalled -UNATTENDED + +taskkill /f /im amdaemon.exe > nul 2>&1 + +echo. +echo Game processes have terminated +pause \ No newline at end of file diff --git a/dist/idac/segatools.ini b/dist/idac/segatools.ini new file mode 100644 index 0000000..a2387af --- /dev/null +++ b/dist/idac/segatools.ini @@ -0,0 +1,255 @@ +; ----------------------------------------------------------------------------- +; Path settings +; ----------------------------------------------------------------------------- + +[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 MVxx 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= + +; ----------------------------------------------------------------------------- +; Device settings +; ----------------------------------------------------------------------------- + +[aime] +; Controls emulation of the Aime card reader assembly. +enable=1 +aimePath=DEVICE\aime.txt + +; ----------------------------------------------------------------------------- +; Network settings +; ----------------------------------------------------------------------------- + +[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. +; SEGA games are somewhat picky about their LAN environment, so leaving this +; setting enabled is recommended. +enable=1 + +; ----------------------------------------------------------------------------- +; Board settings +; ----------------------------------------------------------------------------- + +[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. Set it to your LAN's subnet if you +; want to play head-to-head using netenv=1. +subnet=192.168.158.0 + +; Override the keychip's region code. Most games seem to pay attention to the +; DS EEPROM region code and not the keychip region code, and this seems to be +; a bit mask that controls which Nu PCB region codes this keychip is authorized +; for. So it probably only affects the system software and not the game software. +; 1: JPN: Japan, 4: EXP: Export (for Asian markets) +region=4 + +[system] +; Enable ALLS system settings. +enable=1 + +; Enable freeplay mode. This will disable the coin slot and set the game to +; freeplay. Keep in mind that some game modes (e.g. Freedom/Time Modes) will not +; allow you to start a game in freeplay mode. +freeplay=0 + +; If multiple machines are present on the same LAN then set this to 1 on +; exactly one machine and set this to 0 on all others. +dipsw1=1 +; 0 is the DZero CVT cab and 1 is the SWDC CVT cab. +dipsw2=0 +; Enable the Single Seat mode, always requires dipsw1=1. +dipsw3=0 +; The next two dip switches are the seat settings in bits, where +; 00 = Seat 1, 10 = Seat 2, 01 = Seat 3 and 11 = Seat 4 +dipsw4=0 +dipsw5=0 + +[ffb] +; Enable force feedback (838-15069) board emulation. This is required for +; both DirectInput and XInput steering wheel effects. +enable=1 + +; ----------------------------------------------------------------------------- +; LED settings +; ----------------------------------------------------------------------------- + +[led15070] +; Enable emulation of the 837-15070-02 controlled lights, which handle the +; cabinet and seat LEDs. +enable=1 + +; ----------------------------------------------------------------------------- +; Misc. hooks settings +; ----------------------------------------------------------------------------- + +[indrun] +; Hooks to patch GameProject-Win64-Shipping.exe and IndRun.dll. This is needed +; to boot version 1.60.00 and up. The hooks are not needed for version 1.50.00 +; and below. +enable=1 + +; ----------------------------------------------------------------------------- +; Custom IO settings +; ----------------------------------------------------------------------------- + +[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= + +[idacio] +; To use a custom Initial D THE ARCADE IO DLL enter its path here. +; Leave empty if you want to use Segatools built-in gamepad/wheel input. +path= + +; ----------------------------------------------------------------------------- +; 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 F1 key. +test=0x70 +; Service button virtual-key code. Default is the F2 key. +service=0x71 +; Keyboard button to increment coin counter. Default is the F3 key. +coin=0x72 + +; Input API selection for IO4 input emulator. +; Set "xinput" to use a gamepad and "dinput" to use a steering wheel. +mode=xinput +; Adjust scaling for steering wheel input. +; +; This setting scales the steering wheel input so that the maximum positive +; and minimum negative steering inputs reported in the operator menu's input +; test screen do not exceed the value below. The maximum possible value is 128, +; and the value that matches the input range of a real cabinet is 128. +; +; NOTE: This is not the same thing as DirectInput steering wheel movement +; range! Segatools cannot control the maximum angle of your physical steering +; wheel controller, this setting is vendor-specific and can only be adjusted +; in the Control Panel. +restrict=128 + +[xinput] +; XInput bindings +; +; Left Stick Steering +; Right Stick (Steering) when "singleStickSteering" is disabled +; Left Trigger Brake +; Right Trigger Accelerator +; Left Stick Click Left (used for Time Up) +; Right Stick Click Right (used for Time Up) +; Left Shoulder Shift Down +; Right Shoulder Shift Up +; Start/A Start +; Back/B View Change +; X Shift Up +; Y Shift Down +; D-Pad D-Pad + +; Left and right thumbsticks are mapped to left and right dpad buttons. +; Press both thumbsticks to trigger "Time Up" and exit the course. +; Automatically reset the simulated shifter to Neutral when XInput Start is +; pressed (e.g. when navigating menus between races). +autoNeutral=1 +; Use the left thumbstick for steering instead of both on XInput Controllers. +; Not recommended as it will not give you the precision needed for this game. +singleStickSteering=1 +; Use linear steering instead of the default non-linear cubing steering. +linearSteering=1 +; Configure deadzones for the left and right thumbsticks. +; The default value for the left stick is 7849, max value is 32767. +leftStickDeadzone=7849 +; The default value for the right stick is 8689, max value is 32767. +rightStickDeadzone=8689 + +[dinput] +; Name of the DirectInput wheel to use (or any text that occurs in its name) +; Example: G29 +; +; If this is left blank then the first DirectInput device will be used. +deviceName= +; Name of the DirectInput pedals to use (or any subset thereof). +; Leave blank if you do not have separate pedals; aka the pedals are part of +; the wheel. +; +; The pedals will be mapped to the accelAxis and brakeAxis. +pedalsName= +; Name of the positional shifter to use (or any subset thereof). +; Leave blank if you do not have a positional shifter; a positional shifter +; will be simulated using the configured Shift Down and Shift Up buttons +; in this case. +; +; Can be the same device as the wheel. +; +; Example: G29 +shifterName= +; Pedal mappings. Valid axis names are: +; +; X, Y, Z, RX, RY, RZ, U, V +; +; (U and V are old names for Slider 1 and Slider 2). +; The examples below are valid for a Logitech G29. +brakeAxis=RZ +accelAxis=Y +; DirectInput button numbers to map to menu inputs. Note that buttons are +; numbered from 1; some software numbers buttons from 0. +start=1 +viewChg=2 +; DPad is already emulated, but in order to trigger "Time Up" and exit the +; course you need to press both left and right on the DPad at the same time. +; This is not possible on most devices, so we set the left and right button again. +left=7 +right=8 +; Button mappings for the simulated six-speed shifter. +shiftDn=6 +shiftUp=5 +; Button mappings for the positional shifter, if present. +gear1=13 +gear2=14 +gear3=15 +gear4=16 +gear5=17 +gear6=18 +; Invert the accelerator and or brake axis +; (Needed when using DirectInput for the Dualshock 4 for example) +reverseAccelAxis=0 +reverseBrakeAxis=0 + +; Force feedback settings. +; Only works when FFB board emulation is enabled! +; +; It is recommended to change the strength inside the Game Test Mode! +; +; These settings are only used when using DirectInput for the wheel. +; The values are in the range 0%-100%, where 0 disables the effect and +; 100 is the maximum. + +; Constant force strength, used for centering spring effect. +constantForceStrength=100 +; Damper strength, used for steering wheel damper effect. +damperStrength=100 + +; Rumble strength, used for road surface effects. +rumbleStrength=100 +; Rumble duration factor from ms to µs, used to scale the duration of the rumble effect. +rumbleDuration=1000 diff --git a/dist/idz/launch.bat b/dist/idz/launch.bat new file mode 100644 index 0000000..4f6ea5e --- /dev/null +++ b/dist/idz/launch.bat @@ -0,0 +1,15 @@ +@echo off + +pushd %~dp0 + +start "AM Daemon" /min inject -d -k idzhook.dll amdaemon.exe -c configDHCP_Final_Common.json configDHCP_Final_JP.json configDHCP_Final_JP_ST1.json configDHCP_Final_JP_ST2.json configDHCP_Final_EX.json configDHCP_Final_EX_ST1.json configDHCP_Final_EX_ST2.json + +rem Set dipsw1=0 and uncomment the ServerBox for in store battle? +rem inject -k idzhook.dll ServerBoxD8_Nu_x64.exe +inject -d -k idzhook.dll InitialD0_DX11_Nu.exe + +taskkill /im ServerBoxD8_Nu_x64.exe > nul 2>&1 + +echo. +echo Game processes have terminated +pause \ No newline at end of file diff --git a/dist/idz/segatools.ini b/dist/idz/segatools.ini index 367d27e..47778e9 100644 --- a/dist/idz/segatools.ini +++ b/dist/idz/segatools.ini @@ -1,3 +1,7 @@ +; ----------------------------------------------------------------------------- +; Path settings +; ----------------------------------------------------------------------------- + [vfs] ; Insert the path to the game AMFS directory here (contains ICF1 and ICF2) amfs= @@ -8,30 +12,55 @@ option= ; NOTE: This has nothing to do with Windows %APPDATA%. appdata= +; ----------------------------------------------------------------------------- +; Device settings +; ----------------------------------------------------------------------------- + +[aime] +; Controls emulation of the Aime card reader assembly. +enable=1 +; Necessary for IDZ Version 2+ to work +aimePath=DEVICE\aime.txt +felicaGen=0 +aimeGen=1 + +; ----------------------------------------------------------------------------- +; Network settings +; ----------------------------------------------------------------------------- + [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. +; SEGA games are somewhat picky about their LAN environment, so leaving this +; setting enabled is recommended. +enable=1 +; The final octet of the local host's IP address on the virtualized subnet +; (so, if the keychip subnet is 192.168.32.0 and this value is set to 11, +; then the local host's virtualized LAN IP is 192.168.32.11). +; Needed for in store battle, one needs to set it to 12. +;addrSuffix=12 + +; ----------------------------------------------------------------------------- +; Board settings +; ----------------------------------------------------------------------------- + +[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.158.0 + [ds] ; Region code on the emulated AMEX board DS EEPROM. ; 1: Japan ; 4: Export (some UI elements in English) ; ; NOTE: Changing this setting causes a factory reset. -region=1 - -[netenv] -; Simulate an ideal LAN environment. This may interfere with head-to-head play. -; SEGA games are somewhat picky about their LAN environment, so leaving this -; setting enabled is recommended. -enable=1 - -[keychip] -; The /24 LAN subnet that the emulated keychip will tell the game to expect. -; If you disable netenv then you must set this to your LAN's IP subnet, and -; that subnet must start with 192.168. -subnet=192.168.100.0 +region=4 [gpio] ; Emulated Nu DIP switch for Distribution Server setting. @@ -40,6 +69,42 @@ subnet=192.168.100.0 ; exactly one machine and set this to 0 on all others. dipsw1=1 +[ffb] +; Enable force feedback (838-15069) board emulation. This is required for +; both DirectInput and XInput steering wheel effects. +enable=1 + +; ----------------------------------------------------------------------------- +; LED settings +; ----------------------------------------------------------------------------- + +[led15070] +; Enable emulation of the 837-15070-02 controlled lights, which handle the +; cabinet and seat LEDs. +enable=1 + +; ----------------------------------------------------------------------------- +; Misc. hooks settings +; ----------------------------------------------------------------------------- + +[gfx] +; Enables the graphics hook. This is required for some Initial D Zero versions +; for example v1.31 and v2.11 to run properly in fullscreen. +enable=0 +; Force the game to run windowed. +windowed=0 +; 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 +; Enable DPI awareness for the game process, preventing Windows from stretching the game window if a DPI scaling higher than 100% is used +dpiAware=1 + + +; ----------------------------------------------------------------------------- +; Custom IO settings +; ----------------------------------------------------------------------------- + [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. @@ -50,16 +115,29 @@ path= ; Leave empty if you want to use Segatools built-in gamepad/wheel input. path= +; ----------------------------------------------------------------------------- +; 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. + [io3] +; Test button virtual-key code. Default is the F1 key. +test=0x70 +; Service button virtual-key code. Default is the F2 key. +service=0x71 +; Keyboard button to increment coin counter. Default is the F3 key. +coin=0x72 + ; Input API selection for JVS input emulator. ; Set "xinput" to use a gamepad and "dinput" to use a steering wheel. mode=xinput -; Automatically reset the simulated shifter to Neutral when XInput Start is -; pressed (e.g. when navigating menus between races). -autoNeutral=1 -; Use the left thumbstick for steering instead of both on XInput Controllers. -; Not recommended as it will not give you the precision needed for this game -singleStickSteering=0 ; Adjust scaling for steering wheel input. ; ; This setting scales the steering wheel input so that the maximum positive @@ -73,9 +151,38 @@ singleStickSteering=0 ; in the Control Panel. restrict=97 +[xinput] +; XInput bindings +; +; Left Stick Steering +; Right Stick (Steering) when "singleStickSteering" is disabled +; Left Trigger Brake +; Right Trigger Accelerator +; Left Shoulder Shift Down +; Right Shoulder Shift Up +; Start/A Start +; Back/B View Change +; X Shift Up +; Y Shift Down +; D-Pad D-Pad + +; Automatically reset the simulated shifter to Neutral when XInput Start is +; pressed (e.g. when navigating menus between races). +autoNeutral=1 +; Use the left thumbstick for steering instead of both on XInput Controllers. +; Not recommended as it will not give you the precision needed for this game. +singleStickSteering=1 +; Use linear steering instead of the default non-linear cubing steering. +linearSteering=1 +; Configure deadzones for the left and right thumbsticks. +; The default value for the left stick is 7849, max value is 32767. +leftStickDeadzone=7849 +; The default value for the right stick is 8689, max value is 32767. +rightStickDeadzone=8689 + [dinput] ; Name of the DirectInput wheel to use (or any text that occurs in its name) -; Example: TMX +; Example: G29 ; ; If this is left blank then the first DirectInput device will be used. deviceName= @@ -86,31 +193,56 @@ deviceName= ; ; Can be the same device as the wheel. ; -; Example: T500 +; Example: G29 shifterName= +; Name of the DirectInput pedals to use (or any subset thereof). +; Leave blank if you do not have separate pedals; aka the pedals are part of +; the wheel. +; +; The pedals will be mapped to the accelAxis and brakeAxis. +pedalsName= ; Pedal mappings. Valid axis names are: ; ; X, Y, Z, RX, RY, RZ, U, V ; ; (U and V are old names for Slider 1 and Slider 2). -; The examples below are valid for a Thrustmaster TMX. -brakeAxis=RZ +; The examples below are valid for a Logitech G29. +brakeAxis=U accelAxis=Y ; DirectInput button numbers to map to menu inputs. Note that buttons are ; numbered from 1; some software numbers buttons from 0. -start=3 -viewChg=10 +start=1 +viewChg=2 ; Button mappings for the simulated six-speed shifter. -shiftDn=1 -shiftUp=2 +shiftDn=5 +shiftUp=6 ; Button mappings for the positional shifter, if present. -gear1=1 -gear2=2 -gear3=3 -gear4=4 -gear5=5 -gear6=6 +gear1=13 +gear2=14 +gear3=15 +gear4=16 +gear5=17 +gear6=18 ; Invert the accelerator and or brake axis ; (Needed when using DirectInput for the Dualshock 4 for example) reverseAccelAxis=0 reverseBrakeAxis=0 + +; Force feedback settings. +; Only works when FFB board emulation is enabled! +; +; It is recommended to change the strength inside the Game Test Mode! +; +; These settings are only used when using DirectInput for the wheel. +; The values are in the range 0%-100%, where 0 disables the effect and +; 100 is the maximum. + +; Constant force strength, used for centering spring effect. +constantForceStrength=100 +; Damper strength, used for steering wheel damper effect. +damperStrength=100 + +; Rumble strength, used for road surface effects. +rumbleStrength=100 +; Rumble duration factor from ms to µs, used to scale the duration of the rumble effect. +rumbleDuration=1000 \ No newline at end of file diff --git a/dist/idz/start.bat b/dist/idz/start.bat deleted file mode 100644 index b8d6b18..0000000 --- a/dist/idz/start.bat +++ /dev/null @@ -1,10 +0,0 @@ -@echo off - -pushd %~dp0 - -.\inject.exe -k .\idzhook.dll .\InitialD0_DX11_Nu.exe -.\inject.exe -d -k .\idzhook.dll .\amdaemon.exe -c configDHCP_Final_Common.json configDHCP_Final_JP.json configDHCP_Final_JP_ST1.json configDHCP_Final_JP_ST2.json configDHCP_Final_EX.json configDHCP_Final_EX_ST1.json configDHCP_Final_EX_ST2.json - -echo. -echo Game processes have terminated -pause \ No newline at end of file diff --git a/dist/kemono/launch.bat b/dist/kemono/launch.bat new file mode 100644 index 0000000..eb4b106 --- /dev/null +++ b/dist/kemono/launch.bat @@ -0,0 +1,12 @@ +@echo off + +pushd %~dp0 + +start "AM Daemon" /min inject_x64 -d -k kemonohook_x64.dll amdaemon.exe -c config.json +inject_x86 -d -k kemonohook_x86.dll UnityApp\Parade -screen-fullscreen 0 -popupwindow -screen-width 720 -screen-height 1280 -silent-crashes + +taskkill /f /im amdaemon.exe > nul 2>&1 + +echo. +echo Game processes have terminated +pause diff --git a/dist/kemono/segatools.ini b/dist/kemono/segatools.ini new file mode 100644 index 0000000..5f6186e --- /dev/null +++ b/dist/kemono/segatools.ini @@ -0,0 +1,150 @@ +; ----------------------------------------------------------------------------- +; Path settings +; ----------------------------------------------------------------------------- + +[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= + +; ----------------------------------------------------------------------------- +; Device settings +; ----------------------------------------------------------------------------- + +[aime] +; Enable Aime card reader assembly emulation. Disable to use a real SEGA Aime +; reader. +enable=1 +aimePath=DEVICE\aime.txt + +[vfd] +; Enable VFD emulation (currently just stubbed). Disable to use a real VFD +; GP1232A02A FUTABA assembly. +enable=1 + +; ----------------------------------------------------------------------------- +; Network settings +; ----------------------------------------------------------------------------- + +[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. +; SEGA games are somewhat picky about its LAN environment, so leaving this +; setting enabled is recommended. +enable=1 +; The final octet of the local host's IP address on the virtualized subnet (so, +; if the keychip subnet is `192.168.32.0` and this value is set to `11`, then the +; local host's virtualized LAN IP is `192.168.32.11`). +addrSuffix=11 + +; ----------------------------------------------------------------------------- +; Board settings +; ----------------------------------------------------------------------------- + +[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.179.0 + +[gpio] +; Emulated Nu DIP switch for Distribution Server setting. +; +; If multiple machines are present on the same LAN then set this to 1 on +; exactly one machine and set this to 0 on all others. +dipsw1=1 + +; Chassis Test button virtual-key code. Default is the 4 key. +test=0x34 +; Chassis Service button virtual-key code. Default is the 5 key. +service=0x35 + +; ----------------------------------------------------------------------------- +; Misc. hook settings +; ----------------------------------------------------------------------------- + +[unity] +; Enable Unity hook. This will allow you to run custom .NET code before the game +enable=1 + +; Path to a .NET DLL that should run before the game. Useful for loading +; modding frameworks such as BepInEx. +; +; NOTE: For Kemono Friends, BepInEx (or similar) should be installed to the main folder, not the "UnityApp" folder. +targetAssembly= + +[printer] +; Sinfonia CHC-C300 printer emulation setting. +enable=1 +; Change the printer serial number here. +serial_no="5A-A123" +; Insert the path to the image output directory here. +printerOutPath="DEVICE\print" +; The length in milliseconds the printer should be busy printing. +; Set to 0 to instantly finish printing. +waitTime=20000 + +; ----------------------------------------------------------------------------- +; LED settings +; ----------------------------------------------------------------------------- + +[led15093] +; 837-15093-06 LED board emulation setting. +enable=1 + +; ----------------------------------------------------------------------------- +; Custom IO settings +; ----------------------------------------------------------------------------- + +[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= + +[kemonoio] +; To use a custom Kemono IO DLL enter its path here. +; Leave empty if you want to use Segatools built-in keyboard input. +path= + +; ----------------------------------------------------------------------------- +; 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. + +[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 + +; Analog lever (which is actually just four buttons, and not real analog input, default: Arrow keys) +left=0x25 +right=0x27 +up=0x26 +down=0x28 + +; Controller face buttons (default A, S, D) +red=0x41 +green=0x53 +blue=0x44 + +; Menu confirmation key (default RETURN) +start=0x0D \ No newline at end of file diff --git a/dist/mai2/launch.bat b/dist/mai2/launch.bat new file mode 100644 index 0000000..e7c6d9a --- /dev/null +++ b/dist/mai2/launch.bat @@ -0,0 +1,12 @@ +@echo off + +pushd %~dp0 + +start "AM Daemon" /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 -popupwindow -screen-width 2160 -screen-height 1920 -silent-crashes + +taskkill /f /im amdaemon.exe > nul 2>&1 + +echo. +echo Game processes have terminated +pause \ No newline at end of file diff --git a/dist/mai2/segatools.ini b/dist/mai2/segatools.ini new file mode 100644 index 0000000..dde853f --- /dev/null +++ b/dist/mai2/segatools.ini @@ -0,0 +1,156 @@ +; ----------------------------------------------------------------------------- +; Path settings +; ----------------------------------------------------------------------------- + +[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= + +; ----------------------------------------------------------------------------- +; Device settings +; ----------------------------------------------------------------------------- + +[aime] +; Enable Aime card reader assembly emulation. Disable to use a real SEGA Aime +; reader. +enable=1 +aimePath=DEVICE\aime.txt + +[vfd] +; Enable VFD emulation. Disable to use a real VFD +; GP1232A02A FUTABA assembly. +enable=1 + +; ----------------------------------------------------------------------------- +; Network settings +; ----------------------------------------------------------------------------- + +[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. +; SEGA games are somewhat picky about its LAN environment, so leaving this +; setting enabled is recommended. +enable=1 +; The final octet of the local host's IP address on the virtualized subnet (so, +; if the keychip subnet is `192.168.32.0` and this value is set to `11`, then the +; local host's virtualized LAN IP is `192.168.32.11`). +addrSuffix=11 + +; ----------------------------------------------------------------------------- +; Board settings +; ----------------------------------------------------------------------------- + +[keychip] +; The /24 LAN subnet that the emulated keychip will tell the game to expect. +; If you disable netenv then you must set this to your LAN's IP subnet, and +; that subnet must start with 192.168. +subnet=192.168.172.0 + +[system] +; Enable ALLS system settings. +enable=1 + +; Enable freeplay mode. This will disable the coin slot and set the game to +; freeplay. Keep in mind that some game modes (e.g. Freedom/Time Modes) will not +; allow you to start a game in freeplay mode. +freeplay=0 + +; LAN Install: If multiple machines are present on the same LAN then set +; this to 1 on exactly one machine and set this to 0 on all others. +dipsw1=1 + +; ----------------------------------------------------------------------------- +; LED settings +; ----------------------------------------------------------------------------- + +[led15070] +; Enable emulation of the 837-15070-04 controlled lights, which handle the +; cabinet and button LEDs. +enable=1 + +; ----------------------------------------------------------------------------- +; Misc. hook settings +; ----------------------------------------------------------------------------- + +[unity] +; Enable Unity hook. This will allow you to run custom .NET code before the game +enable=1 + +; Path to a .NET DLL that should run before the game. Useful for loading +; modding frameworks such as BepInEx. +targetAssembly= + +; ----------------------------------------------------------------------------- +; Custom IO settings +; ----------------------------------------------------------------------------- + +[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= + +[mai2io] +; To use a custom maimai DX IO DLL enter its path here. +; Leave empty if you want to use Segatools built-in keyboard input. +path= + +; ----------------------------------------------------------------------------- +; 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 F1 key. +test=0x70 +; Service button virtual-key code. Default is the F2 key. +service=0x71 +; Keyboard button to increment coin counter. Default is the F3 key. +coin=0x72 + +; 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] +enable=1 +;p1Btn1=0x53 +;p1Btn2=0x53 +;p1Btn3=0x53 +; ... etc ... +;p2Btn1=0x53 +;p2Btn2=0x53 +;p2Btn3=0x53 +; ... etc ... + +[touch] +p1Enable=1 +;p1DebugInput=0 +p2Enable=1 +;p2DebugInput=0 +;p1TouchA1=0x53 +;p1TouchA2=0x53 +; ... etc ... +;p1TouchE8=0x53 diff --git a/dist/mercury/launch.bat b/dist/mercury/launch.bat new file mode 100644 index 0000000..4a64365 --- /dev/null +++ b/dist/mercury/launch.bat @@ -0,0 +1,13 @@ +@echo off +pushd %~dp0 + +REM USA +REM start "AM Daemon" /min inject -d -k mercuryhook.dll amdaemon.exe -f -c config.json config_lan_install_client.json config_lan_install_server.json config_video_clone.json config_video_dual.json config_video_clone_flip.json config_video_dual_flip.json config_region_exp.json config_region_chn.json config_region_usa.json + +REM JP +start "AM Daemon" /min inject -d -k mercuryhook.dll amdaemon.exe -f -c config.json config_lan_install_client.json config_lan_install_server.json config_video_clone.json config_video_dual.json config_video_clone_flip.json config_video_dual_flip.json config_region_exp.json config_region_chn.json config_region_jpn.json +inject -d -k mercuryhook.dll ../WindowsNoEditor/Mercury/Binaries/Win64/Mercury-Win64-Shipping.exe + +taskkill /f /im amdaemon.exe > nul 2>&1 + +echo Game processes have terminated \ No newline at end of file diff --git a/dist/mercury/segatools.ini b/dist/mercury/segatools.ini index f231c66..3199a20 100644 --- a/dist/mercury/segatools.ini +++ b/dist/mercury/segatools.ini @@ -1,30 +1,54 @@ +; ----------------------------------------------------------------------------- +; Path settings +; ----------------------------------------------------------------------------- + [vfs] ; Insert the path to the game AMFS directory here (contains ICF1 and ICF2) -amfs=amfs +amfs= +; Insert the path to the game Option directory here (unused in Mercury) +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=appdata -option=option +appdata= + +; ----------------------------------------------------------------------------- +; Device settings +; ----------------------------------------------------------------------------- + +[aime] +; Enable Aime card reader assembly emulation. Disable to use a real SEGA Aime +; reader. +enable=1 +aimePath=DEVICE\aime.txt + +[vfd] +; Enable VFD emulation. Disable to use a real VFD +; GP1232A02A FUTABA assembly. +enable=1 + +; ----------------------------------------------------------------------------- +; Network settings +; ----------------------------------------------------------------------------- [dns] ; Insert the hostname or IP address of the server you wish to use here. ; Note that 127.0.0.1, localhost etc are specifically rejected. default=127.0.0.1 -[ds] -; Region code on the emulated AMEX board DS EEPROM. -; 1: Japan -; 4: Export (some UI elements in English) -; -; NOTE: Changing this setting causes a factory reset. -region=1 - [netenv] ; Simulate an ideal LAN environment. This may interfere with head-to-head play. ; SEGA games are somewhat picky about their LAN environment, so leaving this ; setting enabled is recommended. enable=1 +; The final octet of the local host's IP address on the virtualized subnet (so, +; if the keychip subnet is `192.168.32.0` and this value is set to `11`, then the +; local host's virtualized LAN IP is `192.168.32.11`). +addrSuffix=11 + +; ----------------------------------------------------------------------------- +; Board settings +; ----------------------------------------------------------------------------- [keychip] ; The /24 LAN subnet that the emulated keychip will tell the game to expect. @@ -32,16 +56,29 @@ enable=1 ; that subnet must start with 192.168. subnet=192.168.174.0 -[gfx] +[system] +; Enable ALLS system settings. enable=1 -[io4] -; Input API selection for JVS input emulator. -test=0x2D -service=0x2E -coin=0x24 -volup=0x26 -voldown=0x28 +; Enable freeplay mode. This will disable the coin slot and set the game to +; freeplay. Keep in mind that some game modes (e.g. Freedom/Time Modes) will not +; allow you to start a game in freeplay mode. +freeplay=0 + +; LAN Install: If multiple machines are present on the same LAN then set +; this to 1 on exactly one machine and set this to 0 on all others. +dipsw1=1 + +; ----------------------------------------------------------------------------- +; Misc. hook settings +; ----------------------------------------------------------------------------- + +[gfx] +; Enables the graphics hook. +enable=1 +; Enable DPI awareness for the game process, preventing Windows from stretching the game window if a DPI scaling higher than 100% is used +dpiAware=1 + ; Hooks related to the touch boards [touch] @@ -51,6 +88,41 @@ enable=1 [elisabeth] enable=1 -;[mercuryio] -; Use mercuryio.dll -;path=mercuryio.dll +; ----------------------------------------------------------------------------- +; Custom IO settings +; ----------------------------------------------------------------------------- + +[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= + +[mercuryio] +; To use a custom WACCA IO DLL enter its path here. +; Leave empty if you want to use Segatools built-in keyboard input. +path= + +; ----------------------------------------------------------------------------- +; 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 F1 key. +test=0x70 +; Service button virtual-key code. Default is the F2 key. +service=0x71 +; Keyboard button to increment coin counter. Default is the F3 key. +coin=0x72 + +; Volume up virtual-key code. Default is the "UP" key. +volup=0x26 +; Volume down virtual-key code. Default is the "DOWN" key. +voldown=0x28 diff --git a/dist/mercury/start.bat b/dist/mercury/start.bat deleted file mode 100644 index c00f2ea..0000000 --- a/dist/mercury/start.bat +++ /dev/null @@ -1,15 +0,0 @@ -@echo off -pushd %~dp0 - -taskkill /f /im amdaemon.exe > nul 2>&1 - -REM USA -REM start inject -d -k mercuryhook.dll amdaemon.exe -f -c config.json config_lan_install_client.json config_lan_install_server.json config_video_clone.json config_video_dual.json config_video_clone_flip.json config_video_dual_flip.json config_region_exp.json config_region_chn.json config_region_usa.json - -REM JP -start inject -d -k mercuryhook.dll amdaemon.exe -f -c config.json config_lan_install_client.json config_lan_install_server.json config_video_clone.json config_video_dual.json config_video_clone_flip.json config_video_dual_flip.json config_region_exp.json config_region_chn.json config_region_jpn.json -inject -d -k mercuryhook.dll ../WindowsNoEditor/Mercury/Binaries/Win64/Mercury-Win64-Shipping.exe - -taskkill /f /im amdaemon.exe > nul 2>&1 - -echo Game processes have terminated \ No newline at end of file diff --git a/dist/mu3/launch.bat b/dist/mu3/launch.bat new file mode 100644 index 0000000..743a3df --- /dev/null +++ b/dist/mu3/launch.bat @@ -0,0 +1,11 @@ +@echo off + +pushd %~dp0 + +start "AM Daemon" /min inject -d -k mu3hook.dll amdaemon.exe -f -c config_common.json config_server.json config_client.json +inject -d -k mu3hook.dll mu3 -screen-fullscreen 0 -popupwindow -screen-width 1080 -screen-height 1920 +taskkill /f /im amdaemon.exe > nul 2>&1 + +echo. +echo Game processes have terminated +pause \ No newline at end of file diff --git a/dist/mu3/segatools.ini b/dist/mu3/segatools.ini index 3279509..61d8cfe 100644 --- a/dist/mu3/segatools.ini +++ b/dist/mu3/segatools.ini @@ -1,60 +1,201 @@ +; ----------------------------------------------------------------------------- +; Path settings +; ----------------------------------------------------------------------------- + [vfs] ; Insert the path to the game AMFS directory here (contains ICF1 and ICF2) -amfs=amfs +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=appdata -option=option +appdata= + +; ----------------------------------------------------------------------------- +; Device settings +; ----------------------------------------------------------------------------- + +[aime] +; Enable Aime card reader assembly emulation. Disable to use a real SEGA Aime +; reader. +enable=1 +aimePath=DEVICE\aime.txt + +[vfd] +; Enable VFD emulation. Disable to use a real VFD +; GP1232A02A FUTABA assembly. +enable=1 + +; ----------------------------------------------------------------------------- +; Network settings +; ----------------------------------------------------------------------------- [dns] ; Insert the hostname or IP address of the server you wish to use here. ; Note that 127.0.0.1, localhost etc are specifically rejected. default=127.0.0.1 -[ds] -; Region code on the emulated AMEX board DS EEPROM. -; 1: Japan -; 4: Export (some UI elements in English) -; -; NOTE: Changing this setting causes a factory reset. -region=1 - [netenv] ; Simulate an ideal LAN environment. This may interfere with head-to-head play. ; SEGA games are somewhat picky about their LAN environment, so leaving this ; setting enabled is recommended. enable=1 +; ----------------------------------------------------------------------------- +; Board settings +; ----------------------------------------------------------------------------- + [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.250.0 +subnet=192.168.162.0 -[gfx] +[system] +; Enable ALLS system settings. enable=1 +; Enable freeplay mode. This will disable the coin slot and set the game to +; freeplay. Keep in mind that some game modes (e.g. Freedom/Time Modes) will not +; allow you to start a game in freeplay mode. +freeplay=0 + +; LAN Install: If multiple machines are present on the same LAN then set +; this to 1 on exactly one machine and set this to 0 on all others. +dipsw1=1 + +; ----------------------------------------------------------------------------- +; Misc. hook settings +; ----------------------------------------------------------------------------- + +[gfx] +; Enables the graphics hook. +enable=1 +; Enable DPI awareness for the game process, preventing Windows from stretching the game window if a DPI scaling higher than 100% is used +dpiAware=1 + + +[unity] +; Enable Unity hook. This will allow you to run custom .NET code before the game +enable=1 + +; Path to a .NET DLL that should run before the game. Useful for loading +; modding frameworks such as BepInEx. +targetAssembly= + +; ----------------------------------------------------------------------------- +; LED settings +; ----------------------------------------------------------------------------- + +[led15093] +; Enable emulation of the 15093-06 controlled lights, which handle the air tower +; RGBs and the rear LED panel (billboard) on the cabinet. +enable=1 + +[led] +; Output billboard LED strip data to a named pipe called "\\.\pipe\ongeki_led" +cabLedOutputPipe=1 +; Output billboard LED strip data to serial +cabLedOutputSerial=0 + +; Output slider LED data to the named pipe +controllerLedOutputPipe=1 +; Output slider LED data to the serial port +controllerLedOutputSerial=0 + +; Serial port to send data to if using serial output. Default is COM5. +;serialPort=COM5 +; Baud rate for serial data +;serialBaud=921600 + +; Data output a sequence of bytes, with JVS-like framing. +; Each "packet" starts with 0xE0 as a sync. To avoid E0 appearing elsewhere, +; 0xD0 is used as an escape character -- if you receive D0 in the output, ignore +; it and use the next sent byte plus one instead. +; +; After the sync is one byte for the board number that was updated, followed by +; the red, green and blue values for each LED. +; +; Board 0 has 61 LEDs: +; [0]-[1]: left side button +; [2]-[8]: left pillar lower LEDs +; [9]-[17]: left pillar center LEDs +; [18]-[24]: left pillar upper LEDs +; [25]-[35]: billboard LEDs +; [36]-[42]: right pillar upper LEDs +; [43]-[51]: right pillar center LEDs +; [52]-[58]: right pillar lower LEDs +; [59]-[60]: right side button +; +; Board 1 has 6 LEDs: +; [0]-[5]: 3 left and 3 right controller buttons +; + +; ----------------------------------------------------------------------------- +; Custom IO settings +; ----------------------------------------------------------------------------- + +[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= + +[mu3io] +; To use a custom O.N.G.E.K.I. IO DLL enter its path here. +; Leave empty if you want to use Segatools built-in keyboard/gamepad input. +path= + +; ----------------------------------------------------------------------------- +; 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] -; Input API selection for JVS input emulator. -; Set "1" to use a xinput gamepad and set "2" to use keyboard. -mode=2 +; Test button virtual-key code. Default is the F1 key. +test=0x70 +; Service button virtual-key code. Default is the F2 key. +service=0x71 +; Keyboard button to increment coin counter. Default is the F3 key. +coin=0x72 -test=0x31 -service=0x32 +; Set "1" to enable mouse lever emulation, "0" to use XInput +mouse=1 -[dinput] -LEFT_A=0x53 -LEFT_B=0x44 -LEFT_C=0x46 -LEFT_MENU=0x51 -LEFT_SIDE=0x52 -RIGHT_A=0x4A -RIGHT_B=0x4B -RIGHT_C=0x4C -RIGHT_MENU=0x50 -RIGHT_SIDE=0x55 -SLIDER_LEFT=0x54 -SLIDER_RIGHT=0x59 -;Change move speed of slider when use dinput -SLIDER_SPEED=1000 \ No newline at end of file +; XInput input bindings +; +; Left Stick Lever +; Left Trigger Lever (move to the left) +; Right Trigger Lever (move to the right) +; Left Left red button +; Up Left green button +; Right Left blue button +; Left Shoulder Left side button +; Right Shoulder Right side button +; X Right red button +; Y Right green button +; A Right blue button +; Back Left menu button +; Start Right menu button + +; Keyboard input bindings +left1=0x41 ; A +left2=0x53 ; S +left3=0x44 ; D + +leftSide=0x01 ; Mouse Left +rightSide=0x02 ; Mouse Right + +right1=0x4A ; J +right2=0x4B ; K +right3=0x4C ; L + +leftMenu=0x55 ; U +rightMenu=0x4F ; O diff --git a/dist/mu3/start.bat b/dist/mu3/start.bat deleted file mode 100644 index c00f2ea..0000000 --- a/dist/mu3/start.bat +++ /dev/null @@ -1,15 +0,0 @@ -@echo off -pushd %~dp0 - -taskkill /f /im amdaemon.exe > nul 2>&1 - -REM USA -REM start inject -d -k mercuryhook.dll amdaemon.exe -f -c config.json config_lan_install_client.json config_lan_install_server.json config_video_clone.json config_video_dual.json config_video_clone_flip.json config_video_dual_flip.json config_region_exp.json config_region_chn.json config_region_usa.json - -REM JP -start inject -d -k mercuryhook.dll amdaemon.exe -f -c config.json config_lan_install_client.json config_lan_install_server.json config_video_clone.json config_video_dual.json config_video_clone_flip.json config_video_dual_flip.json config_region_exp.json config_region_chn.json config_region_jpn.json -inject -d -k mercuryhook.dll ../WindowsNoEditor/Mercury/Binaries/Win64/Mercury-Win64-Shipping.exe - -taskkill /f /im amdaemon.exe > nul 2>&1 - -echo Game processes have terminated \ No newline at end of file diff --git a/dist/swdc/config_hook.json b/dist/swdc/config_hook.json new file mode 100644 index 0000000..6c33540 --- /dev/null +++ b/dist/swdc/config_hook.json @@ -0,0 +1,6 @@ +{ + "emoney" : + { + "enable" : false + } +} diff --git a/dist/swdc/launch.bat b/dist/swdc/launch.bat new file mode 100644 index 0000000..a44605b --- /dev/null +++ b/dist/swdc/launch.bat @@ -0,0 +1,41 @@ +@echo off + +pushd %~dp0 + +REM Root directory +set ROOT_DIR=WindowsNoEditor + +rem Matching Server paths +set MATCHING_SERVER_FILE_NAME=tdrserver.exe +set MATCHING_SERVER_PATH=..\Tools\%MATCHING_SERVER_FILE_NAME% + +rem AM Daemon paths +set DAEMON_DIR=%ROOT_DIR%\AMDaemon +set DAEMON_CONFIG_PATH=%DAEMON_DIR%\config.json +rem Make sure to use appdata=appdata in segatools.ini +set DAEMON_CHECK_LAN_SERVER_PATH=appdata\SDDS\LanServer.dat + +rem Check if LanServer.dat is present +if exist "%DAEMON_CHECK_LAN_SERVER_PATH%" ( + set DAEMON_LAN_CONFIG_PATH=%DAEMON_DIR%\config_LanServer.json + + start "matching_server" /min %MATCHING_SERVER_PATH% +) else ( + set DAEMON_LAN_CONFIG_PATH=%DAEMON_DIR%\config_LanClient.json +) + +start "AM Daemon" /min inject -d -k swdchook.dll "%DAEMON_DIR%\amdaemon.exe" -c %DAEMON_CONFIG_PATH% -c %DAEMON_LAN_CONFIG_PATH% config_hook.json + +REM Launch Todoroki +set APP_EXE_DIR=WindowsNoEditor\Todoroki\Binaries\Win64 +set APP_EXE_PATH=%APP_EXE_DIR%\Todoroki-Win64-Shipping.exe + +REM Valid -launch parameters are "Cabinet" or "MiniCabinet" +inject -d -k swdchook.dll "%APP_EXE_PATH%" -launch="MiniCabinet" -ABSLOG="..\Userdata\Todoroki.log" -UserDir="..\Userdata" -NotInstalled -UNATTENDED + +taskkill /f /im amdaemon.exe > nul 2>&1 +taskkill /f /im tdrserver.exe > nul 2>&1 + +echo. +echo Game processes have terminated +pause \ No newline at end of file diff --git a/dist/swdc/segatools.ini b/dist/swdc/segatools.ini new file mode 100644 index 0000000..1b24a74 --- /dev/null +++ b/dist/swdc/segatools.ini @@ -0,0 +1,205 @@ +; ----------------------------------------------------------------------------- +; Path settings +; ----------------------------------------------------------------------------- + +[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=appdata + +; ----------------------------------------------------------------------------- +; Device settings +; ----------------------------------------------------------------------------- + +[aime] +; Enable Aime card reader assembly emulation. Disable to use a real SEGA Aime +; reader. +enable=1 +aimePath=DEVICE\aime.txt + +[vfd] +; Enable VFD emulation. Disable to use a real VFD +; GP1232A02A FUTABA assembly. +enable=1 + +; ----------------------------------------------------------------------------- +; Network settings +; ----------------------------------------------------------------------------- + +[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. +; SEGA games are somewhat picky about their LAN environment, so leaving this +; setting enabled is recommended. +enable=1 +; The final octet of the local host's IP address on the virtualized subnet (so, +; if the keychip subnet is `192.168.32.0` and this value is set to `11`, then the +; local host's virtualized LAN IP is `192.168.32.11`). +addrSuffix=11 + +; ----------------------------------------------------------------------------- +; Board settings +; ----------------------------------------------------------------------------- + +[keychip] +; The /24 LAN subnet that the emulated keychip will tell the game to expect. +; You must set this to your LAN's IP subnet, and that subnet must start with 192.168, +; in order to find the MAIN cabinet. +subnet=192.168.160.0 + +[system] +; Enable ALLS system settings. +enable=1 + +; Enable freeplay mode. This will disable the coin slot and set the game to +; freeplay. Keep in mind that some game modes (e.g. Freedom/Time Modes) will not +; allow you to start a game in freeplay mode. +freeplay=0 + +[ffb] +; Enable force feedback (838-15069) board emulation. This is required for +; both DirectInput and XInput steering wheel effects. +enable=1 + +; ----------------------------------------------------------------------------- +; Custom IO settings +; ----------------------------------------------------------------------------- + +[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= + +[swdcio] +; To use a custom SEGA World Drivers Championship DLL enter its path here. +; Leave empty if you want to use Segatools built-in gamepad/wheel input. +path= + +; ----------------------------------------------------------------------------- +; 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 F1 key. +test=0x70 +; Service button virtual-key code. Default is the F2 key. +service=0x71 +; Keyboard button to increment coin counter. Default is the F3 key. +coin=0x72 + +; Input API selection for IO4 input emulator. +; Set "xinput" to use a gamepad and "dinput" to use a steering wheel. +mode=xinput +; Adjust scaling for steering wheel input. +; +; This setting scales the steering wheel input so that the maximum positive +; and minimum negative steering inputs reported in the operator menu's input +; test screen do not exceed the value below. The maximum possible value is 128, +; and the value that matches the input range of a real cabinet is 128. +; +; NOTE: This is not the same thing as DirectInput steering wheel movement +; range! Segatools cannot control the maximum angle of your physical steering +; wheel controller, this setting is vendor-specific and can only be adjusted +; in the Control Panel. +restrict=128 + +[xinput] +; XInput bindings +; +; Left Stick Steering +; Right Stick (Steering) when "singleStickSteering" is disabled +; Left Trigger Brake +; Right Trigger Accelerator +; Left Shoulder Left Paddle +; Right Shoulder Right Paddle +; Start Start +; Back View Change +; A Green (Wheel) +; B Red (Wheel) +; X Blue (Wheel) +; Y Yellow (Wheel) +; D-Pad D-Pad + +; Use the left thumbstick for steering instead of both on XInput Controllers. +; Not recommended as it will not give you the precision needed for this game. +singleStickSteering=1 +; Use linear steering instead of the default non-linear cubing steering. +linearSteering=1 +; Configure deadzones for the left and right thumbsticks. +; The default value for the left stick is 7849, max value is 32767. +leftStickDeadzone=7849 +; The default value for the right stick is 8689, max value is 32767. +rightStickDeadzone=8689 + +[dinput] +; Name of the DirectInput wheel to use (or any text that occurs in its name) +; Example: G29 +; +; If this is left blank then the first DirectInput device will be used. +deviceName= +; Name of the DirectInput pedals to use (or any subset thereof). +; Leave blank if you do not have separate pedals; aka the pedals are part of +; the wheel. +; +; The pedals will be mapped to the accelAxis and brakeAxis. +pedalsName= +; Pedal mappings. Valid axis names are: +; +; X, Y, Z, RX, RY, RZ, U, V +; +; (U and V are old names for Slider 1 and Slider 2). +; The examples below are valid for a Logitech G29. +brakeAxis=RZ +accelAxis=Y +; DirectInput button numbers to map to menu inputs. Note that buttons are +; numbered from 1; some software numbers buttons from 0. +start=1 +viewChg=2 +; Button mappings for the steering wheel paddles. +paddleLeft=6 +paddleRight=5 +; Button mappings for the steering wheel buttons. +wheelRed=7 +wheelBlue=8 +wheelYellow=9 +wheelGreen=10 +; Invert the accelerator and or brake axis +; (Needed when using DirectInput for the Dualshock 4 for example) +reverseAccelAxis=0 +reverseBrakeAxis=0 + +; Force feedback settings. +; Only works when FFB board emulation is enabled! +; +; It is recommended to change the strength inside the Game Test Mode! +; +; These settings are only used when using DirectInput for the wheel. +; The values are in the range 0%-100%, where 0 disables the effect and +; 100 is the maximum. + +; Constant force strength, used for centering spring effect. +constantForceStrength=100 +; Damper strength, used for steering wheel damper effect. +damperStrength=100 + +; Rumble strength, used for road surface effects. +rumbleStrength=100 +; Rumble duration factor from ms to µs, used to scale the duration of the rumble effect. +rumbleDuration=1000 diff --git a/dist/tokyo/config_hook.json b/dist/tokyo/config_hook.json new file mode 100644 index 0000000..3758b3d --- /dev/null +++ b/dist/tokyo/config_hook.json @@ -0,0 +1,9 @@ +{ + "network" : + { + "property" : + { + "dhcp" : true + } + } +} diff --git a/dist/tokyo/launch.bat b/dist/tokyo/launch.bat new file mode 100644 index 0000000..01c4ddf --- /dev/null +++ b/dist/tokyo/launch.bat @@ -0,0 +1,57 @@ +@echo off +pushd %~dp0 + +set DAEMON_WAIT_SECONDS=5 + +set AMDAEMON_CFG=config_common.json ^ +config_ch.json ^ +config_ex.json ^ +config_jp.json ^ +config_st1_ch.json ^ +config_st1_ex.json ^ +config_st1_jp.json ^ +config_st2_ch.json ^ +config_st2_ex.json ^ +config_st2_jp.json ^ +config_st3_ch.json ^ +config_st3_ex.json ^ +config_st3_jp.json ^ +config_st4_ch.json ^ +config_st4_ex.json ^ +config_st4_jp.json ^ +config_laninstall_server_ch.json ^ +config_laninstall_client1_ch.json ^ +config_laninstall_client2_ch.json ^ +config_laninstall_client3_ch.json ^ +config_laninstall_server_ex.json ^ +config_laninstall_client1_ex.json ^ +config_laninstall_client2_ex.json ^ +config_laninstall_client3_ex.json ^ +config_laninstall_server_jp.json ^ +config_laninstall_client1_jp.json ^ +config_laninstall_client2_jp.json ^ +config_laninstall_client3_jp.json ^ +config_hook.json + +start /min "AM Daemon" inject -d -k tokyohook.dll amdaemon.exe -c %AMDAEMON_CFG% +timeout %DAEMON_WAIT_SECONDS% > nul 2>&1 + +REM --------------------------------------------------------------------------- +REM Set configuration +REM --------------------------------------------------------------------------- + +REM Configuration values to be passed to the game executable. +REM All known values: +REM -forceapi:11 +REM -forcehal +REM -forcevsync:0/1 +REM -fullscreen +REM -windowed +REM Note: -windowed is recommended as the game looks sharper in windowed mode. +inject -d -k tokyohook.dll app.exe -windowed + +taskkill /f /im amdaemon.exe > nul 2>&1 + +echo. +echo Game processes have terminated +pause diff --git a/dist/tokyo/segatools.ini b/dist/tokyo/segatools.ini new file mode 100644 index 0000000..8c68b53 --- /dev/null +++ b/dist/tokyo/segatools.ini @@ -0,0 +1,199 @@ +; ----------------------------------------------------------------------------- +; Path settings +; ----------------------------------------------------------------------------- + +[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 OPxx 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= + +; ----------------------------------------------------------------------------- +; Network settings +; ----------------------------------------------------------------------------- + +[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. +; SEGA games are somewhat picky about their LAN environment, so leaving this +; setting enabled is recommended. +enable=1 + +; The final octet of the local host's IP address on the virtualized subnet (so, +; if the keychip subnet is `192.168.149.0` and this value is set to `205`, then the +; local host's virtualized LAN IP is `192.168.149.205`). +addrSuffix=205 + +; ----------------------------------------------------------------------------- +; Board settings +; ----------------------------------------------------------------------------- + +[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.149.0 + +; Override the keychip's region code. +; 1: JAPAN (ALL.Net, Japanese language, Option support enabled) +; 4: EXPORT (Local networking only, English language, No option support) +; 8: CHINA +; +; NOTE: Changing this setting causes a factory reset. The language can be +; changed in the game settings, so it's possible to run the JAPAN region +; with English language. +region=1 + +[system] +; Enable ALLS system settings. +enable=1 + +; Enable freeplay mode. This will disable the coin slot and set the game to +; freeplay. Keep in mind that some game modes (e.g. Freedom/Time Modes) will not +; allow you to start a game in freeplay mode. +freeplay=0 + +; For Mario & Sonic at the Tokyo 2020 Olympics Arcade, DipSw 1/2/3 must be set +; as the following: +; Cabinet ID 1 (Server): 1 0 0 +; Cabinet ID 2 (Client): 0 1 0 +; Cabinet ID 3 (Client): 0 0 1 +; Cabinet ID 4 (Client): 0 1 1 +dipsw1=1 +dipsw2=0 +dipsw3=0 + +; ----------------------------------------------------------------------------- +; LED settings +; ----------------------------------------------------------------------------- + +[led15093] +; Enable emulation of the 15093-04 controlled lights, which handle the cabinet +; LEDs. +enable=1 + +; ----------------------------------------------------------------------------- +; Misc. hook settings +; ----------------------------------------------------------------------------- + +[zinput] +; Disables the built-in DirectInput support, which is used to support a +; controller out of the box. +enable=1 + +; ----------------------------------------------------------------------------- +; Custom IO settings +; ----------------------------------------------------------------------------- + +[tokyoio] +; To use a custom Mario & Sonic at the Tokyo 2020 Olympics Arcade IO DLL enter +; its path here. Leave empty if you want to use Segatools built-in keyboard/ +; gamepad input. +path= + +; ----------------------------------------------------------------------------- +; 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 F1 key. +test=0x70 +; Service button virtual-key code. Default is the F2 key. +service=0x71 +; Keyboard button to increment coin counter. Default is the F3 key. +coin=0x72 + +; Input API selection for IO4 input emulator. +; Set "xinput" to use a gamepad and "keyboard" to use a keyboard. +mode=xinput + +; Mario & Sonic at the Tokyo 2020 Olympics Arcade Control Panel +; +; |--|------------------ Main-Assy ------------------|--| +; | | YELLOW | | +; | | --- | | +; | | ( O ) | | +; |--| BLUE --- RED |--| +; | | --- PUSH CENTER --- | | +; | | ( O ) /---------------\ ( O ) | | +; | | --- / \ --- | | +; | | PUSH LEFT / \ PUSH RIGHT| | +; |--|---------/ Floor Assy \---------|--| +; | | |JUMP SENSE JUMP SENSE| | | +; | | |1|---------------|-|-------------->|1| | | +; | | | | Foot Panel | | Foot Panel | | | | +; | | |2|<- - - - - - - |-| - - - - - - - |2| | | +; | | | | | | | | | | +; | | |3| -FOOT SENSE - |-| - FOOT SENSE->|3| | | +; | | | | L | | R | | | | +; | | |4|<- - - - - - - |-| - - - - - - - |4| | | +; | | | | | | | | | | +; | | |5| - - - - - - - |-| - - - - - - ->|5| | | +; | | | | | | | | | | +; | | |6|<--------------|-|---------------|6| | | +; | | | | | | +; | | | | | | +; |--|----|-------------------------------------|----|--| +; + +; XInput bindings +; +; X Push Left Blue +; Y Push Center Yellow +; B Push Right Red +; D-Pad Left Push Left Blue +; D-Pad Right Push Right Red +; Left Trigger Foot Sense L/Jump Sense +; Right Trigger Foot Sense R/Jump Sense + +[keyboard] +; Keyboard bindings + +; Keyoard: Push button settings + +; PUSH LEFT (BLUE) button virtual-key code. Default is the A key. +leftBlue=0x41 +; PUSH CENTER (YELLOW) button virtual-key code. Default is the S key. +centerYellow=0x53 +; PUSH RIGHT (RED) button virtual-key code. Default is the D key. +rightRed=0x44 + +; Keyboard: Sensor settings +; FOOT SENSE L (LEFT) button virtual-key code. Default is the Left Arrow key. +footLeft=0x25 +; FOOT SENSE R (RIGHT) button virtual-key code. Default is the Right Arrow key. +footRight=0x27 + +; Keyboard: Jump sensor settings +; All jump sensors will also trigger the FOOT SENSE L and FOOT SENSE R buttons. +; JUMP SENSOR 1 button virtual-key code. Default is the Z key. +jump1=0x5A +; JUMP SENSOR 2 button virtual-key code. Default is the X key. +jump2=0x58 +; JUMP SENSOR 3 button virtual-key code. Default is the C key. +jump3=0x43 +; JUMP SENSOR 4 button virtual-key code. Default is the B key. +jump4=0x42 +; JUMP SENSOR 5 button virtual-key code. Default is the N key. +jump5=0x4E +; JUMP SENSOR 6 button virtual-key code. Default is the M key. +jump6=0x4D + +; Virtual-key code for all jump sensors. Default is the Space key. +jumpAll=0x20 diff --git a/doc/15093-06.txt b/doc/15093-06.txt new file mode 100644 index 0000000..75e3011 --- /dev/null +++ b/doc/15093-06.txt @@ -0,0 +1,124 @@ +Reverse-engineered 15093-06 protocol +(somewhatlurker) + + +The host and device seem to communicate using data frames similar to (but not +the same as) jvs and the slider protocol. + +In general, the host will issue a command to the device and the device will +respond using the same command number. +The response will have source and destination addresses swapped of course. + +The host can request for future packets to not have responses, though this may +only affect certain commands such as LED data. Just something to be aware of +when implementing the system. + + +Basic packet format: `[sync] [dest] [src] [len] [data] [sum]` +sync: 0xe0 +dest: destination address +src: source address +len: length of data +data: payload +sum: sum of all prior bytes except sync + +When the host requests something from/sends something to the board, [data] will +be `[cmd] ...`. +cmd: command number +(followed by arbitrary additional data if applicable) + +When the board responds, [data] will be `[status] [cmd] [report] ...`. +status: status code + (1: Ok, 2: SumError, 3: ParityError, 4: FramingError, 5: OverRunError, + 6: RecvBfOverflow) +cmd: command number (same as the one from request) +report: report status code + (1: Ok, 2: Wait, 3: ReportError, 4: ReportError) +(followed by arbitrary additional data if applicable) + + +Escaping: +Like in JVS, the sync byte and 0xd0 are reserved. To include these in data, send +0xd0 followed by the reserved byte minus 1. (ie. `d0 cf` or `d0 df`) + + +Addresses and game-specific details: +Chunithm uses 2 for the LED boards and 1 for the host. There's two boards +present, but they are differentiated purely by COM port (one COM10, one COM11). +Based on wiring diagrams, I think COM10 should be for the left half of the +marquee display (10 pixels * 5 columns) and the left partition lights (3 pixels). +COM11 should be for the right half (6 columns) and the right partition lights. +The marquee appears to snake strips back and forth (input of first column should +be at the top). + +Ongeki seems to use 1 for the LED board and 2 for the host. It should be on +COM3. +I think the chain is left button (x2), lower left pillar (x7), left ring (x9), +upper left pillar (x7), top edge (x11), upper right pillar (x7), +right ring (x9), lower right pillar (x9), right button (x2). + + +Known Commands: +0xf0: get board info + -- chunithm host sends command with no additional data (`e0 02 01 01 f0 f4`) + -- respond with additional data `[boardno] 0a [chipno] ff [fwver] ...`, + boardno and chipno are strings (seems same as slider protocol) + -- ongeki uses 0a and ff as string terminators, not sure if that's the + intended use though + -- fwver can be found in an update filename (90 for chunithm, a0 for ongeki) + -- there's probably some additional bytes like for slider board info, but + I don't think they're important + -- pad strings with 0x20 (important!) + +0xf2: get firm sum + -- respond with additional data `[sum_upper] [sum_lower]` + -- sum can be found in an update filename (adf7 for chuni, aa53 for ongeki) + +0xf3: get protocol version + -- respond with additional data `[appli_mode] [major] [minor]`, appli_mode + is bool + -- version shouldn't matter much, but I think appli_mode should be 1 + -- try `01 01 04` + +0x11: set timeout + -- host will send with additional data `[timeout_upper] [timeout_lower]` + -- respond with additional data `[timeout_upper] [timeout_lower]` + -- 0 disables timeout + -- presumably this makes the device reset if no data is sent for a certain + time period, or maybe the device sends some kind of heartbeat within this + period + +0x10: reset + -- host will send one additional byte (d9) to choose the reset code/type + -- respond with no additional data + +0xf1: get board status + -- shouldn't be necessary to properly implement this, but if you must... + host sends with additional data `[flagclear]`, + respond with additional data `[boardflag] [uartflag] [cmdflag] [dipsw]` + -- flagclear is a bool that presumably resets error flags + -- flags are bitfields + -- boardflag: `0 0 0 0 [bor] [reset] [timeout] [wdt]` (MSB first) + -- uartflag: `0 0 [txoverflow] [rxoverflow] [overrun] [framing] [parity] [sum]` + -- cmdflag: `0 0 0 0 [exe] [param] [unknown] [busy]` + +0x14: set disable response + -- host will send with additional data `[enable]` + -- respond with additional data `[enable]` + -- it looks like setting enable to true will _disable_ responses + -- I think this makes the device not send responses for future commands + -- it might only affect LED commands + +0x82: set led direct + -- host sends 66*3 bytes for rgb as additional data + -- respond with no additional data + +0x86: set led count + -- host sends additional data `[count]` + -- respond with additional data `[count]` + -- probably just affects the output from the board to LEDs, + neither chuni nor ongeki use this + +0xfd: enter bootloader + -- no real point implementing this, just interesting + -- MCU might be an ATMega 32M1 btw, but I'm not sure \ No newline at end of file diff --git a/doc/chunihook.md b/doc/chunihook.md index 57e19b0..7b68a20 100644 --- a/doc/chunihook.md +++ b/doc/chunihook.md @@ -44,7 +44,7 @@ option=../../option 1. In the `[dns]` section, set `default=` to your computer's hostname or LAN IP. Do not put `127.0.0.1` here, the game specifically checks for and rejects loopback addresses. This setting controls the address of the network services server -1. Right click `start.bat` in `app/bin` and run it as Administrator. I think you need to run it as +1. Right click `launch.bat` in `app/bin` and run it as Administrator. I think you need to run it as admin at least once, but once you have done that you can run the game as a regular user 1. A sequence of several start-up screens will be displayed. You should also see a bunch of debug output in a command line window; if you're seeing hex dumps here then that's a good sign. There diff --git a/doc/config/common.md b/doc/config/common.md index 7dc14c1..1485feb 100644 --- a/doc/config/common.md +++ b/doc/config/common.md @@ -6,6 +6,17 @@ all games. Keyboard binding settings use [Virtual-Key Codes](https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes). +## Config Path +The default file path for config file is `./segatools.ini`. + +You can modify environment variable `SEGATOOLS_CONFIG_PATH` to another path. + +For example, You can have another `launch.bat` with following code in it, +Then you can copy `segatools.ini` to `another_config.ini` but with different dns host in it +```bat +set SEGATOOLS_CONFIG_PATH=.\another_config.ini +``` + ## `[aimeio]` Controls the card reader driver. @@ -31,12 +42,42 @@ Default: `1` Enable Aime card reader assembly emulation. Disable to use a real SEGA Aime reader (COM port number varies by game). +### `portNo` + +Default: (game specific) + +Sets the COM port to use for the aime card reader assembly. + +### `highBaud` + +Default: `1` + +Enables the high baudrate of the Aime card reader to be 115200 (instead of 38400). +This is required for some games (e.g. Chunithm) but not others (e.g. WACCA). + +### `gen` + +Default: `1` + +Changes the Aime card reader generation, this will also change the LED info +provided for the game. + +- `1`: TN32MSEC003S H/W Ver3.0 / TN32MSEC003S F/W Ver1.2 +- `2`: 837-15286 / 94 +- `3`: 837-15396 / 94 + ### `aimePath` Default: `DEVICE\aime.txt` -Path to a text file containing a classic Aime IC card ID. **This does not -currently work**. +Path to a text file containing a classic Aime IC card ID. + +### `aimeGen` + +Default: `1` + +Whether to generate a random AiMe ID if the file at `aimePath` does not +exist. ### `felicaPath` @@ -46,7 +87,7 @@ Path to a text file containing a FeliCa e-cash card IDm serial number. ### `felicaGen` -Default: `1` +Default: `0` Whether to generate a random FeliCa ID if the file at `felicaPath` does not exist. @@ -60,6 +101,41 @@ emulates an IC card in its proximity. A variety of different IC cards can be emulated; the exact choice of card that is emulated depends on the presence or absence of the configured card ID files. +### `proxyFlag` + +Default: `2` + +The "proxy flag" of the emulated Thinca authentication card. This should be 2 if no proxy is used, and 3 if it is. Invalid values will break Thinca authentication card reading. This information can be obtained by checking for the presence of "use_proxy: true" `tfps-res-pro\env.json`. + +### `authdataPath` + +Default: `DEVICE\authdata.bin` + +Path to the binary file containing data for a Thinca authentication card (see `emoney.txt`) + +## `[vfd]` + +Controls emulation of the VFD GP1232A02A FUTABA assembly. + +### `enable` + +Default: `1` + +Enable VFD emulation. Disable to use a real VFD +GP1232A02A FUTABA assembly (COM port number varies by game). + +### `portNo` + +Default: (game specific) + +Sets the COM port to use for the VFD. + +### `utfConversion` + +Default: `0` + +Converts the strings from the VFD from their respective encoding to UTF, so console output will display as it should on non-Japanese locales. + ## `[amvideo]` Controls the `amvideo.dll` stub built into Segatools. This is a DLL that is @@ -118,6 +194,13 @@ setting. Also, loopback addresses are specifically checked for and rejected by the games themselves; this needs to be a LAN or WAN IP (or a hostname that resolves to one). +### `title` + +Default: `title` + +Leave it as `title` to use the title server returned by ALL.Net. Rewrites +the title server hostname for certain games, such as crossbeats REV. + ### `router` Default: Empty string (i.e. use value from `default` setting) @@ -143,6 +226,30 @@ Default: Empty string (i.e. use value from `default` setting) Overrides the target of the `aime.naominet.jp` host lookup. +### `replaceHost` + +Default: `0` + +Replace the HOST field in HTTP request headers with the settings above. This may help bypass network restrictions in some regions. + +### `startupPort` + +Default: `0` (i.e. no operation will perform) + +Overrides the port of connections to the `startup` server. The current implementation affects every TCP connection to the port 80. + +### `billingPort` + +Default: `0` (i.e. no operation will perform) + +Overrides the port of connections to the `billing` server. The current implementation affects every TCP connection to the port 8443. + +### `aimedbPort` + +Default: `0` (i.e. no operation will perform) + +Overrides the port of connections to the `aimedb` server. The current implementation affects every TCP connection to the port 22345. + ## `[ds]` Controls emulation of the "DS (Dallas Semiconductor) EEPROM" chip on the AMEX @@ -247,6 +354,38 @@ Nu chassis DIP switch settings: - `111`: 1920x1080 - Switch 8: Game-specific. Not used in any shipping game. +## `[gfx]` + +### `enable` + +Default: `1` + +Enables graphic hooks. + +### `windowed` + +Default: `0` + +Force the game to run windowed. + +### `framed` + +Default: `0` + +Add a frame to the game window if running windowed. + +### `monitor` + +Default: `0` + +Select the monitor to run the game on. (Fullscreen only, 0 = primary screen) + +### `dpiAware` + +Default: `1` + +Sets the game to be DPI-aware. This prevents Windows automatically scaling the game window by your desktop's scaling factor, which may cause blurry graphics. + ## `[hwmon]` Configure stub implementation of the platform hardware monitor driver. The @@ -270,6 +409,33 @@ Default `1` Enable JVS port emulation. Disable to use the JVS port on a real AMEX. +## `[io4]` + +Configure emulation of the IO4 board. Same settings also apply to `[io3]`. + +### `enable` + +Default `1` + +Enable IO4 port emulation. Disable to use the IO4 port on a real ALLS. + +### `test` +Default `0x31` + +Test button virtual-key code. Default is the 1 key. + +### `service` + +Default `0x32` + +Service button virtual-key code. Default is the 2 key. + +### `coin` + +Default `0x33` + +Keyboard button to increment coin counter. Default is the 3 key. + ## `[keychip]` Configure keychip emulation. @@ -283,7 +449,7 @@ Enable keychip emulation. Disable to use a real keychip. Default: `A69E-01A88888888` Keychip serial number. Keychip serials observed in the wild follow this -pattern: `A6xE-01Ayyyyyyyy`. +pattern: `A\d{2}(E|X)-(01|20)[ABCDU]\d{8}`. ### `gameId` @@ -298,18 +464,22 @@ Default: (Varies depending on game) Override the game's four-character platform code (e.g. `AAV2` for Nu 2). This is actually supposed to be a separate three-character `platformId` and -integer `modelType` setting, but they are combined here for convenience. Valid -values include: +integer `modelType` setting, but they are combined here for convenience. -- `AAV0`: Nu 1 (Project DIVA) -- `AAV1`: Nu 1.1 (Chunithm) -- `AAV2`: Nu 2 (Initial D Zero) -- `AAW0`: NuSX 1 -- `AAW1`: NuSX 1.1 -- `ACA0`: ALLS UX -- `ACA1`: ALLS HX -- `ACA2`: ALLS UX (without dedicated GPU) -- `ACA4`: ALLS MX +`platformId` is one of the following: +- `AAV`: Nu 1/1.1/2 +- `AAW`: NuSX 1/1.1 +- `ACA`: ALLS UX/HX/MX + +`modelType` is one of the following: +- `1`: Server (SV) +- `2`: Satalite (ST) +- `3`: Live (LV) +- `4`: Terminal (TN) + +It's safe to assume that every game you'll be playing with these tools will be a Satalite. +Some games care, others don't. Some even change how they run based on this value (Wonderland Wars +will boot into terminal mode if `modelType` is 4). ### `region` @@ -326,27 +496,37 @@ Bit values are: - 3: EXP: Export (for Asian markets) - 4: CHS: China (Simplified Chinese?) +### `billingCa` + +Default: `DEVICE\\ca.crt` + +Set the billing certificate path. This has to match the one used for the +SSL billing server. The DER certificate must fit in 1024 bytes so it must be +small. + +### `billingPub` + +Default: `DEVICE\\billing.pub` + +Set the actual keychip RSA public key path. This public key has to match the +private key `billing.key` of the billing server in order to decrypt/encrypt +the billing transactions. + ### `billingType` Default: `1` -Set the billing "type" for the keychip. The type determins what kind of revenue share, -if any, the game maker has with SEGA. Some games may be picky and require types other -then 1 (ex. Crossbeats requires billing type 2), so this option is provided if this -is an issue. Billing types are: - -- 0: No billing? -- 1: Billing type A -- 2: Billing type B1 -- 3: Billing type B2 +Sets the billing type, a single bit value that flags the cabinet as a rental if set to `1`, or `0` otherwise. +Certian games that were only sold officially as full purchases (that is, non-rentals) must have this value set to 0. +NOTE: Crossbeats erroniously displays Billing modes A/B1/B2, but this value comes from the `modelType` and NOT this value! ### `systemFlag` Default: `0x64` An 8-bit bitfield of unclear meaning. The least significant bit indicates a -developer dongle, I think? Changing this doesn't seem to have any effect on -anything other than Project DIVA. +developer dongle. Changing this doesn't seem to have any effect on +anything other than SEGA AM2 games. Other values observed in the wild: @@ -468,3 +648,19 @@ Default: Empty string Configure the location of the "Option" data mount point. This mount point is optional (hence the name, probably) and contains directories which contain minor over-the-air content updates. + +## `[epay]` + +Configure Thinca Payment (E-Money) emulation and hooks. + +### `enable` + +Default: `1` + +Enables the Thinca emulation. This will allow you to enable E-Money on compatible servers. + +### `hook` + +Default: `0` + +Enables hooking of respective Thinca DLL functions to emulate the existence of E-Money. This cannot be used with a real E-Money server. \ No newline at end of file diff --git a/doc/config/initiald.md b/doc/config/initiald.md index d7d6e60..42ec781 100644 --- a/doc/config/initiald.md +++ b/doc/config/initiald.md @@ -92,6 +92,16 @@ don't know the name of your input device, you can find it in the windows controller panel. The quickest way to access it is to press Win+R, then type in `joy.cpl` and look at the list it will display. +### `pedalsName` + +Default ` ` + +Name of the pedals to use (or any subset thereof). Leave blank if you do not +have separate pedals; aka the pedals are part of the wheel. The pedals will +be mapped to the `accelAxis` and `brakeAxis` which would normally be used by +the wheel defined under `deviceName`. The quickest way to access it is to press +Win+R, then type in `joy.cpl` and look at the list it will display. + ### `shifterName` Default ` ` diff --git a/doc/emoney.md b/doc/emoney.md new file mode 100644 index 0000000..05a6192 --- /dev/null +++ b/doc/emoney.md @@ -0,0 +1,213 @@ +# E-Money Authentication Procedure +by Haruka Akechi + +### SETTING UP + +1) Obtain the 64 byte long authentication card encryption key. `amdaemon.exe` holds the secrets. + +2) Inside the `emoney\` folder, install the python modules and launch the generator script: + +```shell +python -m pip install -r requirements.txt +python authcardgen.py --key +``` + +``` +Usage: authcardgen.py [OPTIONS] + +Options: + --cardid TEXT Card ID (64 hex characters) + --key TEXT Key (128 hex characters, required) [required] + --store-card-id TEXT Store Card ID (padded to 16 bytes) + --merchant-code TEXT Merchant Code (padded to 20 bytes) + --store-branch-id TEXT Store Branch ID (padded to 12 bytes) + --passphrase TEXT Passphrase, used for the pfx password (padded to 16 bytes) + --output TEXT Output filename + --help Show this message and exit. +``` + +3) Place the generated `authcard.bin` in your `DEVICE\` folder. + +4) Check `tfps-res-pro\env.json` for your game. If it contains a `"use_proxy": true` statement, add to segatools.ini: + +```ini +[aime] +proxy_flag=3 +``` + +5) Replace the two URLs in `tfps-res-pro\resource.xml` to your servers'. This is to ensure the Host header will match the certificate's. + +6) Where amdaemon.exe is located, there should be a `ca.pem`. Replace this file with either [this](https://curl.se/ca/cacert.pem) for the most common CA's (including Let's Encrypt), or whatever CA the server is using (your server will provide this). + +7) Run your game and enter the test menu, and navigate to E-Money Settings. + +8) Select "Terminal Authentication" + +9) Hold your key for scanning a card (default: RETURN) + +10) If your shop name shows up, everything was done succesfully. Otherwise, check the VFD. + +### TECHNICAL INFO + +For debugging anything e-money related, I highly recommend setting "emoney.log.level" to 4 in your game's amdaemon config.json. This should create a `\emoney_log\thincapayment.log`. + +When terminal authentication is started from the test menu, the game will check for an Aime reader of at least generation 3. If that is not fulfilled, the VFD will display "unsupported card reader" and abort. If the card reader is good, the VFD will prompt for a card to be touched, and the reader will start scanning for a MIFARE card, which from this point we call "Thinca Authentication Card". This card contains one unencrypted block (3) which contains: + +[0] = 0x54 // T + +[1] = 0x43 // C + +[2] = proxy_type + +[3] = 0x01 + +Afterwards, a number of encrypted blocks are read, namely the blocks 5,6,8,9,10,12,13 and 14. These blocks together form a 130 byte long binary blob that contains the authentication data. + +This data is encrypted as following: + +Given a fixed 0x40 byte long encryption key and a fixed 0x20 byte long static "card ID", both of which can be found in amdaemon: +XOR every byte of the encryption key with 0x1C. +Calculate a 0x20 bytes long HMAC-SHA-256 of the card ID with the XOR'ed encryption key as the key. +Calculate the needed IV by XORing the lower 0x10 bytes of the HMAC with the upper 0x10 bytes of the HMAC: + +``` +byte[] iv = new byte[16]; +for (int i = 0; i < 16; i++) { + iv[i] = (byte) (hmac[i + 16] ^ hmac[i]); +} +``` + +With this IV, and the HMAC as the key, finally encrypt the data with AES/CBC/PKCS5Padding. + +Now what is actually stored on such a card? This: + +``` ++---------------+---------------+-----------------+------------+----------+ +| Store Card ID | Merchant Code | Store Branch ID | Passphrase | NULL | ++---------------+---------------+-----------------+------------+----------+ +| 0x10 bytes | 0x14 bytes | 0xC bytes | 0x10 bytes | 0x1 byte | +| char* | char* | uint128_t | char* | NULL | ++---------------+---------------+-----------------+------------+----------+ +``` + +Only two things really matter here. The Store Branch ID must be non-zero, otherwise amdaemon will reject it, and the passphrase, which is the PFX key password (passphrase during authcard creation) for the certificate returned in the network response (see below). + +Technically with the Store Card ID you could bind different auth cards to different users, but for home usage, it really doesn't matter. + +That's the Thinca Authentication Card out of the way, so continue on to: + +### NETWORK + +First, a regular HTTP(S) connection will be made to the URL specified in tfps-res-pro\env.json, tasms.root_endpoint. + +Request Data: +`{"modelName":"ACA","serialNumber":"ACAE01A9999","merchantCode":"NOTSEGA","storeBranchNumber":11111,"storeCardId":"FAKESTORE"}` + +Note that the serialNumber here actually isn't the keychip ID, but rather the PCBID. The three other values are read from the Thinca Authentication Card. + +Response Headers: +`x-certificate-md5: ` + +Response Data: +``` +{ + "certificate": "", + "initSettings": { + "endpoints": { + "terminals": { + "uri": "https://localhost/emoney/terminals" + }, + "statuses": { + "uri": "https://localhost/emoney/statuses" + }, + "sales": { + "uri": "https://localhost/emoney/sales" + }, + "counters": { + "uri": "https://localhost/emoney/counters" + } + }, + "intervals": { + "checkSetting": 60, + "sendStatus": 60 + }, + "settigsType": "AmusementTerminalSettings", // sic + "status": "1", // a string + "terminalId": "536453645364536453645364536453", // must be exactly 30 characters + "version": "2024-01-01T01:01:01", // a timestamp + "availableElectronicMoney": [ + 1, + 2, + 3, + 5, + 6, + 8, + 9, + 91, // aimepay + 101 // "cash" ??? + ], + "cashAvailability": true, + "productCode": 1337 + } +} +``` + +Next up, we will connect to the TLAM service to get the URL for the TCAP service. Everything from this point on requires not only HTTPS, but also client certificate validation (which is the certificate returned from the previous request). Technically you don't need to validate it, but you must accept a client certificate or the client HTTPS library will not be happy. + +The client certificate itself must be signed with the same key than the server's HTTPS certificate. + +The client AND server certificate must have it's CA included in the "ca.pem" file in amdaemon's directory. You can freely replace this file with https://curl.se/ca/cacert.pem to allow Let's Encrypt and whatever else. + +At this point, "ThincaPayment::setClientCertificate(). ErrCode 203" means that the downloaded file couldn't be found, or the certificate password is wrong. + +[Warn ] TCAP communicate error 06514086 means the ca.pem has no entry for the given server CA. + +TLAM: +The TLAM url comes from tftp-res-pro\resource.xml, commonPrimaryUri. + +/initauth.jsp + +Request Data: + +Response Headers: +`Content-Type: application/x-tlam` +Response Data: +`SERV=https://localhost/emoney/tcap` + +TCAP: +Here be dragons. + +Is it this?? https://en.wikipedia.org/wiki/Transaction_Capabilities_Application_Part +Maybe thincatcapclient.dll holds the secrets? + +Request Data: +``` +02 05 01 00 ba 00 00 00 21 00 00 00 00 00 25 00 ....º... !.....%. +9f 00 01 00 00 07 47 65 6e 65 72 69 63 06 43 4c ......Ge neric.CL +49 45 4e 54 00 02 00 00 07 47 65 6e 65 72 69 63 IENT.... .Generic +06 53 54 41 54 55 53 00 03 00 00 07 47 65 6e 65 .STATUS. ....Gene +72 69 63 06 4f 50 54 49 4f 4e 00 04 00 00 06 46 ric.OPTI ON.....F +65 6c 69 43 61 03 52 2f 57 00 05 00 00 07 47 65 eliCa.R/ W.....Ge +6e 65 72 69 63 09 52 2f 57 5f 45 56 45 4e 54 00 neric.R/ W_EVENT. +06 00 00 07 47 65 6e 65 72 69 63 0a 52 2f 57 5f ....Gene ric.R/W_ +53 54 41 54 55 53 00 07 00 00 07 47 65 6e 65 72 STATUS.. ...Gener +69 63 0a 52 2f 57 5f 4f 50 54 49 4f 4e 00 08 00 ic.R/W_O PTION... +00 07 47 65 6e 65 72 69 63 06 4e 46 43 5f 52 57 ..Generi c.NFC_RW +00 00 00 26 00 03 02 05 00 00 00 00 22 00 00 ...&.... ....".. +``` + +and +``` +02 05 03 00 17 00 00 00 21 00 11 34 34 20 30 30 ........ !..44 00 +20 30 31 20 32 31 20 30 30 20 30 30 01 21 0 0 00 +``` + +Response Headers: +``` +Content-Type: application/x-tcap +Transfer-Encoding: chunked +``` +Response Data: +???? + +To be continued ... \ No newline at end of file diff --git a/doc/idzhook.md b/doc/idzhook.md index e23c18c..1686458 100644 --- a/doc/idzhook.md +++ b/doc/idzhook.md @@ -65,7 +65,7 @@ felicaGen=0 and fill it with 20 digits, for example `01234567891234567890`. Make sure to save the file. -1. Right click `start.bat` in `package` and run it as Administrator. After the +1. Right click `launch.bat` in `package` and run it as Administrator. After the first run you may be able to run the game as a normal user. 1. Once you're at the title screen, press 2 or 3 a few times to add some credits, then _hold_ the Enter key for a few seconds to scan a card and start diff --git a/docker-build.bat b/docker-build.bat index 1b0ce12..822dc9b 100644 --- a/docker-build.bat +++ b/docker-build.bat @@ -2,7 +2,6 @@ setlocal enabledelayedexpansion :: Static Environment Variables -set BUILD_OUTPUT_PATH=build\docker set IMAGE_NAME=djhackers/segatools-build:latest set CONTAINER_NAME=segatools-build @@ -19,7 +18,7 @@ if ERRORLEVEL 1 ( goto failure ) -docker image rm -f %IMAGE_NAME% +:: docker image rm -f %IMAGE_NAME% goto success diff --git a/emoney/authcardgen.py b/emoney/authcardgen.py new file mode 100644 index 0000000..ae821be --- /dev/null +++ b/emoney/authcardgen.py @@ -0,0 +1,131 @@ +import hmac +import hashlib +import click + +from Crypto.Cipher import AES +from Crypto.Util.Padding import pad, unpad +from pathlib import Path + + +class CardEncryptor: + def __init__( + self, + cardid_hex, + key_hex, + store_card_id_str="FAKESTORE", + merchant_code_str="NOTSEGA", + store_branch_id_str="11111", + passphrase_str="573", + output_file="authdata.bin", + ): + self.cardid = bytes.fromhex(cardid_hex) + self.key = bytearray.fromhex(key_hex) + self.output_file = output_file + + if len(self.cardid) != 0x20: + raise ValueError("Card ID must be 32 bytes (64 hex characters)") + if len(self.key) != 0x40: + raise ValueError("Key must be 64 bytes (128 hex characters)") + + # XOR the key with 0x1C as in original Java + for i in range(len(self.key)): + self.key[i] ^= 0x1C + + self.store_card_id = self._str_to_bytes(store_card_id_str, 0x10) + self.merchant_code = self._str_to_bytes(merchant_code_str, 0x14) + self.store_branch_id = self._str_to_bytes(store_branch_id_str, 0x0C) + self.passphrase = self._str_to_bytes(passphrase_str, 0x10) + + # Construct full data payload + self.data = ( + self.store_card_id + + self.merchant_code + + self.store_branch_id + + self.passphrase + + bytes([0x00]) # +1 null terminator / padding byte + ) + + def _str_to_bytes(self, s, length): + b = bytearray(length) + b[: len(s)] = s.encode() + return bytes(b) + + def _bytes_to_str(self, b): + return b.decode() + + def _bytes_to_hex(self, b): + return b.hex().upper() + + def _calculate_hmac_sha256(self, key, data, length): + h = hmac.new(key, data, hashlib.sha256) + return h.digest()[:length] + + def _aes_cbc_encrypt(self, key, data, iv): + cipher = AES.new(key, AES.MODE_CBC, iv) + return cipher.encrypt(pad(data, AES.block_size)) + + def _aes_cbc_decrypt(self, key, data, iv): + cipher = AES.new(key, AES.MODE_CBC, iv) + return unpad(cipher.decrypt(data), AES.block_size) + + def run(self): + print("Card ID:\t\t", self._bytes_to_hex(self.cardid)) + print("Store Card ID:\t\t", self._bytes_to_str(self.store_card_id)) + print("Merchant Code:\t\t", self._bytes_to_str(self.merchant_code)) + print("Store Branch ID:\t", self._bytes_to_str(self.store_branch_id)) + print("Passphrase:\t\t", self._bytes_to_str(self.passphrase)) + + hmac_output = self._calculate_hmac_sha256(self.key, self.cardid, 0x20) + # print("HMAC:\t\t", self._bytes_to_hex(hmac_output)) + + iv = bytes([hmac_output[i + 16] ^ hmac_output[i] for i in range(16)]) + # print("IV:\t\t", self._bytes_to_hex(iv)) + + encrypted = self._aes_cbc_encrypt(hmac_output, self.data, iv) + # print("ENCRYPTED:\t", self._bytes_to_hex(encrypted)) + Path(self.output_file).write_bytes(encrypted) + + decrypted = self._aes_cbc_decrypt(hmac_output, encrypted, iv) + # print("DECRYPTED:\t", self._bytes_to_hex(decrypted)) + + +@click.command() +@click.option( + "--cardid", + default="0102030401020304010203040102030401020304010203040102030401020304", + help="Card ID (64 hex characters)", +) +@click.option( + "--key", + required=True, + help="Key (128 hex characters, required)", +) +@click.option( + "--store-card-id", default="FAKESTORE", help="Store Card ID (padded to 16 bytes)" +) +@click.option( + "--merchant-code", default="NOTSEGA", help="Merchant Code (padded to 20 bytes)" +) +@click.option( + "--store-branch-id", default="11111", help="Store Branch ID (padded to 12 bytes)" +) +@click.option("--passphrase", default="573", help="Passphrase, used for the pfx password (padded to 16 bytes)") +@click.option("--output", default="authdata.bin", help="Output filename") +def cli(cardid, key, store_card_id, merchant_code, store_branch_id, passphrase, output): + if len(key) != 128 or not all(c in "0123456789abcdefABCDEF" for c in key): + raise click.BadParameter("The key must be a 128-character hexadecimal string.") + + encryptor = CardEncryptor( + cardid, + key, + store_card_id_str=store_card_id, + merchant_code_str=merchant_code, + store_branch_id_str=store_branch_id, + passphrase_str=passphrase, + output_file=output, + ) + encryptor.run() + + +if __name__ == "__main__": + cli() diff --git a/emoney/requirements.txt b/emoney/requirements.txt new file mode 100644 index 0000000..b21ba16 --- /dev/null +++ b/emoney/requirements.txt @@ -0,0 +1,2 @@ +click +pycryptodome diff --git a/carolhook/carol-dll.c b/games/carolhook/carol-dll.c similarity index 90% rename from carolhook/carol-dll.c rename to games/carolhook/carol-dll.c index 5cc8e98..ce3adf9 100644 --- a/carolhook/carol-dll.c +++ b/games/carolhook/carol-dll.c @@ -21,9 +21,18 @@ const struct dll_bind_sym carol_dll_syms[] = { }, { .sym = "carol_io_touch_init", .off = offsetof(struct carol_dll, touch_init), + }, { + .sym = "carol_io_ledbd_init", + .off = offsetof(struct carol_dll, ledbd_init), }, { .sym = "carol_io_controlbd_init", .off = offsetof(struct carol_dll, controlbd_init), + }, { + .sym = "carol_io_touch_start", + .off = offsetof(struct carol_dll, touch_start), + }, { + .sym = "carol_io_touch_stop", + .off = offsetof(struct carol_dll, touch_stop), } }; diff --git a/carolhook/carol-dll.h b/games/carolhook/carol-dll.h similarity index 80% rename from carolhook/carol-dll.h rename to games/carolhook/carol-dll.h index 095a1fd..9f8c7ff 100644 --- a/carolhook/carol-dll.h +++ b/games/carolhook/carol-dll.h @@ -10,7 +10,10 @@ struct carol_dll { void (*jvs_poll)(uint8_t *opbtn, uint8_t *beams); void (*jvs_read_coin_counter)(uint16_t *total); HRESULT (*touch_init)(); + HRESULT (*ledbd_init)(); HRESULT (*controlbd_init)(); + void (*touch_start)(carol_io_touch_callback_t callback); + void (*touch_stop)(); }; struct carol_dll_config { diff --git a/carolhook/carolhook.def b/games/carolhook/carolhook.def similarity index 85% rename from carolhook/carolhook.def rename to games/carolhook/carolhook.def index 3c324c4..5102937 100644 --- a/carolhook/carolhook.def +++ b/games/carolhook/carolhook.def @@ -16,4 +16,7 @@ EXPORTS carol_io_jvs_poll carol_io_jvs_read_coin_counter carol_io_touch_init + carol_io_ledbd_init carol_io_controlbd_init + carol_io_touch_start + carol_io_touch_stop diff --git a/carolhook/config.c b/games/carolhook/config.c similarity index 83% rename from carolhook/config.c rename to games/carolhook/config.c index 740a162..9f2ef2d 100644 --- a/carolhook/config.c +++ b/games/carolhook/config.c @@ -58,6 +58,20 @@ void controlbd_config_load( filename); } +void ledbd_config_load( + struct ledbd_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW( + L"ledbd", + L"enable", + 1, + filename); +} + void carol_hook_config_load( struct carol_hook_config *cfg, @@ -73,4 +87,5 @@ void carol_hook_config_load( gfx_config_load(&cfg->gfx, filename); touch_config_load(&cfg->touch, filename); controlbd_config_load(&cfg->controlbd, filename); + ledbd_config_load(&cfg->ledbd, filename); } diff --git a/carolhook/config.h b/games/carolhook/config.h similarity index 92% rename from carolhook/config.h rename to games/carolhook/config.h index 50b7f0b..31f4525 100644 --- a/carolhook/config.h +++ b/games/carolhook/config.h @@ -13,6 +13,7 @@ #include "gfxhook/gfx.h" #include "carolhook/touch.h" +#include "carolhook/ledbd.h" #include "carolhook/controlbd.h" struct carol_hook_config { @@ -22,6 +23,7 @@ struct carol_hook_config { struct carol_dll_config dll; struct gfx_config gfx; struct touch_config touch; + struct ledbd_config ledbd; struct controlbd_config controlbd; }; diff --git a/games/carolhook/controlbd.c b/games/carolhook/controlbd.c new file mode 100644 index 0000000..240e35b --- /dev/null +++ b/games/carolhook/controlbd.c @@ -0,0 +1,339 @@ +#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_any *dest, struct iobuf *src); +static HRESULT controlbd_set_header(struct controlbd_resp_hdr *resp, uint8_t cmd, uint8_t len); +static uint8_t calc_checksum(void *data, size_t len); + +static HRESULT controlbd_req_dispatch(const struct controlbd_req_any *req); +static HRESULT controlbd_req_ack_any(uint8_t cmd); +static HRESULT controlbd_req_reset(void); +static HRESULT controlbd_req_get_board_info(void); +static HRESULT controlbd_req_firmware_checksum(void); +static HRESULT controlbd_req_polling(const struct controlbd_req_any *req); + +const uint8_t CONTROLBD_SYNC_BYTE = 0xE0; + +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, 12); + 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_frame_decode(struct controlbd_req_any *req, struct iobuf *src) +{ + uint8_t data_len = 0; + uint8_t checksum_pos = src->pos - 1; + uint8_t calculated_checksum = 0; + uint8_t checksum = 0; + + if (src->pos < 6) { + dprintf("Control Board: Decode Error, request too short (pos is 0x%08X)\n", (int)src->pos); + return SEC_E_BUFFER_TOO_SMALL; + } + + req->hdr.sync = src->bytes[0]; + req->hdr.dest = src->bytes[1]; + req->hdr.src = src->bytes[2]; + req->hdr.len = src->bytes[3]; + req->hdr.cmd = src->bytes[4]; + data_len = req->hdr.len; + src->pos -= 5; + + for (int i = 0; i < data_len; i++) { + if (src->pos == 0) { + break; + } + req->bytes[i] = src->bytes[i + 5]; + src->pos --; + } + + checksum = src->bytes[checksum_pos]; + calculated_checksum = calc_checksum(req, checksum_pos); + + if (checksum != calculated_checksum) { + dprintf("Control Board: Decode Error, checksum failure (expected 0x%02X, got 0x%02X)\n", calculated_checksum, checksum); + return HRESULT_FROM_WIN32(ERROR_CRC); + } + + return S_OK; +} + +static HRESULT controlbd_handle_irp_locked(struct irp *irp) +{ + HRESULT hr; + struct controlbd_req_any req; + + 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: 0X%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 (controlbd_uart.written.bytes[0] == 0xE0) { +#if defined(LOG_CAROL_CONTROL_BD) + 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_req_dispatch(&req); + if (FAILED(hr)) { + dprintf("Control Board: Dispatch Error: 0X%X\n", (int) hr); + return hr; + } +#if defined(LOG_CAROL_CONTROL_BD) + dprintf("Control Board: RX Buffer:\n"); + dump_iobuf(&controlbd_uart.readable); +#endif + controlbd_uart.written.pos = 0; + return hr; + } + + // The board has a LPC111x bootloader that gets ran over every boot + // this is to account for that. + char cmd[255]; + strcpy_s(cmd, 255, (char *)controlbd_uart.written.bytes); + cmd[controlbd_uart.written.pos] = '\0'; + + if (!strcmp(cmd, "?")) { + dprintf("Control Board: Bootloader Hello\n"); + } + else if (!strcmp(cmd, "Synchronized\r\n")) { + iobuf_write(&controlbd_uart.readable, "Synchronized\r\nNG\r\n", 19); + // Set this to OK instead of NG to do an update + } + else if (!strcmp(cmd, "12000\r\n")) { + iobuf_write(&controlbd_uart.readable, "12000\r\nOK\r\n", 12); + } + else { + // Everything other then the two commands above just want 0\r\n + // appended to the request as the response. Given that it only checks sometimes, + // it's safe to just run over the readable buffer every response. + controlbd_uart.readable.pos = 0; + cmd[controlbd_uart.written.pos] = '0'; + cmd[controlbd_uart.written.pos + 1] = '\r'; + cmd[controlbd_uart.written.pos + 2] = '\n'; + cmd[controlbd_uart.written.pos + 3] = '\0'; + // dprintf("Control Board: Return %s\n", cmd); + iobuf_write(&controlbd_uart.readable, cmd, controlbd_uart.written.pos + 3); + } + + controlbd_uart.written.pos = 0; + return hr; + } +} + +static HRESULT controlbd_req_dispatch(const struct controlbd_req_any *req) +{ + switch (req->hdr.cmd) { + case CONTROLBD_CMD_RESET: + return controlbd_req_reset(); + + case CONTROLBD_CMD_BDINFO: + return controlbd_req_get_board_info(); + + case CONTROLBD_CMD_FIRM_SUM: + return controlbd_req_firmware_checksum(); + + case CONTROLBD_CMD_TIMEOUT: + dprintf("Control Board: Acknowledge Timeout\n"); + return controlbd_req_ack_any(req->hdr.cmd); + + case CONTROLBD_CMD_PORT_SETTING: + dprintf("Control Board: Acknowledge Port Setting\n"); + return controlbd_req_ack_any(req->hdr.cmd); + + case CONTROLBD_CMD_INITIALIZE: + dprintf("Control Board: Acknowledge Initialize\n"); + return controlbd_req_ack_any(req->hdr.cmd); + + case CONTROLBD_CMD_POLLING: + return controlbd_req_polling(req); + + default: + dprintf("Unhandled command 0x%02x\n", req->hdr.cmd); + return controlbd_req_ack_any(req->hdr.cmd); + } +} + +static HRESULT controlbd_set_header(struct controlbd_resp_hdr *resp, uint8_t cmd, uint8_t len) +{ + resp->sync = CONTROLBD_SYNC_BYTE; + resp->dest = 0x01; + resp->src = 0x02; + resp->len = len; + resp->report = 0x01; + resp->cmd = cmd; + return S_OK; +} + +static uint8_t calc_checksum(void *data, size_t len) +{ + uint8_t *stuff; + stuff = data; + uint8_t checksum = 0; + uint16_t tmp = 0; + + for (int i = 1; i < len; i++) { + tmp = checksum + stuff[i]; + checksum = tmp & 0xFF; + } + + return checksum; +} + +static HRESULT controlbd_req_reset(void) +{ + struct controlbd_resp_reset resp; + + dprintf("Control Board: Reset\n"); + + controlbd_set_header(&resp.hdr, CONTROLBD_CMD_RESET, 2); + resp.checksum = 0; + + // No data, just ack + resp.checksum = calc_checksum(&resp, sizeof(resp)); + return iobuf_write(&controlbd_uart.readable, &resp, sizeof(resp)); + +} + +static HRESULT controlbd_req_get_board_info(void) +{ + struct controlbd_resp_bdinfo resp; + memset(&resp, 0, sizeof(resp)); + dprintf("Control Board: Get Board Info\n"); + controlbd_set_header(&resp.hdr, CONTROLBD_CMD_BDINFO, 21); + + resp.rev = 0x90; + resp.bfr_size = 0x0001; + resp.ack = 1; + + strcpy_s(resp.bd_no, sizeof(resp.bd_no), "15312 "); + strcpy_s(resp.chip_no, sizeof(resp.chip_no), "6699 "); + resp.chip_no[5] = 0xFF; + resp.bd_no[8] = 0x0A; + + resp.checksum = calc_checksum(&resp, sizeof(resp)); + + return iobuf_write(&controlbd_uart.readable, &resp, sizeof(resp)); +} + +static HRESULT controlbd_req_firmware_checksum(void) +{ + struct controlbd_resp_fw_checksum resp; + memset(&resp, 0, sizeof(resp)); + dprintf("Control Board: Get Firmware Checksum\n"); + controlbd_set_header(&resp.hdr, CONTROLBD_CMD_FIRM_SUM, 5); + + resp.ack = 1; + resp.fw_checksum = 0x1b36; // This could change with an update... oh well + resp.checksum = calc_checksum(&resp, sizeof(resp)); + + return iobuf_write(&controlbd_uart.readable, &resp, sizeof(resp)); +} + +static HRESULT controlbd_req_polling(const struct controlbd_req_any *req) +{ + struct controlbd_req_polling req_struct; + memset(&req_struct, 0, sizeof(req_struct)); + memcpy_s(&req_struct, sizeof(req_struct), req, sizeof(req_struct)); + struct controlbd_resp_polling resp; + memset(&resp, 0, sizeof(resp)); + controlbd_set_header(&resp.hdr, CONTROLBD_CMD_POLLING, 16); + // TODO: Figure out output (pen vibration, etc) + + resp.ack = 1; + resp.unk7 = 3; + resp.unk8 = 1; + resp.unk9 = 1; + + resp.btns_pressed = 0; // bit 1 is pen button, bit 2 is dodge + resp.coord_x = 0x0; + resp.coord_y = 0x0; + resp.checksum = calc_checksum(&resp, sizeof(resp)); + + return iobuf_write(&controlbd_uart.readable, &resp, sizeof(resp)); +} + +static HRESULT controlbd_req_ack_any(uint8_t cmd) +{ + struct controlbd_resp_any_ack resp; + memset(&resp, 0, sizeof(resp)); + controlbd_set_header(&resp.hdr, cmd, 3); + + resp.ack = 1; + resp.checksum = calc_checksum(&resp, sizeof(resp)); + + return iobuf_write(&controlbd_uart.readable, &resp, sizeof(resp)); +} diff --git a/games/carolhook/controlbd.h b/games/carolhook/controlbd.h new file mode 100644 index 0000000..5f160d3 --- /dev/null +++ b/games/carolhook/controlbd.h @@ -0,0 +1,148 @@ +#pragma once +#include +#include +#include + +#include +#include + +struct controlbd_config { + bool enable; +}; + +#pragma pack(push, 1) +struct controlbd_req_hdr { + uint8_t sync; + uint8_t dest; // unsure + uint8_t src; // unsure + uint8_t len; // length of the rest of the request minus checksum + uint8_t cmd; +}; + +struct controlbd_req_any { + struct controlbd_req_hdr hdr; + uint8_t bytes[255]; +}; + +struct controlbd_req_reset { + struct controlbd_req_hdr hdr; + uint8_t payload; + uint8_t checksum; +}; + +struct controlbd_req_bdinfo { + struct controlbd_req_hdr hdr; + uint8_t checksum; +}; + +struct controlbd_req_fw_checksum { + struct controlbd_req_hdr hdr; + uint8_t checksum; +}; + +struct controlbd_req_timeout { + struct controlbd_req_hdr hdr; + uint8_t unknown; + uint8_t checksum; +}; + +struct controlbd_req_polling { + struct controlbd_req_hdr hdr; + uint8_t unknown[20]; + uint8_t checksum; +}; + +struct controlbd_resp_hdr { + uint8_t sync; + uint8_t dest; // unsure + uint8_t src; // unsure + uint8_t len; // length of the rest of the request minus checksum + uint8_t report; // 0x01 for success, anything else for failure + uint8_t cmd; +}; + +struct controlbd_resp_any { + struct controlbd_resp_hdr hdr; + uint8_t bytes[255]; +}; + +struct controlbd_resp_any_ack { + struct controlbd_resp_hdr hdr; + uint8_t ack; + uint8_t checksum; +}; + +struct controlbd_resp_reset { + struct controlbd_resp_hdr hdr; + uint8_t checksum; +}; + +struct controlbd_resp_bdinfo { + struct controlbd_resp_hdr hdr; + uint8_t ack; + char bd_no[9]; + char chip_no[6]; + uint8_t rev; + uint16_t bfr_size; + uint8_t checksum; +}; + +struct controlbd_resp_fw_checksum { + struct controlbd_resp_hdr hdr; + uint8_t ack; + uint16_t fw_checksum; + uint8_t checksum; +}; + +struct controlbd_resp_polling { + struct controlbd_resp_hdr hdr; + uint8_t ack; + uint8_t unk7; + uint8_t unk8; + uint8_t unk9; + uint8_t btns_pressed; + uint8_t blob[7]; + int8_t coord_x; + int8_t coord_y; + uint8_t checksum; +}; + +enum { + PEN_BTN_PRESSED_BIT = 1, + DODGE_BTN_PRESSED_BIT = 2 +}; + +enum { + CONTROLBD_CMD_RESET = 0x10, + CONTROLBD_CMD_TIMEOUT = 0x11, + CONTROLBD_CMD_RETRY = 0x12, + + CONTROLBD_CMD_GETIN = 0x20, + CONTROLBD_CMD_GETADI = 0x21, + + CONTROLBD_CMD_SETOUTPUT = 0x30, + + CONTROLBD_CMD_INITIALIZE = 0x80, + CONTROLBD_CMD_POLLING = 0x81, + CONTROLBD_CMD_CUSTOM_PATTERN = 0x82, + CONTROLBD_CMD_DEBUG_CAROL = 0x83, + CONTROLBD_CMD_POLLING_GENERAL = 0x84, + + CONTROLBD_CMD_CMD_STATUS = 0x90, + CONTROLBD_CMD_PORT_SETTING = 0x91, + CONTROLBD_CMD_PWM_DUTY = 0x92, + CONTROLBD_CMD_LED_SET = 0x93, + CONTROLBD_CMD_LED_REFRESH = 0x94, + + CONTROLBD_CMD_DEBUG_UART = 0xB0, + CONTROLBD_CMD_DEBUG_I2C = 0xB1, + + CONTROLBD_CMD_DEBUG_STATUS = 0xC0, + + CONTROLBD_CMD_BDINFO = 0xF0, + CONTROLBD_CMD_FIRM_SUM = 0xF2, + CONTROLBD_CMD_PROTOCOL = 0xF3, + CONTROLBD_CMD_UPDATE = 0xFE, +}; +#pragma pack(pop) +HRESULT controlbd_hook_init(const struct controlbd_config *cfg); \ No newline at end of file diff --git a/carolhook/dllmain.c b/games/carolhook/dllmain.c similarity index 51% rename from carolhook/dllmain.c rename to games/carolhook/dllmain.c index bb664d1..35fc7f0 100644 --- a/carolhook/dllmain.c +++ b/games/carolhook/dllmain.c @@ -1,3 +1,31 @@ +/* + "Wonderland Wars" (carol*) hook + + Devices: + + JVS: 837-14572 "Type 3" I/O Board + + [Satellite] + + USB: "WinTouch" Controller Board + ^ (DIPSW2 ON, Version 5.xx.xx or above) + COM1: 3M Touch Systems 78-0011-2353-4 Touch Controller Board + ^ (DIPSW2 OFF) + COM10: TN32MSEC003S "Gen 1" Aime Reader + OR + 837-15286 "Gen 2" Aime Reader + ^ (Version 1.6x.xx or above) + COM11: 837-15070-02 LED Controller Board + COM12: 837-15312 Pen Controller I/O Board + + [Terminal] + + COM10: 837-15286 "Gen 2" Aime Reader + + *: SEGA's abbreviation for Lewis Carroll, author of Alice's Adventures in + Wonderland. +*/ + #include #include @@ -12,17 +40,20 @@ #include "carolhook/carol-dll.h" #include "carolhook/jvs.h" #include "carolhook/touch.h" +#include "carolhook/ledbd.h" #include "carolhook/controlbd.h" -#include "carolhook/serial.h" #include "hook/process.h" #include "hooklib/serial.h" #include "hooklib/spike.h" +#include "hooklib/createprocess.h" +#include "hooklib/cursor.h" #include "platform/platform.h" #include "util/dprintf.h" +#include "util/env.h" static HMODULE carol_hook_mod; static process_entry_t carol_startup; @@ -30,21 +61,45 @@ static struct carol_hook_config carol_hook_cfg; /* COM Layout -01:(?) Touchscreen +01: Touchscreen 10: Aime reader -11: Control board -12(?): LED Board +11: LED board +12: Control Board */ static DWORD CALLBACK carol_pre_startup(void) { HRESULT hr; + HMODULE d3dc; + HMODULE dbghelp; dprintf("--- Begin carol_pre_startup ---\n"); + + /* Pin the D3D shader compiler. This makes startup much faster. */ + + d3dc = LoadLibraryW(L"D3DCompiler_43.dll"); + + if (d3dc != NULL) { + dprintf("Pinned shader compiler, hMod=%p\n", d3dc); + } else { + dprintf("Failed to load shader compiler!\n"); + } + + /* Pin dbghelp so the path hooks apply to it. */ + + dbghelp = LoadLibraryW(L"dbghelp.dll"); + + if (dbghelp != NULL) { + dprintf("Pinned debug helper library, hMod=%p\n", dbghelp); + } else { + dprintf("Failed to load debug helper library!\n"); + } + + cursor_hook_init(); /* Config load */ - carol_hook_config_load(&carol_hook_cfg, L".\\segatools.ini"); + carol_hook_config_load(&carol_hook_cfg, get_config_path()); /* Hook Win32 APIs */ @@ -74,16 +129,14 @@ static DWORD CALLBACK carol_pre_startup(void) goto fail; } - hr = sg_reader_hook_init(&carol_hook_cfg.aime, 10, carol_hook_mod); + hr = sg_reader_hook_init(&carol_hook_cfg.aime, 10, 1, 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(); - + gfx_d3d9_hook_init(&carol_hook_cfg.gfx, carol_hook_mod); hr = touch_hook_init(&carol_hook_cfg.touch); @@ -91,15 +144,26 @@ static DWORD CALLBACK carol_pre_startup(void) goto fail; } + hr = ledbd_hook_init(&carol_hook_cfg.ledbd); + + if (FAILED(hr)) { + goto fail; + } + hr = controlbd_hook_init(&carol_hook_cfg.controlbd); if (FAILED(hr)) { goto fail; } - + + hr = createprocess_push_hook_a(".\\15312firm\\firmupdate_1113.exe", "inject -d -k carolhook.dll ", NULL, false); + + if (FAILED(hr)) { + goto fail; + } /* Initialize debug helpers */ - spike_hook_init(L".\\segatools.ini"); + spike_hook_init(get_config_path()); dprintf("--- End carol_pre_startup ---\n"); diff --git a/carolhook/jvs.c b/games/carolhook/jvs.c similarity index 100% rename from carolhook/jvs.c rename to games/carolhook/jvs.c diff --git a/carolhook/jvs.h b/games/carolhook/jvs.h similarity index 100% rename from carolhook/jvs.h rename to games/carolhook/jvs.h diff --git a/games/carolhook/ledbd.c b/games/carolhook/ledbd.c new file mode 100644 index 0000000..58de42a --- /dev/null +++ b/games/carolhook/ledbd.c @@ -0,0 +1,168 @@ +#include + +#include +#include +#include +#include + +#include "hook/iobuf.h" +#include "hook/iohook.h" + +#include "carolhook/carol-dll.h" +#include "carolhook/ledbd.h" + +#include "hooklib/uart.h" + +#include "util/dprintf.h" +#include "util/dump.h" + +#include "board/slider-frame.h" +#include "board/slider-cmd.h" + +static HRESULT ledbd_handle_irp(struct irp *irp); +static HRESULT ledbd_handle_irp_locked(struct irp *irp); +static HRESULT ledbd_frame_dispatch(const union slider_req_any *dest); + +static HRESULT ledbd_req_noop(uint8_t cmd); +static HRESULT ledbd_req_unk7c(uint8_t cmd); +static HRESULT ledbd_req_unkF0(uint8_t cmd); + +static CRITICAL_SECTION ledbd_lock; +static struct uart ledbd_uart; +static uint8_t ledbd_written_bytes[520]; +static uint8_t ledbd_readable_bytes[520]; + +HRESULT ledbd_hook_init(const struct ledbd_config *cfg) +{ + if (!cfg->enable) { + return S_OK; + } + + InitializeCriticalSection(&ledbd_lock); + + uart_init(&ledbd_uart, 11); + ledbd_uart.written.bytes = ledbd_written_bytes; + ledbd_uart.written.nbytes = sizeof(ledbd_written_bytes); + ledbd_uart.readable.bytes = ledbd_readable_bytes; + ledbd_uart.readable.nbytes = sizeof(ledbd_readable_bytes); + + dprintf("LED Board: Init\n"); + + return iohook_push_handler(ledbd_handle_irp); +} + +static HRESULT ledbd_handle_irp(struct irp *irp) +{ + HRESULT hr; + + assert(irp != NULL); + + if (!uart_match_irp(&ledbd_uart, irp)) { + return iohook_invoke_next(irp); + } + + EnterCriticalSection(&ledbd_lock); + hr = ledbd_handle_irp_locked(irp); + LeaveCriticalSection(&ledbd_lock); + + return hr; +} + +static HRESULT ledbd_handle_irp_locked(struct irp *irp) +{ + union slider_req_any req; + struct iobuf req_iobuf; + HRESULT hr; + + assert(carol_dll.ledbd_init != NULL); + + if (irp->op == IRP_OP_OPEN) { + dprintf("LED Board: Starting backend DLL\n"); + hr = carol_dll.ledbd_init(); + + if (FAILED(hr)) { + dprintf("LED Board: Backend DLL error: 0X%X\n", (int) hr); + + return hr; + } + } + + hr = uart_handle_irp(&ledbd_uart, irp); + + if (FAILED(hr) || irp->op != IRP_OP_WRITE) { + return hr; + } + + for (;;) { +#if defined(LOG_CAROL_LED_BD) + dprintf("LED Board: TX Buffer:\n"); + dump_iobuf(&ledbd_uart.written); +#endif + req_iobuf.bytes = req.bytes; + req_iobuf.nbytes = sizeof(req.bytes); + req_iobuf.pos = 0; + + hr = slider_frame_decode(&req_iobuf, &ledbd_uart.written); + + if (FAILED(hr)) { + dprintf("LED Board: Deframe Error: 0X%X\n", (int) hr); + + return hr; + } + + hr = ledbd_frame_dispatch(&req); + if (FAILED(hr)) { + dprintf("LED Board: Dispatch Error: 0X%X\n", (int) hr); + + return hr; + } + + return hr; + } +} + +static HRESULT ledbd_frame_dispatch(const union slider_req_any *req) +{ + switch (req->hdr.cmd) { + case LEDBD_CMD_UNK_10: + return ledbd_req_noop(req->hdr.cmd); + case LEDBD_CMD_UNK_7C: + return ledbd_req_unk7c(req->hdr.cmd); + case LEDBD_CMD_UNK_F0: + return ledbd_req_unkF0(req->hdr.cmd); + case LEDBD_CMD_UNK_30: + return ledbd_req_noop(req->hdr.cmd); + default: + //dprintf("Unhandled command 0x%02X\n", req->cmd); + return ledbd_req_noop(req->hdr.cmd); + } +} + +static HRESULT ledbd_req_noop(uint8_t cmd) +{ + //dprintf("LED Board: Noop cmd 0x%02X\n", cmd); + uint8_t resp[] = { 0xE0, 0x01, 0x11, 0x03, 0x01, 0x00, 0x01, 0x17 }; + resp[5] = cmd; + resp[7] = 0x17 + cmd; + iobuf_write(&ledbd_uart.readable, resp, 8); + + return S_OK; +} + +static HRESULT ledbd_req_unk7c(uint8_t cmd) +{ + //dprintf("LED Board: Cmd 0x7C\n"); + uint8_t resp[] = { 0xE0, 0x01, 0x11, 0x04, 0x01, 0x7C, 0x01, 0x07, 0x9B }; + iobuf_write(&ledbd_uart.readable, resp, 9); + + return S_OK; +} + +static HRESULT ledbd_req_unkF0(uint8_t cmd) +{ + //dprintf("LED Board: Cmd 0xF0\n"); + uint8_t resp[] = { 0xE0, 0x01, 0x11, 0x0A, 0x01, 0xF0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E }; + iobuf_write(&ledbd_uart.readable, resp, 16); + + return S_OK; +} diff --git a/games/carolhook/ledbd.h b/games/carolhook/ledbd.h new file mode 100644 index 0000000..bbabf4c --- /dev/null +++ b/games/carolhook/ledbd.h @@ -0,0 +1,28 @@ +#pragma once +#include + +#include +#include + +struct ledbd_config { + bool enable; +}; + +enum ledbd_cmd { + LEDBD_CMD_UNK_10 = 0x10, + LEDBD_CMD_UNK_7C = 0x7C, + LEDBD_CMD_UNK_30 = 0x30, + LEDBD_CMD_UNK_F0 = 0xF0, +}; + +struct ledbd_req { + uint8_t sync; // Sync byte, always 0xE0 + uint8_t dest; // command destination id? + uint8_t src; // command source id? + uint8_t data_len; // length of the proceeding data bytes + uint8_t cmd; // might be the command byte? + uint8_t data[255]; // rest of the data, len = data_len - 1 + uint8_t checksum; // final byte is all bytes (excluding sync) added +}; + +HRESULT ledbd_hook_init(const struct ledbd_config *cfg); \ No newline at end of file diff --git a/carolhook/meson.build b/games/carolhook/meson.build similarity index 91% rename from carolhook/meson.build rename to games/carolhook/meson.build index ac4c3cd..bd7fed2 100644 --- a/carolhook/meson.build +++ b/games/carolhook/meson.build @@ -4,7 +4,6 @@ shared_library( 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'), @@ -32,7 +31,7 @@ shared_library( 'touch.h', 'controlbd.c', 'controlbd.h', - 'serial.c', - 'serial.h', + 'ledbd.c', + 'ledbd.h', ], ) diff --git a/games/carolhook/touch.c b/games/carolhook/touch.c new file mode 100644 index 0000000..cf94355 --- /dev/null +++ b/games/carolhook/touch.c @@ -0,0 +1,253 @@ +#include + +#include +#include +#include +#include + +#include "carolhook/carol-dll.h" +#include "carolhook/touch.h" + +#include "hook/table.h" + +#include "hooklib/uart.h" + +#include "util/dprintf.h" +#include "util/dump.h" + +/** + * CMDS for touch thing + * CX -> Calibrate Extend, preform callibration + * MS -> Mode Stream, enters stream mode + * R -> Reset, resets the device + * RD -> Reset Default, Resets the device to factory + * Z -> Null, keepalive command + * NM -> Name, return the name of the device (not documented?) + * OI -> Output Identity, output unique device identity, SC followed by 4 characters + * UT -> Unit Type, returns controller unit type + status + */ + +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 HRESULT handle_touch_ack_cmd(const struct touch_req *req); +static HRESULT handle_touch_name_cmd(const struct touch_req *req); +static HRESULT handle_touch_id_cmd(const struct touch_req *req); +static HRESULT handle_touch_unit_type_cmd(const struct touch_req *req); +static void touch_scan_auto(const bool is_pressed, const uint16_t mouse_x, const uint16_t mouse_y); + +static CRITICAL_SECTION touch_lock; +static struct uart touch_uart; +static uint8_t touch_written_bytes[528]; +static uint8_t touch_readable_bytes[528]; +static bool should_stream = false; +static bool last_pressed; +static uint8_t last_x1; +static uint8_t last_x2; +static uint8_t last_y1; +static uint8_t last_y2; + + +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(); + carol_dll.touch_start(touch_scan_auto); + + 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 defined(LOG_CAROL_TOUCH) + 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; + } + + if (!strcmp("Z", (char *)req.cmd)) { + hr = handle_touch_ack_cmd(&req); + } + else if (!strcmp("OI", (char *)req.cmd)) { + hr = handle_touch_id_cmd(&req); + } + else if (!strcmp("UT", (char *)req.cmd)) { + hr = handle_touch_unit_type_cmd(&req); + } + else if (!strcmp("NM", (char *)req.cmd)) { + hr = handle_touch_name_cmd(&req); + } + else if (!strcmp("R", (char *)req.cmd)) { + carol_dll.touch_stop(); + dprintf("Touch: Reset\n"); + hr = handle_touch_ack_cmd(&req); + } + else { + dprintf("Touchscreen: Unhandled cmd %s\n", (char *)req.cmd); + hr = handle_touch_ack_cmd(&req); + } + + return hr; + } +} + +static HRESULT handle_touch_ack_cmd(const struct touch_req *req) +{ + return iobuf_write(&touch_uart.readable, "\0010\015", 3); +} + +static HRESULT handle_touch_name_cmd(const struct touch_req *req) +{ + dprintf("Touch: Get Name\n"); + return iobuf_write(&touch_uart.readable, "\001AD1000\015", 15); +} + +static HRESULT handle_touch_id_cmd(const struct touch_req *req) +{ + dprintf("Touch: Get ID\n"); + return iobuf_write(&touch_uart.readable, "\001AD1000\015", 8); +} + +static HRESULT handle_touch_unit_type_cmd(const struct touch_req *req) +{ + dprintf("Touch: Get Unit Type\n"); + return iobuf_write(&touch_uart.readable, "\001AD****00\015", 8); +} + +static void touch_scan_auto(const bool is_pressed, const uint16_t mouse_x, const uint16_t mouse_y) +{ + struct touch_auto_resp resp; + uint16_t tmp_x; + uint16_t tmp_y; + bool flg = false; + + memset(&resp, 0, sizeof(resp)); + resp.touches[0].status |= 1 << 7; + + if (is_pressed) { + resp.touches[0].status |= (1 << 7) | (1 << 6); + resp.touches[0].touch_id = 1; + tmp_x = mouse_x & 0x7FFF; + tmp_y = mouse_y & 0x7FFF; + + resp.touches[0].x1 = tmp_x & 0x7F; + resp.touches[0].x2 = (tmp_x >> 7) & 0x7F; + resp.touches[0].y1 = tmp_y & 0x7F; + resp.touches[0].y2 = (tmp_y >> 7) & 0x7F; + + flg = resp.touches[0].x1 != last_x1 || resp.touches[0].x2 != last_x2 || resp.touches[0].y1 != last_y1 || resp.touches[0].y2 != last_y2; + +#if 0 + if (flg) + dprintf("Touch: Mouse down! x %02X %02X y: %02X %02X\n", resp.touches[0].x1, resp.touches[0].x2, resp.touches[0].y1, resp.touches[0].y2); +#endif + + + last_x1 = resp.touches[0].x1; + last_x2 = resp.touches[0].x2; + last_y1 = resp.touches[0].y1; + last_y2 = resp.touches[0].y2; + + } else if (last_pressed) { + resp.touches[0].x1 = last_x1; + resp.touches[0].x2 = last_x2; + resp.touches[0].y1 = last_y1; + resp.touches[0].y2 = last_y2; + } + + last_pressed = is_pressed; + + EnterCriticalSection(&touch_lock); + iobuf_write(&touch_uart.readable, &resp, sizeof(resp)); + LeaveCriticalSection(&touch_lock); + +#if defined(LOG_CAROL_TOUCH) + dprintf("Touch: RX Buffer: (pos %08x)\n", (uint32_t)touch_uart.readable.pos); + dump_iobuf(&touch_uart.readable); +#endif +} + +/* 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->sync = iobuf->bytes[0]; + memset(dest->cmd, 0, sizeof(dest->cmd)); + size_t data_len = 0; + + for (int i = 1; i < 255; i++) { + if (iobuf->bytes[i] == 0x0D) { break; } + dest->cmd[i - 1] = iobuf->bytes[i]; + data_len++; + } + + dest->tail = iobuf->bytes[data_len + 1]; + dest->data_len = data_len; + + iobuf->pos = 0; + + if (dest->sync != 1 || dest->tail != 0x0D) { + dprintf("Touch: Data recieve error, sync: 0x%02X (expected 0x01) tail: 0x%02X (expected 0x0D)", dest->sync, dest->tail); + return E_FAIL; + } + + return S_OK; +} diff --git a/games/carolhook/touch.h b/games/carolhook/touch.h new file mode 100644 index 0000000..bc621d6 --- /dev/null +++ b/games/carolhook/touch.h @@ -0,0 +1,45 @@ +#pragma once +#include + +#include +#include + +#pragma pack(push, 1) + +struct touch_config { + bool enable; + unsigned int port_no; + char board_id[7]; + char unit_type[9]; +}; + +// Always starts with 0x01, always ends with 0x0D +struct touch_req { + uint8_t sync; // Always 0x01 + uint8_t cmd[256]; // rest of the data goes here + uint8_t tail; // Always 0x0D + size_t data_len; // length of data +}; + +struct touch_report { + uint8_t status; + uint8_t x1; + uint8_t x2; + uint8_t y1; + uint8_t y2; + uint8_t touch_id; +}; + +struct touch_auto_resp { + struct touch_report touches[10]; +}; + +enum { + TOUCH_MODE_STREAM = 0x01, + TOUCH_MODE_DOWN_UP = 0x02, + TOUCH_MODE_INACTIVE = 0x03, +}; + +#pragma pack(pop) + +HRESULT touch_hook_init(const struct touch_config *cfg); \ No newline at end of file diff --git a/games/carolio/carolio.c b/games/carolio/carolio.c new file mode 100644 index 0000000..7582489 --- /dev/null +++ b/games/carolio/carolio.c @@ -0,0 +1,182 @@ +#include + +#include +#include +#include +#include + +#include "carolio/carolio.h" +#include "carolio/config.h" +#include "util/dprintf.h" +#include "util/env.h" + +static unsigned int __stdcall carol_io_touch_thread_proc(void *ctx); + +static bool carol_io_coin; +static uint16_t carol_io_coins; +static struct carol_io_config carol_io_cfg; +static bool carol_io_touch_stop_flag; +static HANDLE carol_io_touch_thread; +static bool carol_io_window_focus = false; + +uint16_t carol_io_get_api_version(void) +{ + return 0x0100; +} + +HRESULT carol_io_jvs_init(void) +{ + carol_io_config_load(&carol_io_cfg, get_config_path()); + + 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_ledbd_init() +{ + return S_OK; +} + +HRESULT carol_io_controlbd_init() +{ + return S_OK; +} + +void carol_io_touch_start(carol_io_touch_callback_t callback) +{ + if (carol_io_touch_thread != NULL) { + return; + } + + carol_io_touch_stop_flag = false; + + carol_io_touch_thread = (HANDLE) _beginthreadex( + NULL, + 0, + carol_io_touch_thread_proc, + callback, + 0, + NULL + ); +} + +void carol_io_touch_stop() +{ + carol_io_touch_stop_flag = true; +} + +void check_fg_wind(void) +{ + HWND hwnd = GetForegroundWindow(); + wchar_t window_class[MAX_PATH]; + + /* Unlike every other game, we can't use GetWindowText here. Why? + + From MSDN: + + "If the window does not have a caption, the return value is a null + string. This behavior is by design. It allows applications to call + GetWindowText without becoming unresponsive if the process that owns + the target window is not responding. However, if the target window + is not responding and it belongs to the calling application, + GetWindowText will cause the calling application to become + unresponsive." + + Great, thanks Microsoft, very cool. Luckily Carol sets its class + name to the window title too so we can use that. */ + + GetClassNameW(hwnd, window_class, MAX_PATH); + + if (wcscmp(window_class, L"WONDER Master")) { + if (carol_io_window_focus) { + dprintf("Carol IO: Window focus lost\n"); + carol_io_window_focus = false; + } + } else if (!carol_io_window_focus) { + dprintf("Carol IO: Window focus regained\n"); + carol_io_window_focus = true; + } +} + +static unsigned int __stdcall carol_io_touch_thread_proc(void *ctx) +{ + carol_io_touch_callback_t callback; + bool mouse_is_down = false; + uint16_t mX = 0; + uint16_t mY = 0; + POINT lpPoint; + HWND hwnd; + + callback = ctx; + + while (!carol_io_touch_stop_flag) { + check_fg_wind(); + if (GetAsyncKeyState(VK_LBUTTON) & 0x8000) { + mouse_is_down = true; + if (GetCursorPos(&lpPoint)) { + hwnd = GetForegroundWindow(); + if (ScreenToClient(hwnd, &lpPoint)) { + if (lpPoint.x < 0) lpPoint.x = 0; + if (lpPoint.y < 0) lpPoint.y = 0; + mX = (uint16_t)lpPoint.x; + mY = (uint16_t)lpPoint.y; + } + } + } else { + mouse_is_down = false; + } + + callback(mouse_is_down, mX, mY); + Sleep(1); + } + + return 0; +} diff --git a/carolio/carolio.h b/games/carolio/carolio.h similarity index 84% rename from carolio/carolio.h rename to games/carolio/carolio.h index dbe1091..8b2b308 100644 --- a/carolio/carolio.h +++ b/games/carolio/carolio.h @@ -5,6 +5,8 @@ #include #include +typedef void (*carol_io_touch_callback_t)(const bool is_pressed, const uint16_t mouse_x, const uint16_t mouse_y); + /* 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 @@ -49,4 +51,10 @@ 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 +HRESULT carol_io_ledbd_init(); + +HRESULT carol_io_controlbd_init(); + +void carol_io_touch_start(carol_io_touch_callback_t callback); + +void carol_io_touch_stop(); \ No newline at end of file diff --git a/carolio/config.c b/games/carolio/config.c similarity index 67% rename from carolio/config.c rename to games/carolio/config.c index 151e909..fba56ad 100644 --- a/carolio/config.c +++ b/games/carolio/config.c @@ -14,7 +14,7 @@ void carol_io_config_load( 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); + cfg->vk_test = GetPrivateProfileIntW(L"io3", L"test", VK_F1, filename); + cfg->vk_service = GetPrivateProfileIntW(L"io3", L"service", VK_F2, filename); + cfg->vk_coin = GetPrivateProfileIntW(L"io3", L"coin", VK_F3, filename); } diff --git a/carolio/config.h b/games/carolio/config.h similarity index 100% rename from carolio/config.h rename to games/carolio/config.h diff --git a/carolio/meson.build b/games/carolio/meson.build similarity index 88% rename from carolio/meson.build rename to games/carolio/meson.build index dcb81ab..e7a7bb2 100644 --- a/carolio/meson.build +++ b/games/carolio/meson.build @@ -3,7 +3,6 @@ carolio_lib = static_library( name_prefix : '', include_directories : inc, implicit_include_directories : false, - c_pch : '../precompiled.h', sources : [ 'carolio.c', 'carolio.h', diff --git a/chunihook/chuni-dll.c b/games/chunihook/chuni-dll.c similarity index 72% rename from chunihook/chuni-dll.c rename to games/chunihook/chuni-dll.c index c98a9de..5867f66 100644 --- a/chunihook/chuni-dll.c +++ b/games/chunihook/chuni-dll.c @@ -30,9 +30,28 @@ const struct dll_bind_sym chuni_dll_syms[] = { }, { .sym = "chuni_io_slider_set_leds", .off = offsetof(struct chuni_dll, slider_set_leds), + }, { + .sym = "chuni_io_led_init", + .off = offsetof(struct chuni_dll, led_init), + }, { + .sym = "chuni_io_led_set_colors", + .off = offsetof(struct chuni_dll, led_set_leds), } }; +/* Helper function to determine upon dll_bind failure whether the required functions were found + NOTE: relies on symbols order declared above */ +static HRESULT has_enough_symbols(uint16_t version, uint8_t count) +{ + if ( version <= 0x0101 && count == 7 ) + return S_OK; + + if ( version >= 0x0102 && count == 9 ) + return S_OK; + + return E_FAIL; +} + struct chuni_dll chuni_dll; // Copypasta DLL binding and diagnostic message boilerplate. @@ -92,16 +111,24 @@ HRESULT chuni_dll_init(const struct chuni_dll_config *cfg, HINSTANCE self) } sym = chuni_dll_syms; + const struct dll_bind_sym *init_sym = &sym[0]; hr = dll_bind(&chuni_dll, src, &sym, _countof(chuni_dll_syms)); if (FAILED(hr)) { if (src != self) { - dprintf("Chunithm IO: Custom IO DLL does not provide function " - "\"%s\". Please contact your IO DLL's developer for " - "further assistance.\n", - sym->sym); + // Might still be ok depending on external dll API version + int bind_count = sym - init_sym; + if ( has_enough_symbols(chuni_dll.api_version, bind_count) == S_OK ) + { + hr = S_OK; + } else { + dprintf("Chunithm IO: Custom IO DLL does not provide function " + "\"%s\". Please contact your IO DLL's developer for " + "further assistance.\n", + sym->sym); - goto end; + goto end; + } } else { dprintf("Internal error: could not reflect \"%s\"\n", sym->sym); } diff --git a/chunihook/chuni-dll.h b/games/chunihook/chuni-dll.h similarity index 87% rename from chunihook/chuni-dll.h rename to games/chunihook/chuni-dll.h index ecd88ee..8341b48 100644 --- a/chunihook/chuni-dll.h +++ b/games/chunihook/chuni-dll.h @@ -13,6 +13,8 @@ struct chuni_dll { void (*slider_start)(chuni_io_slider_callback_t callback); void (*slider_stop)(void); void (*slider_set_leds)(const uint8_t *rgb); + HRESULT (*led_init)(void); + void (*led_set_leds)(uint8_t board, uint8_t *rgb); }; struct chuni_dll_config { diff --git a/chunihook/chunihook.def b/games/chunihook/chunihook.def similarity index 91% rename from chunihook/chunihook.def rename to games/chunihook/chunihook.def index 2a90ab6..517a10b 100644 --- a/chunihook/chunihook.def +++ b/games/chunihook/chunihook.def @@ -20,3 +20,5 @@ EXPORTS chuni_io_slider_set_leds chuni_io_slider_start chuni_io_slider_stop + chuni_io_led_init + chuni_io_led_set_colors diff --git a/games/chunihook/config.c b/games/chunihook/config.c new file mode 100644 index 0000000..d711fec --- /dev/null +++ b/games/chunihook/config.c @@ -0,0 +1,124 @@ +#include + +#include +#include +#include +#include + +#include "amex/amex.h" +#include "amex/config.h" + +#include "board/config.h" +#include "board/sg-reader.h" + +#include "chunihook/config.h" + +#include "gfxhook/config.h" + +#include "hooklib/config.h" + +#include "platform/config.h" +#include "platform/platform.h" + +void chuni_dll_config_load( + struct chuni_dll_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + GetPrivateProfileStringW( + L"chuniio", + L"path", + L"", + cfg->path, + _countof(cfg->path), + filename); +} + +void slider_config_load(struct slider_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"slider", L"enable", 1, filename); +} + +void led15093_config_load(struct led15093_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + wchar_t tmpstr[16]; + + memset(cfg->board_number, ' ', sizeof(cfg->board_number)); + memset(cfg->chip_number, ' ', sizeof(cfg->chip_number)); + memset(cfg->boot_chip_number, ' ', sizeof(cfg->boot_chip_number)); + + cfg->enable = GetPrivateProfileIntW(L"led15093", L"enable", 1, filename); + cfg->port_no[0] = GetPrivateProfileIntW(L"led15093", L"portNo1", 0, filename); + cfg->port_no[1] = GetPrivateProfileIntW(L"led15093", L"portNo2", 0, filename); + cfg->high_baudrate = GetPrivateProfileIntW(L"led15093", L"highBaud", 0, filename); + cfg->fw_ver = GetPrivateProfileIntW(L"led15093", L"fwVer", 0x90, filename); + cfg->fw_sum = GetPrivateProfileIntW(L"led15093", L"fwSum", 0xadf7, filename); + + GetPrivateProfileStringW( + L"led15093", + L"boardNumber", + L"15093-06", + tmpstr, + _countof(tmpstr), + filename); + + size_t n = wcstombs(cfg->board_number, tmpstr, sizeof(cfg->board_number)); + for (int i = n; i < sizeof(cfg->board_number); i++) + { + cfg->board_number[i] = ' '; + } + + GetPrivateProfileStringW( + L"led15093", + L"chipNumber", + L"6710 ", + tmpstr, + _countof(tmpstr), + filename); + + n = wcstombs(cfg->chip_number, tmpstr, sizeof(cfg->chip_number)); + for (int i = n; i < sizeof(cfg->chip_number); i++) + { + cfg->chip_number[i] = ' '; + } + + GetPrivateProfileStringW( + L"led15093", + L"bootChipNumber", + L"6709 ", + tmpstr, + _countof(tmpstr), + filename); + + n = wcstombs(cfg->boot_chip_number, tmpstr, sizeof(cfg->boot_chip_number)); + for (int i = n; i < sizeof(cfg->boot_chip_number); i++) + { + cfg->boot_chip_number[i] = ' '; + } +} + +void chuni_hook_config_load( + struct chuni_hook_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + memset(cfg, 0, sizeof(*cfg)); + + platform_config_load(&cfg->platform, filename); + amex_config_load(&cfg->amex, filename); + aime_config_load(&cfg->aime, filename); + gfx_config_load(&cfg->gfx, filename); + chuni_dll_config_load(&cfg->dll, filename); + slider_config_load(&cfg->slider, filename); + led15093_config_load(&cfg->led15093, filename); +} diff --git a/chunihook/config.h b/games/chunihook/config.h similarity index 91% rename from chunihook/config.h rename to games/chunihook/config.h index 7740822..8902b85 100644 --- a/chunihook/config.h +++ b/games/chunihook/config.h @@ -6,6 +6,7 @@ #include "amex/amex.h" #include "board/sg-reader.h" +#include "board/led15093.h" #include "chunihook/chuni-dll.h" #include "chunihook/slider.h" @@ -21,6 +22,7 @@ struct chuni_hook_config { struct gfx_config gfx; struct chuni_dll_config dll; struct slider_config slider; + struct led15093_config led15093; }; void chuni_dll_config_load( diff --git a/chunihook/dllmain.c b/games/chunihook/dllmain.c similarity index 73% rename from chunihook/dllmain.c rename to games/chunihook/dllmain.c index 5350eb9..a3fa8ab 100644 --- a/chunihook/dllmain.c +++ b/games/chunihook/dllmain.c @@ -1,9 +1,22 @@ +/* + "CHUNITHM" (chuni) hook + + Devices + + JVS: 837-14572 "Type 3" I/O Board + COM1: 837-15330 Ground Slider + COM10: 837-15093-06 LED Controller Board + COM11: 837-15093-06 LED Controller Board + COM12: TN32MSEC003S "Gen 1" Aime Reader +*/ + #include #include #include "amex/amex.h" +#include "board/led15093.h" #include "board/sg-reader.h" #include "chunihook/config.h" @@ -23,6 +36,7 @@ #include "platform/platform.h" #include "util/dprintf.h" +#include "util/env.h" static HMODULE chuni_hook_mod; static process_entry_t chuni_startup; @@ -58,7 +72,7 @@ static DWORD CALLBACK chuni_pre_startup(void) /* Config load */ - chuni_hook_config_load(&chuni_hook_cfg, L".\\segatools.ini"); + chuni_hook_config_load(&chuni_hook_cfg, get_config_path()); /* Hook Win32 APIs */ @@ -96,7 +110,20 @@ static DWORD CALLBACK chuni_pre_startup(void) goto fail; } - hr = sg_reader_hook_init(&chuni_hook_cfg.aime, 12, chuni_hook_mod); + if ( chuni_dll.led_init == NULL || chuni_dll.led_set_leds == NULL ) + { + dprintf("IO DLL doesn't support led_init/led_set_leds, cannot start LED15093 hook\n"); + } else { + unsigned int led_port_no[2] = {10, 11}; + hr = led15093_hook_init(&chuni_hook_cfg.led15093, + chuni_dll.led_init, chuni_dll.led_set_leds, led_port_no); + + if (FAILED(hr)) { + goto fail; + } + } + + hr = sg_reader_hook_init(&chuni_hook_cfg.aime, 12, 1, chuni_hook_mod); if (FAILED(hr)) { goto fail; @@ -104,7 +131,7 @@ static DWORD CALLBACK chuni_pre_startup(void) /* Initialize debug helpers */ - spike_hook_init(L".\\segatools.ini"); + spike_hook_init(get_config_path()); dprintf("--- End chuni_pre_startup ---\n"); diff --git a/chunihook/jvs.c b/games/chunihook/jvs.c similarity index 97% rename from chunihook/jvs.c rename to games/chunihook/jvs.c index 8c630d4..85c2076 100644 --- a/chunihook/jvs.c +++ b/games/chunihook/jvs.c @@ -101,13 +101,13 @@ static void chunithm_jvs_read_switches(void *ctx, struct io3_switch_state *out) out->p1 = 0x0000; out->p2 = 0x0000; - if (opbtn & 0x01) { + if (opbtn & CHUNI_IO_OPBTN_TEST) { out->system = 0x80; } else { out->system = 0x00; } - if (opbtn & 0x02) { + if (opbtn & CHUNI_IO_OPBTN_SERVICE) { out->p1 |= 0x4000; } diff --git a/chunihook/jvs.h b/games/chunihook/jvs.h similarity index 100% rename from chunihook/jvs.h rename to games/chunihook/jvs.h diff --git a/chunihook/meson.build b/games/chunihook/meson.build similarity index 95% rename from chunihook/meson.build rename to games/chunihook/meson.build index 3f4a35d..b758676 100644 --- a/chunihook/meson.build +++ b/games/chunihook/meson.build @@ -4,7 +4,6 @@ shared_library( include_directories : inc, implicit_include_directories : false, vs_module_defs : 'chunihook.def', - c_pch : '../precompiled.h', dependencies : [ capnhook.get_variable('hook_dep'), capnhook.get_variable('hooklib_dep'), diff --git a/chunihook/slider.c b/games/chunihook/slider.c similarity index 99% rename from chunihook/slider.c rename to games/chunihook/slider.c index 753a608..0a42cb3 100644 --- a/chunihook/slider.c +++ b/games/chunihook/slider.c @@ -98,7 +98,7 @@ static HRESULT slider_handle_irp_locked(struct irp *irp) } for (;;) { -#if 0 +#if defined(LOG_CHUNI_SLIDER) dprintf("TX Buffer:\n"); dump_iobuf(&slider_uart.written); #endif @@ -117,7 +117,7 @@ static HRESULT slider_handle_irp_locked(struct irp *irp) return hr; } -#if 0 +#if defined(LOG_CHUNI_SLIDER) dprintf("Deframe Buffer:\n"); dump_iobuf(&req_iobuf); #endif diff --git a/chunihook/slider.h b/games/chunihook/slider.h similarity index 100% rename from chunihook/slider.h rename to games/chunihook/slider.h diff --git a/games/chuniio/chu2to3.c b/games/chuniio/chu2to3.c new file mode 100644 index 0000000..550583b --- /dev/null +++ b/games/chuniio/chu2to3.c @@ -0,0 +1,358 @@ +#include + +#include +#include +#include +#include + +#include "chuniio/chu2to3.h" +#include "util/dprintf.h" + +// Check windows +#if _WIN32 || _WIN64 + #if _WIN64 + #define ENV64BIT + #else + #define ENV32BIT + #endif +#endif + +// Check GCC +#if __GNUC__ + #if __x86_64__ || __ppc64__ + #define ENV64BIT + #else + #define ENV32BIT + #endif +#endif + +/* chuniio.dll dynamic loading */ +HMODULE hinstLib; +typedef uint16_t (*chuni_io_get_api_version_t)(void); +typedef HRESULT (*chuni_io_jvs_init_t)(void); +typedef void (*chuni_io_jvs_poll_t)(uint8_t*, uint8_t*); +typedef void (*chuni_io_jvs_read_coin_counter_t)(uint16_t *); +typedef HRESULT (*chuni_io_slider_init_t)(void); +typedef void (*chuni_io_slider_set_leds_t)(const uint8_t *); +typedef void (*chuni_io_slider_start_t)(chuni_io_slider_callback_t); +typedef void (*chuni_io_slider_stop_t)(void); +typedef HRESULT (*chuni_io_led_init_t)(void); +typedef void (*chuni_io_led_set_colors_t)(uint8_t, uint8_t *); + +chuni_io_get_api_version_t _chuni_io_get_api_version; +chuni_io_jvs_init_t _chuni_io_jvs_init; +chuni_io_jvs_poll_t _chuni_io_jvs_poll; +chuni_io_jvs_read_coin_counter_t _chuni_io_jvs_read_coin_counter; +chuni_io_slider_init_t _chuni_io_slider_init; +chuni_io_slider_set_leds_t _chuni_io_slider_set_leds; +chuni_io_slider_start_t _chuni_io_slider_start; +chuni_io_slider_stop_t _chuni_io_slider_stop; +chuni_io_led_init_t _chuni_io_led_init; +chuni_io_led_set_colors_t _chuni_io_led_set_colors; + +/* SHMEM Handling */ +#define BUF_SIZE 1024 +#define SHMEM_WRITE(buf, size) CopyMemory((PVOID)g_pBuf, buf, size) +#define SHMEM_READ(buf, size) CopyMemory(buf,(PVOID)g_pBuf, size) +TCHAR g_shmem_name[]=TEXT("Local\\Chu2to3Shmem"); +HANDLE g_hMapFile; +LPVOID g_pBuf; + +#pragma pack(1) +typedef struct shared_data_s { + uint16_t coin_counter; + uint8_t opbtn; + uint8_t beams; + uint16_t version; +} shared_data_t; + +shared_data_t g_shared_data; + +bool shmem_create() +{ + g_hMapFile = CreateFileMapping( + INVALID_HANDLE_VALUE, // use paging file + NULL, // default security + PAGE_READWRITE, // read/write access + 0, // maximum object size (high-order DWORD) + BUF_SIZE, // maximum object size (low-order DWORD) + g_shmem_name); // name of mapping object + + if (g_hMapFile == NULL) + { + dprintf("shmem_create : Could not create file mapping object (%ld).\n", + GetLastError()); + return 0; + } + g_pBuf = MapViewOfFile(g_hMapFile, // handle to map object + FILE_MAP_ALL_ACCESS, // read/write permission + 0, + 0, + BUF_SIZE); + + if (g_pBuf == NULL) + { + dprintf("shmem_create : Could not map view of file (%ld).\n", + GetLastError()); + + CloseHandle(g_hMapFile); + + return 0; + } + + return 1; +} + +bool shmem_load() +{ + g_hMapFile = OpenFileMapping( + FILE_MAP_ALL_ACCESS, // read/write access + FALSE, // do not inherit the name + g_shmem_name); // name of mapping object + + if (g_hMapFile == NULL) + { + dprintf("shmem_load : Could not open file mapping object (%ld).\n", GetLastError()); + return 0; + } + + g_pBuf = MapViewOfFile(g_hMapFile, // handle to map object + FILE_MAP_ALL_ACCESS, // read/write permission + 0, + 0, + BUF_SIZE); + + if (g_pBuf == NULL) + { + dprintf("shmem_load : Could not map view of file (%ld).\n", GetLastError()); + CloseHandle(g_hMapFile); + return 0; + } + + dprintf("shmem_load : shmem loaded succesfully.\n"); + return 1; +} + +void shmem_free() +{ + UnmapViewOfFile(g_pBuf); + CloseHandle(g_hMapFile); +} + +/* jvs polling thread (to forward info to x64 dll) */ +static HANDLE jvs_poll_thread; +static bool jvs_poll_stop_flag; + +static unsigned int __stdcall jvs_poll_thread_proc(void *ctx) +{ + while (1) { + _chuni_io_jvs_read_coin_counter(&g_shared_data.coin_counter); + g_shared_data.opbtn = 0; + g_shared_data.beams = 0; + _chuni_io_jvs_poll(&g_shared_data.opbtn, &g_shared_data.beams); + SHMEM_WRITE(&g_shared_data, sizeof(shared_data_t)); + Sleep(1); + } + + return 0; +} + +uint16_t chu2to3_load_dll(const wchar_t *dllname) +{ + #if defined(ENV64BIT) + /* x64 must just open the shmem and do nothing else */ + int errcount = 0; + while (!shmem_load()) + { + if (errcount >= 10) + return -1; + Sleep(5000); + errcount++; + } + Sleep(1000); + return S_OK; + #endif + + /* this is the first function called so let's setup the chuniio forwarding */ + hinstLib = LoadLibraryW(dllname); + if (hinstLib == NULL) { + dprintf("ERROR: unable to load %S (error %ld)\n",dllname, GetLastError()); + return -1; + } + + _chuni_io_get_api_version = (chuni_io_get_api_version_t)GetProcAddress(hinstLib, "chuni_io_get_api_version"); + _chuni_io_jvs_init = (chuni_io_jvs_init_t)GetProcAddress(hinstLib, "chuni_io_jvs_init"); + _chuni_io_jvs_poll = (chuni_io_jvs_poll_t)GetProcAddress(hinstLib, "chuni_io_jvs_poll"); + _chuni_io_jvs_read_coin_counter = (chuni_io_jvs_read_coin_counter_t)GetProcAddress(hinstLib, "chuni_io_jvs_read_coin_counter"); + _chuni_io_slider_init = (chuni_io_slider_init_t)GetProcAddress(hinstLib, "chuni_io_slider_init"); + _chuni_io_slider_set_leds = (chuni_io_slider_set_leds_t)GetProcAddress(hinstLib, "chuni_io_slider_set_leds"); + _chuni_io_slider_start = (chuni_io_slider_start_t)GetProcAddress(hinstLib, "chuni_io_slider_start"); + _chuni_io_slider_stop = (chuni_io_slider_stop_t)GetProcAddress(hinstLib, "chuni_io_slider_stop"); + _chuni_io_led_init = (chuni_io_led_init_t)GetProcAddress(hinstLib, "chuni_io_led_init"); + _chuni_io_led_set_colors = (chuni_io_led_set_colors_t)GetProcAddress(hinstLib, "chuni_io_led_set_colors"); + + /* x86 has to create the shmem */ + if (!shmem_create()) + { + return -1; + } + + return 0; +} + +/* chuniio exports */ +uint16_t chu2to3_io_get_api_version(void) +{ + #if defined(ENV64BIT) + /* This might be called too soon so let's make sure x86 has time to write to the shmem */ + SHMEM_READ(&g_shared_data, sizeof(shared_data_t)); + int errcount = 0; + while (g_shared_data.version == 0) + { + if (errcount >= 3) + { + dprintf("CHU2TO3 X64: Couldn't retrieve api version from shmem, assuming 0x0100\n"); + return 0x0100; + } + Sleep(5000); + errcount++; + SHMEM_READ(&g_shared_data, sizeof(shared_data_t)); + } + dprintf("CHU2TO3 X64: api version is %04X\n", g_shared_data.version); + return g_shared_data.version; + #endif + + if ( _chuni_io_get_api_version == NULL ) + { + g_shared_data.version = 0x0100; + } + else + { + g_shared_data.version = _chuni_io_get_api_version(); + } + dprintf("CHU2TO3: api version is %04X\n", g_shared_data.version); + + SHMEM_WRITE(&g_shared_data, sizeof(shared_data_t)); + + return g_shared_data.version; +} + +HRESULT chu2to3_io_jvs_init(void) +{ + #if defined(ENV64BIT) + /* x86 only */ + return S_OK; + #endif + + _chuni_io_jvs_init(); + + /* start jvs poll thread now that jvs_init is done */ + if (jvs_poll_thread != NULL) { + return S_OK; + } + + jvs_poll_thread = (HANDLE) _beginthreadex(NULL, + 0, + jvs_poll_thread_proc, + NULL, + 0, + NULL); + return S_OK; +} + +void chu2to3_io_jvs_read_coin_counter(uint16_t *out) +{ + #if defined(ENV32BIT) + /* x86 can perform the call and update shmem (although this call never happens) */ + _chuni_io_jvs_read_coin_counter(&g_shared_data.coin_counter); + SHMEM_WRITE(&g_shared_data, sizeof(shared_data_t)); + return; + #endif + + /* x64 must read value from shmem and update arg */ + SHMEM_READ(&g_shared_data, sizeof(shared_data_t)); + if (out == NULL) { + return; + } + *out = g_shared_data.coin_counter; +} + +void chu2to3_io_jvs_poll(uint8_t *opbtn, uint8_t *beams) +{ + #if defined(ENV32BIT) + /* x86 can perform the call and update shmem (although this call never happens) */ + _chuni_io_jvs_poll(&g_shared_data.opbtn, &g_shared_data.beams); + SHMEM_WRITE(&g_shared_data, sizeof(shared_data_t)); + return; + #endif + + /* x64 must read value from shmem and update args */ + SHMEM_READ(&g_shared_data, sizeof(shared_data_t)); + *opbtn = g_shared_data.opbtn; + *beams = g_shared_data.beams; +} + +HRESULT chu2to3_io_slider_init(void) +{ + #if defined(ENV64BIT) + /* x86 only */ + return S_OK; + #endif + + return _chuni_io_slider_init(); +} + +void chu2to3_io_slider_start(chuni_io_slider_callback_t callback) +{ + #if defined(ENV64BIT) + /* x86 only */ + return; + #endif + + _chuni_io_slider_start(callback); +} + +void chu2to3_io_slider_stop(void) +{ + #if defined(ENV64BIT) + /* x86 only */ + return; + #endif + + _chuni_io_slider_stop(); +} + +void chu2to3_io_slider_set_leds(const uint8_t *rgb) +{ + #if defined(ENV64BIT) + /* x86 only */ + return; + #endif + + _chuni_io_slider_set_leds(rgb); +} + +HRESULT chu2to3_io_led_init(void) +{ + #if defined(ENV64BIT) + /* x86 only */ + return S_OK; + #endif + + if (_chuni_io_led_init != NULL) + return _chuni_io_led_init(); + return S_OK; +} + +void chu2to3_io_led_set_colors(uint8_t board, uint8_t *rgb) +{ + #if defined(ENV64BIT) + /* x86 only */ + return; + #endif + + if (_chuni_io_led_set_colors != NULL) + { + _chuni_io_led_set_colors(board, rgb); + } +} diff --git a/games/chuniio/chu2to3.h b/games/chuniio/chu2to3.h new file mode 100644 index 0000000..de6e711 --- /dev/null +++ b/games/chuniio/chu2to3.h @@ -0,0 +1,26 @@ +#pragma once + +/* + CHU2TO3 CUSTOM IO API + + This dll just mirrors chuniio dll binds but with a dynamic library loading and + a SHMEM system to let a single 32bit dll talk with x86 and x64 processes at once +*/ + +#include + +#include +#include + +uint16_t chu2to3_io_get_api_version(void); +HRESULT chu2to3_io_jvs_init(void); +void chu2to3_io_jvs_poll(uint8_t *opbtn, uint8_t *beams); +void chu2to3_io_jvs_read_coin_counter(uint16_t *total); +HRESULT chu2to3_io_slider_init(void); +typedef void (*chuni_io_slider_callback_t)(const uint8_t *state); +void chu2to3_io_slider_start(chuni_io_slider_callback_t callback); +void chu2to3_io_slider_stop(void); +void chu2to3_io_slider_set_leds(const uint8_t *rgb); +HRESULT chu2to3_io_led_init(void); +void chu2to3_io_led_set_colors(uint8_t board, uint8_t *rgb); +uint16_t chu2to3_load_dll(const wchar_t *dllname); diff --git a/games/chuniio/chuniio.c b/games/chuniio/chuniio.c new file mode 100644 index 0000000..a2c0336 --- /dev/null +++ b/games/chuniio/chuniio.c @@ -0,0 +1,193 @@ +#include + +#include +#include +#include +#include +#include + +#include "chuniio/chuniio.h" +#include "chuniio/config.h" +#include "chuniio/ledoutput.h" + +#include "util/dprintf.h" +#include "util/env.h" + +static unsigned int __stdcall chuni_io_slider_thread_proc(void *ctx); + +static bool chuni_io_coin; +static uint16_t chuni_io_coins; +static uint8_t chuni_io_hand_pos; +static HANDLE chuni_io_slider_thread; +static bool chuni_io_slider_stop_flag; +static struct chuni_io_config chuni_io_cfg; + +uint16_t chuni_io_get_api_version(void) +{ + return 0x0102; +} + +HRESULT chuni_io_jvs_init(void) +{ + chuni_io_config_load(&chuni_io_cfg, get_config_path()); + + led_init_mutex = CreateMutex( + NULL, // default security attributes + FALSE, // initially not owned + NULL); // unnamed mutex + + if (led_init_mutex == NULL) + { + return E_FAIL; + } + + return S_OK; +} + +void chuni_io_jvs_read_coin_counter(uint16_t *out) +{ + if (out == NULL) { + return; + } + + if (GetAsyncKeyState(chuni_io_cfg.vk_coin) & 0x8000) { + if (!chuni_io_coin) { + chuni_io_coin = true; + chuni_io_coins++; + } + } else { + chuni_io_coin = false; + } + + *out = chuni_io_coins; +} + +void chuni_io_jvs_poll(uint8_t *opbtn, uint8_t *beams) +{ + size_t i; + + if (GetAsyncKeyState(chuni_io_cfg.vk_test) & 0x8000) { + *opbtn |= CHUNI_IO_OPBTN_TEST; + } + + if (GetAsyncKeyState(chuni_io_cfg.vk_service) & 0x8000) { + *opbtn |= CHUNI_IO_OPBTN_SERVICE; + } + + if (chuni_io_cfg.vk_ir_emu) { + // Use emulated AIR + if (GetAsyncKeyState(chuni_io_cfg.vk_ir_emu)) { + if (chuni_io_hand_pos < 6) { + chuni_io_hand_pos++; + } + } else { + if (chuni_io_hand_pos > 0) { + chuni_io_hand_pos--; + } + } + + for (i = 0 ; i < 6 ; i++) { + if (chuni_io_hand_pos > i) { + *beams |= (1 << i); + } + } + } else { + // Use actual AIR + for (i = 0; i < 6; i++) { + if (GetAsyncKeyState(chuni_io_cfg.vk_ir[i]) & 0x8000) { + *beams |= (1 << i); + } else { + *beams &= ~(1 << i); + } + } + } +} + +HRESULT chuni_io_slider_init(void) +{ + return led_output_init(&chuni_io_cfg); // because of slider LEDs +} + +void chuni_io_slider_start(chuni_io_slider_callback_t callback) +{ + BOOL status; + + if (chuni_io_slider_thread != NULL) { + return; + } + + chuni_io_slider_thread = (HANDLE) _beginthreadex( + NULL, + 0, + chuni_io_slider_thread_proc, + callback, + 0, + NULL); +} + +void chuni_io_slider_stop(void) +{ + if (chuni_io_slider_thread == NULL) { + return; + } + + chuni_io_slider_stop_flag = true; + + WaitForSingleObject(chuni_io_slider_thread, INFINITE); + CloseHandle(chuni_io_slider_thread); + chuni_io_slider_thread = NULL; + chuni_io_slider_stop_flag = false; +} + +void chuni_io_slider_set_leds(const uint8_t *rgb) +{ + led_output_update(2, rgb); +} + +static unsigned int __stdcall chuni_io_slider_thread_proc(void *ctx) +{ + chuni_io_slider_callback_t callback; + uint8_t pressure[32]; + size_t i; + + callback = ctx; + + while (!chuni_io_slider_stop_flag) { + for (i = 0 ; i < _countof(pressure) ; i++) { + if (GetAsyncKeyState(chuni_io_cfg.vk_cell[i]) & 0x8000) { + pressure[i] = 128; + } else { + pressure[i] = 0; + } + } + + callback(pressure); + Sleep(1); + } + + return 0; +} + +HRESULT chuni_io_led_init(void) +{ + return led_output_init(&chuni_io_cfg); +} + +void chuni_io_led_set_colors(uint8_t board, uint8_t *rgb) +{ +#if 0 + if (board == 0) { + dprintf("CHUNI LED: Left Air 1: red: %d, green: %d, blue: %d\n", rgb[0x96], rgb[0x97], rgb[0x98]); + dprintf("CHUNI LED: Left Air 2: red: %d, green: %d, blue: %d\n", rgb[0x99], rgb[0x9A], rgb[0x9B]); + dprintf("CHUNI LED: Left Air 3: red: %d, green: %d, blue: %d\n", rgb[0x9C], rgb[0x9D], rgb[0x9E]); + } + else if (board == 1) + { + dprintf("CHUNI LED: Right Air 1: red: %d, green: %d, blue: %d\n", rgb[0xB4], rgb[0xB5], rgb[0xB6]); + dprintf("CHUNI LED: Right Air 2: red: %d, green: %d, blue: %d\n", rgb[0xB7], rgb[0xB8], rgb[0xB9]); + dprintf("CHUNI LED: Right Air 3: red: %d, green: %d, blue: %d\n", rgb[0xBA], rgb[0xBB], rgb[0xBC]); + } +#endif + + led_output_update(board, rgb); +} diff --git a/chuniio/chuniio.h b/games/chuniio/chuniio.h similarity index 75% rename from chuniio/chuniio.h rename to games/chuniio/chuniio.h index 2a24600..c13bff4 100644 --- a/chuniio/chuniio.h +++ b/games/chuniio/chuniio.h @@ -8,6 +8,7 @@ - 0x0100: Initial API version (assumed if chuni_io_get_api_version is not exported) - 0x0101: Fix IR beam mappings + - 0x0102: Add air tower led and billboard support */ #include @@ -15,6 +16,12 @@ #include #include +enum { + CHUNI_IO_OPBTN_TEST = 0x01, + CHUNI_IO_OPBTN_SERVICE = 0x02, + CHUNI_IO_OPBTN_COIN = 0x04, +}; + /* Get the version of the Chunithm 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 @@ -132,10 +139,46 @@ void chuni_io_slider_start(chuni_io_slider_callback_t callback); void chuni_io_slider_stop(void); /* Update the RGB lighting on the slider. A pointer to an array of 32 * 3 = 96 - bytes is supplied. The illuminated areas on the touch slider are some - combination of rectangular regions and dividing lines between these regions - but the exact mapping of this lighting control buffer is still TBD. + bytes is supplied, organized in BRG format. + The first set of bytes is the right-most slider key, and from there the bytes + alternate between the dividers and the keys until the left-most key. + There are 31 illuminated sections in total. Minimum API version: 0x0100 */ void chuni_io_slider_set_leds(const uint8_t *rgb); + +/* Initialize LED emulation. This function will be called before any + other chuni_io_led_*() function calls. + + 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: 0x0102 */ + +HRESULT chuni_io_led_init(void); + +/* Update the RGB LEDs. rgb is a pointer to an array of up to 63 * 3 = 189 bytes. + + Chunithm uses two chains/boards with WS2811 protocol (each logical led corresponds to 3 physical leds). + board 0 is on the left side and board 1 on the right side of the cab + + Board 0 has 53 LEDs: + [0]-[49]: snakes through left half of billboard (first column starts at top) + [50]-[52]: left side partition LEDs + + Board 1 has 63 LEDs: + [0]-[59]: right half of billboard (first column starts at bottom) + [60]-[62]: right side partition LEDs + + Board 2 is the slider and has 31 LEDs: + [0]-[31]: slider LEDs right to left BRG, alternating between keys and dividers + + Each rgb value is comprised of 3 bytes in R,G,B order + + NOTE: billboard strips have alternating direction (bottom to top, top to bottom, ...) + + Minimum API version: 0x0102 */ + +void chuni_io_led_set_colors(uint8_t board, uint8_t *rgb); diff --git a/games/chuniio/config.c b/games/chuniio/config.c new file mode 100644 index 0000000..a7b5273 --- /dev/null +++ b/games/chuniio/config.c @@ -0,0 +1,83 @@ +#include + +#include +#include +#include +#include +#include + +#include "chuniio/config.h" + +static const int chuni_io_default_cells[] = { + 'L', 'L', 'L', 'L', + 'K', 'K', 'K', 'K', + 'J', 'J', 'J', 'J', + 'H', 'H', 'H', 'H', + 'G', 'G', 'G', 'G', + 'F', 'F', 'F', 'F', + 'D', 'D', 'D', 'D', + 'S', 'S', 'S', 'S', +}; + +static const int chuni_io_default_ir[] = { + '4', '5', '6', '7', '8', '9' +}; + +void chuni_io_config_load( + struct chuni_io_config *cfg, + const wchar_t *filename) +{ + wchar_t key[16]; + int i; + wchar_t port_input[6]; + + assert(cfg != NULL); + assert(filename != NULL); + + // Technically it's io4 but leave this for compatibility with old configs. + cfg->vk_test = GetPrivateProfileIntW(L"io3", L"test", VK_F1, filename); + cfg->vk_service = GetPrivateProfileIntW(L"io3", L"service", VK_F2, filename); + cfg->vk_coin = GetPrivateProfileIntW(L"io3", L"coin", VK_F3, filename); + cfg->vk_ir_emu = GetPrivateProfileIntW(L"io3", L"ir", VK_SPACE, filename); + + for (i = 0 ; i < 6 ; i++) { + swprintf_s(key, _countof(key), L"ir%i", i + 1); + cfg->vk_ir[i] = GetPrivateProfileIntW( + L"ir", + key, + chuni_io_default_ir[i], + filename); + } + + for (i = 0 ; i < 32 ; i++) { + swprintf_s(key, _countof(key), L"cell%i", i + 1); + cfg->vk_cell[i] = GetPrivateProfileIntW( + L"slider", + key, + chuni_io_default_cells[i], + filename); + } + + cfg->cab_led_output_pipe = GetPrivateProfileIntW(L"led", L"cabLedOutputPipe", 1, filename); + cfg->cab_led_output_serial = GetPrivateProfileIntW(L"led", L"cabLedOutputSerial", 0, filename); + + cfg->controller_led_output_pipe = GetPrivateProfileIntW(L"led", L"controllerLedOutputPipe", 1, filename); + cfg->controller_led_output_serial = GetPrivateProfileIntW(L"led", L"controllerLedOutputSerial", 0, filename); + + cfg->controller_led_output_openithm = GetPrivateProfileIntW(L"led", L"controllerLedOutputOpeNITHM", 0, filename); + + cfg->led_serial_baud = GetPrivateProfileIntW(L"led", L"serialBaud", 921600, filename); + + GetPrivateProfileStringW( + L"led", + L"serialPort", + L"COM5", + port_input, + _countof(port_input), + filename); + + // Sanitize the output path. If it's a serial COM port, it needs to be prefixed + // with `\\.\`. + wcsncpy(cfg->led_serial_port, L"\\\\.\\", 4); + wcsncat_s(cfg->led_serial_port, 11, port_input, 6); +} diff --git a/games/chuniio/config.h b/games/chuniio/config.h new file mode 100644 index 0000000..eac6f7a --- /dev/null +++ b/games/chuniio/config.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include + +struct chuni_io_config { + uint8_t vk_test; + uint8_t vk_service; + uint8_t vk_coin; + uint8_t vk_ir_emu; + uint8_t vk_ir[6]; + uint8_t vk_cell[32]; + + // Which ways to output LED information are enabled + bool cab_led_output_pipe; + bool cab_led_output_serial; + + bool controller_led_output_pipe; + bool controller_led_output_serial; + + bool controller_led_output_openithm; + + // The name of a COM port to output LED data on, in serial mode + wchar_t led_serial_port[12]; + int32_t led_serial_baud; +}; + +void chuni_io_config_load( + struct chuni_io_config *cfg, + const wchar_t *filename); diff --git a/games/chuniio/leddata.h b/games/chuniio/leddata.h new file mode 100644 index 0000000..564455b --- /dev/null +++ b/games/chuniio/leddata.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +#define LED_PACKET_FRAMING 0xE0 +#define LED_PACKET_ESCAPE 0xD0 +#define LED_NUM_MAX 66 +#define LED_BOARDS_TOTAL 3 +#define LED_OUTPUT_HEADER_SIZE 2 +#define LED_OUTPUT_DATA_SIZE_MAX LED_NUM_MAX * 3 * 2 // max if every byte's escaped +#define LED_OUTPUT_TOTAL_SIZE_MAX LED_OUTPUT_HEADER_SIZE + LED_OUTPUT_DATA_SIZE_MAX + +// This struct is used to send data related to the slider and billboard LEDs +struct _chuni_led_data_buf_t { + uint8_t framing; // Sync byte + uint8_t board; // LED output the data is for (0-1: billboard, 2: slider) + uint8_t data[LED_OUTPUT_DATA_SIZE_MAX]; // Buffer for LEDs + uint8_t data_len; // How many bytes to output from the buffer +}; + +static uint8_t chuni_led_board_data_lens[LED_BOARDS_TOTAL] = {53*3, 63*3, 31*3}; \ No newline at end of file diff --git a/games/chuniio/ledoutput.c b/games/chuniio/ledoutput.c new file mode 100644 index 0000000..d4660b6 --- /dev/null +++ b/games/chuniio/ledoutput.c @@ -0,0 +1,137 @@ +#include + +#include +#include +#include + +#include "chuniio/config.h" +#include "chuniio/leddata.h" +#include "chuniio/ledoutput.h" +#include "chuniio/pipeimpl.h" +#include "chuniio/serialimpl.h" + +static struct _chuni_led_data_buf_t led_unescaped_buf[LED_BOARDS_TOTAL]; +static struct _chuni_led_data_buf_t led_escaped_buf[LED_BOARDS_TOTAL]; + +static bool led_output_is_init = false; +static struct chuni_io_config* config; +static bool any_outputs_enabled; + +HANDLE led_init_mutex; + +HRESULT led_output_init(struct chuni_io_config* const cfg) +{ + DWORD dwWaitResult = WaitForSingleObject(led_init_mutex, INFINITE); + if (dwWaitResult == WAIT_FAILED) + { + return HRESULT_FROM_WIN32(GetLastError()); + // return 1; + } + else if (dwWaitResult != WAIT_OBJECT_0) + { + return E_FAIL; + // return 1; + } + + if (!led_output_is_init) + { + config = cfg; + + // Setup the framing bytes for the packets + for (int i = 0; i < LED_BOARDS_TOTAL; i++) { + led_unescaped_buf[i].framing = LED_PACKET_FRAMING; + led_unescaped_buf[i].board = i; + led_unescaped_buf[i].data_len = chuni_led_board_data_lens[i]; + + led_escaped_buf[i].framing = LED_PACKET_FRAMING; + led_escaped_buf[i].board = i; + led_escaped_buf[i].data_len = chuni_led_board_data_lens[i]; + } + + any_outputs_enabled = config->cab_led_output_pipe || config->controller_led_output_pipe + || config->cab_led_output_serial || config->controller_led_output_serial; + + if (config->cab_led_output_pipe || config->controller_led_output_pipe) + { + led_pipe_init(); // don't really care about errors here tbh + } + + if (config->cab_led_output_serial || config->controller_led_output_serial) + { + led_serial_init(config->led_serial_port, config->led_serial_baud); + } + } + + led_output_is_init = true; + + ReleaseMutex(led_init_mutex); + return S_OK; + // return 0; +} + +struct _chuni_led_data_buf_t* escape_led_data(struct _chuni_led_data_buf_t* unescaped) +{ + struct _chuni_led_data_buf_t* out_struct = &led_escaped_buf[unescaped->board]; + + uint8_t* in_buf = unescaped->data; + uint8_t* out_buf = out_struct->data; + int i = 0; + int o = 0; + + while (i < unescaped->data_len) + { + uint8_t b = in_buf[i++]; + if (b == LED_PACKET_FRAMING || b == LED_PACKET_ESCAPE) + { + out_buf[o++] = LED_PACKET_ESCAPE; + b--; + } + out_buf[o++] = b; + } + + out_struct->data_len = o; + + return out_struct; +} + +void led_output_update(uint8_t board, const uint8_t* rgb) +{ + if (board < 0 || board > 2 || !any_outputs_enabled) + { + return; + } + + memcpy(led_unescaped_buf[board].data, rgb, led_unescaped_buf[board].data_len); + struct _chuni_led_data_buf_t* escaped_data = escape_led_data(&led_unescaped_buf[board]); + + if (board < 2) + { + // billboard (cab) + if (config->cab_led_output_pipe) + { + led_pipe_update(escaped_data); + } + + if (config->cab_led_output_serial) + { + led_serial_update(escaped_data); + } + } + else + { + // slider + if (config->controller_led_output_pipe) + { + led_pipe_update(escaped_data); + } + + if (config->controller_led_output_serial) + { + if (config->controller_led_output_openithm){ + led_serial_update_openithm(rgb); + } else { + led_serial_update(escaped_data); + } + } + } +} diff --git a/games/chuniio/ledoutput.h b/games/chuniio/ledoutput.h new file mode 100644 index 0000000..86642b9 --- /dev/null +++ b/games/chuniio/ledoutput.h @@ -0,0 +1,19 @@ +/* + LED output functions + + Credits: + somewhatlurker, skogaby +*/ + +#pragma once + +#include + +#include +#include + +#include "chuniio/config.h" + +extern HANDLE led_init_mutex; +HRESULT led_output_init(struct chuni_io_config* const cfg); +void led_output_update(uint8_t board, const uint8_t* rgb); diff --git a/games/chuniio/meson.build b/games/chuniio/meson.build new file mode 100644 index 0000000..eda00f0 --- /dev/null +++ b/games/chuniio/meson.build @@ -0,0 +1,22 @@ +chuniio_lib = static_library( + 'chuniio', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + + sources : [ + 'chu2to3.c', + 'chu2to3.h', + 'chuniio.c', + 'chuniio.h', + 'config.c', + 'config.h', + 'leddata.h', + 'ledoutput.c', + 'ledoutput.h', + 'pipeimpl.c', + 'pipeimpl.h', + 'serialimpl.c', + 'serialimpl.h' + ], +) diff --git a/games/chuniio/pipeimpl.c b/games/chuniio/pipeimpl.c new file mode 100644 index 0000000..972d84d --- /dev/null +++ b/games/chuniio/pipeimpl.c @@ -0,0 +1,160 @@ +#include + +#include +#include +#include + +#include "chuniio/leddata.h" +#include "chuniio/pipeimpl.h" + +static bool pipe_update[LED_BOARDS_TOTAL]; + +// incoming data is copied into these to ensure it isn't written during output +static struct _chuni_led_data_buf_t pipe_write_buf[LED_BOARDS_TOTAL]; +static HANDLE pipe_write_mutex; + +static HRESULT pipe_create(LPHANDLE hPipe, LPCWSTR lpszPipename, DWORD dwBufSize) +{ + *hPipe = INVALID_HANDLE_VALUE; + + *hPipe = CreateNamedPipeW( + lpszPipename, // pipe name + PIPE_ACCESS_OUTBOUND, // read/write access + PIPE_TYPE_BYTE | // byte type pipe + PIPE_WAIT, // blocking mode + PIPE_UNLIMITED_INSTANCES, // max. instances + dwBufSize, // output buffer size + 0, // input buffer size + 0, // client time-out + NULL); // default security attribute + + if (*hPipe == INVALID_HANDLE_VALUE) + { + return E_FAIL; + } + + return S_OK; +} + +static HRESULT pipe_write(HANDLE hPipe, LPCVOID lpBuffer, DWORD dwSize) +{ + DWORD cbWritten = 0; + + bool fSuccess = WriteFile( + hPipe, + lpBuffer, + dwSize, + &cbWritten, + NULL); + + if (!fSuccess || cbWritten != dwSize) + { + DWORD last_err = GetLastError(); + return (last_err == ERROR_BROKEN_PIPE) ? E_ABORT : E_FAIL; + } + + return S_OK; +} + +static unsigned int __stdcall chuni_io_led_pipe_thread_proc(void *ctx) +{ + HANDLE hPipe; + LPCWSTR lpszPipename = L"\\\\.\\pipe\\chuni_led"; + + while (true) + { + hPipe = INVALID_HANDLE_VALUE; + + if (pipe_create(&hPipe, lpszPipename, LED_OUTPUT_TOTAL_SIZE_MAX) != S_OK) + { + continue; + } + + // wait for a connection to the pipe + bool fConnected = ConnectNamedPipe(hPipe, NULL) ? + true : (GetLastError() == ERROR_PIPE_CONNECTED); + + while (fConnected) + { + if (WaitForSingleObject(pipe_write_mutex, INFINITE) != WAIT_OBJECT_0) + { + continue; + } + + for (int i = 0; i < LED_BOARDS_TOTAL; i++) { + if (pipe_update[i]) + { + HRESULT result = pipe_write( + hPipe, + &pipe_write_buf[i], + LED_OUTPUT_HEADER_SIZE + pipe_write_buf[i].data_len); + + if (result != S_OK) + { + //if (result == E_ABORT) + //{ + fConnected = false; + //} + break; + } + + pipe_update[i] = false; + } + } + + ReleaseMutex(pipe_write_mutex); + } + + FlushFileBuffers(hPipe); + DisconnectNamedPipe(hPipe); + CloseHandle(hPipe); + } + + return 0; +} + +HRESULT led_pipe_init() +{ + pipe_write_mutex = CreateMutex( + NULL, // default security attributes + FALSE, // initially not owned + NULL); // unnamed mutex + + if (pipe_write_mutex == NULL) + { + return E_FAIL; + } + + // clear out update bools + for (int i = 0; i < LED_BOARDS_TOTAL; i++) { + pipe_update[i] = false; + } + + _beginthreadex( + NULL, + 0, + chuni_io_led_pipe_thread_proc, + 0, + 0, + NULL); + + return S_OK; +} + +void led_pipe_update(struct _chuni_led_data_buf_t* data) +{ + if (data->board > 2) + { + return; + } + + if (WaitForSingleObject(pipe_write_mutex, INFINITE) != WAIT_OBJECT_0) + { + return; + } + + memcpy(&pipe_write_buf[data->board], data, sizeof(struct _chuni_led_data_buf_t)); + pipe_update[data->board] = true; + + ReleaseMutex(pipe_write_mutex); +} diff --git a/games/chuniio/pipeimpl.h b/games/chuniio/pipeimpl.h new file mode 100644 index 0000000..c95801f --- /dev/null +++ b/games/chuniio/pipeimpl.h @@ -0,0 +1,15 @@ +/* + Pipe implementation for chuniio + + Credits: + somewhatlurker, skogaby +*/ + +#pragma once + +#include + +#include "chuniio/leddata.h" + +HRESULT led_pipe_init(); +void led_pipe_update(struct _chuni_led_data_buf_t* data); diff --git a/games/chuniio/serialimpl.c b/games/chuniio/serialimpl.c new file mode 100644 index 0000000..a111f93 --- /dev/null +++ b/games/chuniio/serialimpl.c @@ -0,0 +1,123 @@ +#include + +#include +#include +#include + +#include "chuniio/leddata.h" +#include "chuniio/serialimpl.h" +#include "util/dprintf.h" + +static HANDLE serial_port; +static HANDLE serial_write_mutex; + +HRESULT led_serial_init(wchar_t led_com[12], DWORD baud) +{ + // Setup the serial communications + BOOL status; + + serial_port = CreateFileW(led_com, + GENERIC_READ | GENERIC_WRITE, + 0, + NULL, + OPEN_EXISTING, + 0, + NULL); + + if (serial_port == INVALID_HANDLE_VALUE) + dprintf("Chunithm Serial LEDs: Failed to open COM port (Attempted on %S)\n", led_com); + else + dprintf("Chunithm Serial LEDs: COM port success!\n"); + + DCB dcb_serial_params = { 0 }; + dcb_serial_params.DCBlength = sizeof(dcb_serial_params); + status = GetCommState(serial_port, &dcb_serial_params); + + dcb_serial_params.BaudRate = baud; + dcb_serial_params.ByteSize = 8; + dcb_serial_params.StopBits = ONESTOPBIT; + dcb_serial_params.Parity = NOPARITY; + SetCommState(serial_port, &dcb_serial_params); + + COMMTIMEOUTS timeouts = { 0 }; + timeouts.ReadIntervalTimeout = 50; + timeouts.ReadTotalTimeoutConstant = 50; + timeouts.ReadTotalTimeoutMultiplier = 10; + timeouts.WriteTotalTimeoutConstant = 50; + timeouts.WriteTotalTimeoutMultiplier = 10; + + SetCommTimeouts(serial_port, &timeouts); + + if (!status) + { + return E_FAIL; + } + + serial_write_mutex = CreateMutex( + NULL, // default security attributes + FALSE, // initially not owned + NULL); // unnamed mutex + + if (serial_write_mutex == NULL) + { + return E_FAIL; + } + + return S_OK; +} + +void led_serial_update(struct _chuni_led_data_buf_t* data) +{ + if (data->board > 2) + { + return; + } + + if (WaitForSingleObject(serial_write_mutex, INFINITE) != WAIT_OBJECT_0) + { + return; + } + + BOOL status = true; + DWORD bytes_written = 0; + + if (serial_port != INVALID_HANDLE_VALUE) { + status = WriteFile( + serial_port, + data, + LED_OUTPUT_HEADER_SIZE + data->data_len, + &bytes_written, + NULL); + } + + if (!status) { + DWORD last_err = GetLastError(); + // dprintf("Chunithm Serial LEDs: Serial port write failed -- %d\n", last_err); + } + + ReleaseMutex(serial_write_mutex); +} + +void led_serial_update_openithm(const uint8_t* rgb) +{ + if (serial_port != INVALID_HANDLE_VALUE) + { + char led_buffer[100]; + DWORD bytes_to_write; // No of bytes to write into the port + DWORD bytes_written = 0; // No of bytes written to the port + bytes_to_write = sizeof(led_buffer); + BOOL status; + + led_buffer[0] = 0xAA; + led_buffer[1] = 0xAA; + memcpy(led_buffer+2, rgb, sizeof(uint8_t) * 96); + led_buffer[98] = 0xDD; + led_buffer[99] = 0xDD; + + status = WriteFile(serial_port, // Handle to the Serial port + led_buffer, // Data to be written to the port + bytes_to_write, // No of bytes to write + &bytes_written, // Bytes written + NULL); + } +} diff --git a/games/chuniio/serialimpl.h b/games/chuniio/serialimpl.h new file mode 100644 index 0000000..b4b6f73 --- /dev/null +++ b/games/chuniio/serialimpl.h @@ -0,0 +1,17 @@ +/* + Serial LED implementation for chuniio + + Credits: + somewhatlurker, skogaby +*/ + +#pragma once + +#include +#include + +#include "chuniio/leddata.h" + +HRESULT led_serial_init(wchar_t led_com[12], DWORD baud); +void led_serial_update(struct _chuni_led_data_buf_t* data); +void led_serial_update_openithm(const uint8_t* rgb); \ No newline at end of file diff --git a/games/chusanhook/chuni-dll.c b/games/chusanhook/chuni-dll.c new file mode 100644 index 0000000..574cbe2 --- /dev/null +++ b/games/chusanhook/chuni-dll.c @@ -0,0 +1,190 @@ +#include + +#include +#include +#include + +#include "chuniio/chu2to3.h" +#include "chusanhook/chuni-dll.h" + +#include "util/dll-bind.h" +#include "util/dprintf.h" + +const struct dll_bind_sym chuni_dll_syms[] = { + { + .sym = "chuni_io_jvs_init", + .off = offsetof(struct chuni_dll, jvs_init), + }, { + .sym = "chuni_io_jvs_poll", + .off = offsetof(struct chuni_dll, jvs_poll), + }, { + .sym = "chuni_io_jvs_read_coin_counter", + .off = offsetof(struct chuni_dll, jvs_read_coin_counter), + }, { + .sym = "chuni_io_slider_init", + .off = offsetof(struct chuni_dll, slider_init), + }, { + .sym = "chuni_io_slider_start", + .off = offsetof(struct chuni_dll, slider_start), + }, { + .sym = "chuni_io_slider_stop", + .off = offsetof(struct chuni_dll, slider_stop), + }, { + .sym = "chuni_io_slider_set_leds", + .off = offsetof(struct chuni_dll, slider_set_leds), + }, { + .sym = "chuni_io_led_init", + .off = offsetof(struct chuni_dll, led_init), + }, { + .sym = "chuni_io_led_set_colors", + .off = offsetof(struct chuni_dll, led_set_leds), + } +}; + +const struct dll_bind_sym chu2to3_dll_syms[] = { + { + .sym = "chu2to3_io_jvs_init", + .off = offsetof(struct chuni_dll, jvs_init), + }, { + .sym = "chu2to3_io_jvs_poll", + .off = offsetof(struct chuni_dll, jvs_poll), + }, { + .sym = "chu2to3_io_jvs_read_coin_counter", + .off = offsetof(struct chuni_dll, jvs_read_coin_counter), + }, { + .sym = "chu2to3_io_slider_init", + .off = offsetof(struct chuni_dll, slider_init), + }, { + .sym = "chu2to3_io_slider_start", + .off = offsetof(struct chuni_dll, slider_start), + }, { + .sym = "chu2to3_io_slider_stop", + .off = offsetof(struct chuni_dll, slider_stop), + }, { + .sym = "chu2to3_io_slider_set_leds", + .off = offsetof(struct chuni_dll, slider_set_leds), + }, { + .sym = "chu2to3_io_led_init", + .off = offsetof(struct chuni_dll, led_init), + }, { + .sym = "chu2to3_io_led_set_colors", + .off = offsetof(struct chuni_dll, led_set_leds), + } +}; + +/* Helper function to determine upon dll_bind failure whether the required functions were found + NOTE: relies on symbols order declared above */ +static HRESULT has_enough_symbols(uint16_t version, uint8_t count) +{ + if ( version <= 0x0101 && count == 7 ) + return S_OK; + + if ( version >= 0x0102 && count == 9 ) + return S_OK; + + return E_FAIL; +} + +struct chuni_dll chuni_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 chuni_dll_init(const struct chuni_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); + + owned = NULL; + src = self; + + if (cfg->chu2to3) { + dprintf("Chunithm IO: using chu2to3 engine for IO DLL: %S\n", cfg->path); + } else if (cfg->path[0] != L'\0') { + owned = LoadLibraryW(cfg->path); + + if (owned == NULL) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("Chunithm IO: Failed to load IO DLL: %lx: %S\n", + hr, + cfg->path); + + goto end; + } + + dprintf("Chunithm IO: Using custom IO DLL: %S\n", cfg->path); + src = owned; + } + + if (cfg->chu2to3) { + if (chu2to3_load_dll(cfg->path) != 0) + dprintf("Could not init chu2to3 engine\n"); + + get_api_version = (void *) GetProcAddress(src, "chu2to3_io_get_api_version"); + } + else + { + get_api_version = (void *) GetProcAddress(src, "chuni_io_get_api_version"); + } + + if (get_api_version != NULL) { + chuni_dll.api_version = get_api_version(); + } else { + chuni_dll.api_version = 0x0100; + dprintf("Custom IO DLL does not expose chuni_io_get_api_version, " + "assuming API version 1.0.\n" + "Please ask the developer to update their DLL.\n"); + } + + if (chuni_dll.api_version >= 0x0200) { + hr = E_NOTIMPL; + dprintf("Chunithm IO: Custom IO DLL implements an unsupported " + "API version (%#04x). Please update Segatools.\n", + chuni_dll.api_version); + + goto end; + } + + sym = cfg->chu2to3 ? chu2to3_dll_syms : chuni_dll_syms; + const struct dll_bind_sym *init_sym = &sym[0]; + + hr = dll_bind(&chuni_dll, src, &sym, _countof(chuni_dll_syms)); + + if (FAILED(hr)) { + if (src != self) { + // Might still be ok depending on external dll API version + int bind_count = sym - init_sym; + if ( has_enough_symbols(chuni_dll.api_version, bind_count) == S_OK ) + { + hr = S_OK; + } else { + dprintf("Chunithm IO: Custom IO DLL does not provide function " + "\"%s\". Please contact your IO DLL's developer for " + "further assistance.\n", + sym->sym); + dprintf("imported %d symbols\n", bind_count); + 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/games/chusanhook/chuni-dll.h b/games/chusanhook/chuni-dll.h new file mode 100644 index 0000000..bfc415c --- /dev/null +++ b/games/chusanhook/chuni-dll.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include "chuniio/chuniio.h" + +struct chuni_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 (*slider_init)(void); + void (*slider_start)(chuni_io_slider_callback_t callback); + void (*slider_stop)(void); + void (*slider_set_leds)(const uint8_t *rgb); + HRESULT (*led_init)(void); + void (*led_set_leds)(uint8_t board, uint8_t *rgb); +}; + +struct chuni_dll_config { + wchar_t path[MAX_PATH]; + uint8_t chu2to3; +}; + +extern struct chuni_dll chuni_dll; + +HRESULT chuni_dll_init(const struct chuni_dll_config *cfg, HINSTANCE self); diff --git a/games/chusanhook/chusanhook.def b/games/chusanhook/chusanhook.def new file mode 100644 index 0000000..ea786f5 --- /dev/null +++ b/games/chusanhook/chusanhook.def @@ -0,0 +1,34 @@ +LIBRARY chusanhook + +EXPORTS + Direct3DCreate9 + aime_io_get_api_version + aime_io_init + aime_io_led_set_color + aime_io_nfc_get_aime_id + aime_io_nfc_get_felica_id + aime_io_nfc_poll + amDllVideoClose @2 + amDllVideoGetVBiosVersion @4 + amDllVideoOpen @1 + amDllVideoSetResolution @3 + chuni_io_get_api_version + chuni_io_jvs_init + chuni_io_jvs_poll + chuni_io_jvs_read_coin_counter + chuni_io_slider_init + chuni_io_slider_set_leds + chuni_io_slider_start + chuni_io_slider_stop + chuni_io_led_init + chuni_io_led_set_colors + chu2to3_io_get_api_version + chu2to3_io_jvs_init + chu2to3_io_jvs_poll + chu2to3_io_jvs_read_coin_counter + chu2to3_io_slider_init + chu2to3_io_slider_set_leds + chu2to3_io_slider_start + chu2to3_io_slider_stop + chu2to3_io_led_init + chu2to3_io_led_set_colors diff --git a/games/chusanhook/config.c b/games/chusanhook/config.c new file mode 100644 index 0000000..691e8f7 --- /dev/null +++ b/games/chusanhook/config.c @@ -0,0 +1,169 @@ +#include +#include +#include + +#include "board/config.h" + +#include "hooklib/config.h" +#include "hooklib/dvd.h" + +#include "gfxhook/config.h" + +#include "platform/config.h" + +#include "chusanhook/config.h" + +// Check windows +#if _WIN32 || _WIN64 + #if _WIN64 + #define ENV64BIT + #else + #define ENV32BIT + #endif +#endif + +// Check GCC +#if __GNUC__ + #if __x86_64__ || __ppc64__ + #define ENV64BIT + #else + #define ENV32BIT + #endif +#endif + +void chuni_dll_config_load( + struct chuni_dll_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + // Workaround for x64/x86 external IO dlls + // path32 for 32bit, path64 for 64bit + // path for 32bit only dlls (internal chu2to3 engine) + + GetPrivateProfileStringW( + L"chuniio", + L"path", + L"", + cfg->path, + _countof(cfg->path), + filename); + if (cfg->path[0] != L'\0') { + cfg->chu2to3 = 1; + } else { + cfg->chu2to3 = 0; + #if defined(ENV32BIT) + GetPrivateProfileStringW( + L"chuniio", + L"path32", + L"", + cfg->path, + _countof(cfg->path), + filename); + #elif defined(ENV64BIT) + GetPrivateProfileStringW( + L"chuniio", + L"path64", + L"", + cfg->path, + _countof(cfg->path), + filename); + #else + #error "Unknown environment" + #endif + } +} + +void slider_config_load(struct slider_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"slider", L"enable", 1, filename); +} + +void led15093_config_load(struct led15093_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + wchar_t tmpstr[16]; + bool cvt_port; + + memset(cfg->board_number, ' ', sizeof(cfg->board_number)); + memset(cfg->chip_number, ' ', sizeof(cfg->chip_number)); + memset(cfg->boot_chip_number, ' ', sizeof(cfg->boot_chip_number)); + + cfg->enable = GetPrivateProfileIntW(L"led15093", L"enable", 1, filename); + cfg->port_no[0] = GetPrivateProfileIntW(L"led15093", L"portNo1", 0, filename); + cfg->port_no[1] = GetPrivateProfileIntW(L"led15093", L"portNo2", 0, filename); + cfg->high_baudrate = GetPrivateProfileIntW(L"led15093", L"highBaud", 0, filename); + cfg->fw_ver = GetPrivateProfileIntW(L"led15093", L"fwVer", 0x90, filename); + cfg->fw_sum = GetPrivateProfileIntW(L"led15093", L"fwSum", 0xadf7, filename); + + GetPrivateProfileStringW( + L"led15093", + L"boardNumber", + L"15093-06", + tmpstr, + _countof(tmpstr), + filename); + + size_t n = wcstombs(cfg->board_number, tmpstr, sizeof(cfg->board_number)); + for (int i = n; i < sizeof(cfg->board_number); i++) + { + cfg->board_number[i] = ' '; + } + + GetPrivateProfileStringW( + L"led15093", + L"chipNumber", + L"6710 ", + tmpstr, + _countof(tmpstr), + filename); + + n = wcstombs(cfg->chip_number, tmpstr, sizeof(cfg->chip_number)); + for (int i = n; i < sizeof(cfg->chip_number); i++) + { + cfg->chip_number[i] = ' '; + } + + GetPrivateProfileStringW( + L"led15093", + L"bootChipNumber", + L"6709 ", + tmpstr, + _countof(tmpstr), + filename); + + n = wcstombs(cfg->boot_chip_number, tmpstr, sizeof(cfg->boot_chip_number)); + for (int i = n; i < sizeof(cfg->boot_chip_number); i++) + { + cfg->boot_chip_number[i] = ' '; + } +} + +void chusan_hook_config_load( + struct chusan_hook_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + memset(cfg, 0, sizeof(*cfg)); + + // Force load the 64bit Aime DLL instead of the 32bit one + cfg->aime.dll.path64 = true; + + platform_config_load(&cfg->platform, filename); + aime_config_load(&cfg->aime, filename); + dvd_config_load(&cfg->dvd, filename); + io4_config_load(&cfg->io4, filename); + gfx_config_load(&cfg->gfx, filename); + vfd_config_load(&cfg->vfd, filename); + chuni_dll_config_load(&cfg->dll, filename); + slider_config_load(&cfg->slider, filename); + led15093_config_load(&cfg->led15093, filename); +} diff --git a/games/chusanhook/config.h b/games/chusanhook/config.h new file mode 100644 index 0000000..e877be5 --- /dev/null +++ b/games/chusanhook/config.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include "board/config.h" +#include "board/led15093.h" + +#include "hooklib/dvd.h" + +#include "gfxhook/config.h" + +#include "platform/config.h" + +#include "chusanhook/chuni-dll.h" +#include "chusanhook/slider.h" + +struct chusan_hook_config { + struct platform_config platform; + struct aime_config aime; + struct dvd_config dvd; + struct io4_config io4; + struct gfx_config gfx; + struct vfd_config vfd; + struct chuni_dll_config dll; + struct slider_config slider; + struct led15093_config led15093; +}; + +void chuni_dll_config_load( + struct chuni_dll_config *cfg, + const wchar_t *filename); +void slider_config_load(struct slider_config *cfg, const wchar_t *filename); +void chusan_hook_config_load( + struct chusan_hook_config *cfg, + const wchar_t *filename); diff --git a/games/chusanhook/dllmain.c b/games/chusanhook/dllmain.c new file mode 100644 index 0000000..6ba4cd4 --- /dev/null +++ b/games/chusanhook/dllmain.c @@ -0,0 +1,202 @@ +/* + "CHUNITHM NEW" (chusan) hook + + Devices + + USB: 837-15257-02 "Type 4" I/O Board + COM1: 837-15330 Ground Slider + + [CVT mode (DIPSW2 ON)] + + COM2: 837-15093-06 LED Controller Board + COM3: 837-15093-06 LED Controller Board + COM4: 837-15286 "Gen 2" Aime Reader + + [SP mode (DIPSW2 OFF)] + + USB: 837-15067-02 USB Serial I/F Board + connected to + 837-15093-06 LED Controller Board (COM20) + 837-15093-06 LED Controller Board (COM21) + COM2: 200-6275 VFD GP1232A02A FUTABA Board + COM4: 837-15396 "Gen 3" Aime Reader +*/ + +#include +#include +#include + +#include "board/sg-reader.h" +#include "board/vfd.h" + +#include "chuniio/chuniio.h" +#include "chusanhook/config.h" +#include "chusanhook/io4.h" +#include "chusanhook/slider.h" + +#include "gfxhook/d3d9.h" +#include "gfxhook/gfx.h" + +#include "hook/process.h" + +#include "hooklib/serial.h" +#include "hooklib/spike.h" + +#include "platform/platform.h" + +#include "util/dprintf.h" +#include "util/env.h" + +static HMODULE chusan_hook_mod; +static process_entry_t chusan_startup; +static struct chusan_hook_config chusan_hook_cfg; + +static DWORD CALLBACK chusan_pre_startup(void) { + HMODULE d3dc; + HMODULE dbghelp; + HRESULT hr; + + dprintf("--- Begin chusan_pre_startup ---\n"); + + /* Pin the D3D shader compiler. This makes startup much faster. */ + + d3dc = LoadLibraryW(L"D3DCompiler_43.dll"); + + if (d3dc != NULL) { + dprintf("Pinned shader compiler, hMod=%p\n", d3dc); + } else { + dprintf("Failed to load shader compiler!\n"); + } + + /* Pin dbghelp so the path hooks apply to it. */ + + dbghelp = LoadLibraryW(L"dbghelp.dll"); + + if (dbghelp != NULL) { + dprintf("Pinned debug helper library, hMod=%p\n", dbghelp); + } else { + dprintf("Failed to load debug helper library!\n"); + } + + /* Config load */ + + chusan_hook_config_load(&chusan_hook_cfg, get_config_path()); + + /* Hook Win32 APIs */ + + dvd_hook_init(&chusan_hook_cfg.dvd, chusan_hook_mod); + gfx_hook_init(&chusan_hook_cfg.gfx); + gfx_d3d9_hook_init(&chusan_hook_cfg.gfx, chusan_hook_mod); + serial_hook_init(); + + /* Initialize emulation hooks */ + + struct dipsw_config new_dipsw_config[8] = { + {L"Delivery Server", L"Server", L"Client"}, + {L"Monitor Type", L"60FPS", L"120FPS"}, + {L"Cabinet Type", L"CVT", L"SP"}, + }; + + // Set the system dip switch configuration + memcpy(chusan_hook_cfg.platform.system.dipsw_config, new_dipsw_config, + sizeof(new_dipsw_config)); + + hr = platform_hook_init( + &chusan_hook_cfg.platform, + "SDHD", + "ACA2", + chusan_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = chuni_dll_init(&chusan_hook_cfg.dll, chusan_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = chusan_io4_hook_init(&chusan_hook_cfg.io4); + + if (FAILED(hr)) { + goto fail; + } + + hr = slider_hook_init(&chusan_hook_cfg.slider); + + if (FAILED(hr)) { + goto fail; + } + + bool is_cvt = chusan_hook_cfg.platform.system.dipsw[2]; + + if (!is_cvt) { + hr = vfd_hook_init(&chusan_hook_cfg.vfd, 2); + + if (FAILED(hr)) { + goto fail; + } + } + + unsigned int led_port_no[2]; + + if (is_cvt) { + led_port_no[0] = 2; + led_port_no[1] = 3; + } else { + led_port_no[0] = 20; + led_port_no[1] = 21; + } + + if (chuni_dll.led_init == NULL || chuni_dll.led_set_leds == NULL) { + dprintf( + "IO DLL doesn't support led_init/led_set_leds, cannot start " + "LED15093 hook\n"); + } else { + hr = led15093_hook_init(&chusan_hook_cfg.led15093, chuni_dll.led_init, + chuni_dll.led_set_leds, led_port_no); + + if (FAILED(hr)) { + goto fail; + } + } + + hr = sg_reader_hook_init(&chusan_hook_cfg.aime, 4, is_cvt ? 2 : 3, + chusan_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + /* Initialize debug helpers */ + + spike_hook_init(get_config_path()); + + dprintf("--- End chusan_pre_startup ---\n"); + + /* Jump to EXE start address */ + + return chusan_startup(); + +fail: + ExitProcess(EXIT_FAILURE); +} + +BOOL WINAPI DllMain(HMODULE mod, DWORD cause, void *ctx) { + HRESULT hr; + + if (cause != DLL_PROCESS_ATTACH) { + return TRUE; + } + + chusan_hook_mod = mod; + + hr = process_hijack_startup(chusan_pre_startup, &chusan_startup); + + if (!SUCCEEDED(hr)) { + dprintf("Failed to hijack process startup: %x\n", (int)hr); + } + + return SUCCEEDED(hr); +} diff --git a/games/chusanhook/io4.c b/games/chusanhook/io4.c new file mode 100644 index 0000000..c7f8265 --- /dev/null +++ b/games/chusanhook/io4.c @@ -0,0 +1,106 @@ +#include + +#include +#include +#include + +#include "board/io4.h" + +#include "chusanhook/chuni-dll.h" +#include "util/dprintf.h" + +struct chunithm_jvs_ir_mask { + uint16_t p1; + uint16_t p2; +}; + +// Incorrect IR beam mappings retained for backward compatibility +static const struct chunithm_jvs_ir_mask chunithm_jvs_ir_masks_v1[] = { + { 0, 1 << 13 }, + { 1 << 13, 0 }, + { 0, 1 << 12 }, + { 1 << 12, 0 }, + { 0, 1 << 11 }, + { 1 << 11, 0 }, +}; + +static const struct chunithm_jvs_ir_mask chunithm_jvs_ir_masks[] = { + { 1 << 13, 0 }, + { 0, 1 << 13 }, + { 1 << 12, 0 }, + { 0, 1 << 12 }, + { 1 << 11, 0 }, + { 0, 1 << 11 }, +}; + +static HRESULT chusan_io4_poll(void* ctx, struct io4_state* state); +static uint16_t coins; + +static const struct io4_ops chusan_io4_ops = { + .poll = chusan_io4_poll, +}; + +HRESULT chusan_io4_hook_init(const struct io4_config* cfg) +{ + HRESULT hr; + + assert(chuni_dll.jvs_init != NULL); + + dprintf("USB I/O: Starting IO backend\n"); + hr = chuni_dll.jvs_init(); + + if (FAILED(hr)) { + dprintf("USB I/O: Backend error, I/O disconnected: %x\n", (int)hr); + + return hr; + } + + io4_hook_init(cfg, &chusan_io4_ops, NULL); + + return S_OK; +} + +static HRESULT chusan_io4_poll(void* ctx, struct io4_state* state) +{ + const struct chunithm_jvs_ir_mask *masks; + uint8_t opbtn; + uint8_t beams; + size_t i; + + memset(state, 0, sizeof(*state)); + + opbtn = 0; + beams = 0; + + chuni_dll.jvs_poll(&opbtn, &beams); + chuni_dll.jvs_read_coin_counter(&coins); + + if (chuni_dll.api_version >= 0x0101) { + // Use correct mapping + masks = chunithm_jvs_ir_masks; + } else { + // Use backwards-compatible incorrect mapping + masks = chunithm_jvs_ir_masks_v1; + } + + if (opbtn & CHUNI_IO_OPBTN_TEST) { + state->buttons[0] |= IO4_BUTTON_TEST; + } + + if (opbtn & CHUNI_IO_OPBTN_SERVICE) { + state->buttons[0] |= IO4_BUTTON_SERVICE; + } + + // Update the coin counter with the value from jvs_read_coin_counter + state->chutes[0] = coins << 8; + + for (i = 0; i < 6; i++) { + /* Beam "press" is active-low hence the ~ */ + if (~beams & (1 << i)) { + state->buttons[0] |= masks[i].p1; + state->buttons[1] |= masks[i].p2; + } + } + + return S_OK; +} diff --git a/games/chusanhook/io4.h b/games/chusanhook/io4.h new file mode 100644 index 0000000..8d56491 --- /dev/null +++ b/games/chusanhook/io4.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +HRESULT chusan_io4_hook_init(const struct io4_config *cfg); diff --git a/games/chusanhook/meson.build b/games/chusanhook/meson.build new file mode 100644 index 0000000..f7c622f --- /dev/null +++ b/games/chusanhook/meson.build @@ -0,0 +1,31 @@ +shared_library( + 'chusanhook', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + vs_module_defs : 'chusanhook.def', + dependencies : [ + capnhook.get_variable('hook_dep'), + capnhook.get_variable('hooklib_dep'), + ], + link_with : [ + aimeio_lib, + board_lib, + chuniio_lib, + gfxhook_lib, + hooklib_lib, + platform_lib, + util_lib, + ], + sources : [ + 'chuni-dll.c', + 'chuni-dll.h', + 'config.c', + 'config.h', + 'dllmain.c', + 'io4.c', + 'io4.h', + 'slider.c', + 'slider.h', + ], +) diff --git a/games/chusanhook/slider.c b/games/chusanhook/slider.c new file mode 100644 index 0000000..2bd98d5 --- /dev/null +++ b/games/chusanhook/slider.c @@ -0,0 +1,249 @@ +#include + +#include +#include +#include +#include +#include + +#include "board/slider-cmd.h" +#include "board/slider-frame.h" + +#include "chusanhook/chuni-dll.h" +#include "chusanhook/slider.h" + +#include "hook/iobuf.h" +#include "hook/iohook.h" + +#include "hooklib/uart.h" + +#include "util/dprintf.h" +#include "util/dump.h" + +static HRESULT slider_handle_irp(struct irp *irp); +static HRESULT slider_handle_irp_locked(struct irp *irp); + +static HRESULT slider_req_dispatch(const union slider_req_any *req); +static HRESULT slider_req_reset(void); +static HRESULT slider_req_get_board_info(void); +static HRESULT slider_req_auto_scan_start(void); +static HRESULT slider_req_auto_scan_stop(void); +static HRESULT slider_req_set_led(const struct slider_req_set_led *req); + +static void slider_res_auto_scan(const uint8_t *state); + +static CRITICAL_SECTION slider_lock; +static struct uart slider_uart; +static uint8_t slider_written_bytes[520]; +static uint8_t slider_readable_bytes[520]; + +HRESULT slider_hook_init(const struct slider_config *cfg) +{ + assert(cfg != NULL); + assert(chuni_dll.slider_init != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + InitializeCriticalSection(&slider_lock); + + uart_init(&slider_uart, 1); + slider_uart.written.bytes = slider_written_bytes; + slider_uart.written.nbytes = sizeof(slider_written_bytes); + slider_uart.readable.bytes = slider_readable_bytes; + slider_uart.readable.nbytes = sizeof(slider_readable_bytes); + + return iohook_push_handler(slider_handle_irp); +} + +static HRESULT slider_handle_irp(struct irp *irp) +{ + HRESULT hr; + + assert(irp != NULL); + + if (!uart_match_irp(&slider_uart, irp)) { + return iohook_invoke_next(irp); + } + + EnterCriticalSection(&slider_lock); + hr = slider_handle_irp_locked(irp); + LeaveCriticalSection(&slider_lock); + + return hr; +} + +static HRESULT slider_handle_irp_locked(struct irp *irp) +{ + union slider_req_any req; + struct iobuf req_iobuf; + HRESULT hr; + + if (irp->op == IRP_OP_OPEN) { + dprintf("Chunithm slider: Starting backend\n"); + hr = chuni_dll.slider_init(); + + if (FAILED(hr)) { + dprintf("Chunithm slider: Backend error: %x\n", (int) hr); + + return hr; + } + } + + hr = uart_handle_irp(&slider_uart, irp); + + if (FAILED(hr) || irp->op != IRP_OP_WRITE) { + return hr; + } + + for (;;) { +#if defined(LOG_CHUSAN_SLIDER) + dprintf("TX Buffer:\n"); + dump_iobuf(&slider_uart.written); +#endif + + req_iobuf.bytes = req.bytes; + req_iobuf.nbytes = sizeof(req.bytes); + req_iobuf.pos = 0; + + hr = slider_frame_decode(&req_iobuf, &slider_uart.written); + + if (hr != S_OK) { + if (FAILED(hr)) { + dprintf("Chunithm slider: Deframe error: %x\n", (int) hr); + } + + return hr; + } + +#if defined(LOG_CHUSAN_SLIDER) + dprintf("Deframe Buffer:\n"); + dump_iobuf(&req_iobuf); +#endif + + hr = slider_req_dispatch(&req); + + if (FAILED(hr)) { + dprintf("Chunithm slider: Processing error: %x\n", (int) hr); + } + } +} + +static HRESULT slider_req_dispatch(const union slider_req_any *req) +{ + switch (req->hdr.cmd) { + case SLIDER_CMD_RESET: + return slider_req_reset(); + + case SLIDER_CMD_GET_BOARD_INFO: + return slider_req_get_board_info(); + + case SLIDER_CMD_SET_LED: + return slider_req_set_led(&req->set_led); + + case SLIDER_CMD_AUTO_SCAN_START: + return slider_req_auto_scan_start(); + + case SLIDER_CMD_AUTO_SCAN_STOP: + return slider_req_auto_scan_stop(); + + default: + dprintf("Unhandled command %02x\n", req->hdr.cmd); + + return S_OK; + } +} + +static HRESULT slider_req_reset(void) +{ + struct slider_hdr resp; + + dprintf("Chunithm slider: Reset\n"); + + resp.sync = 0xFF; + resp.cmd = SLIDER_CMD_RESET; + resp.nbytes = 0; + + return slider_frame_encode(&slider_uart.readable, &resp, sizeof(resp)); +} + +static HRESULT slider_req_get_board_info(void) +{ + struct slider_resp_get_board_info resp; + + dprintf("Chunithm slider: Get firmware version\n"); + + memset(&resp, 0, sizeof(resp)); + resp.hdr.sync = SLIDER_FRAME_SYNC; + resp.hdr.cmd = SLIDER_CMD_GET_BOARD_INFO; + resp.hdr.nbytes = sizeof(resp.version); + + strcpy_s( + resp.version, + sizeof(resp.version), + "15330 \xA0" "06712\xFF" "\x90"); + + return slider_frame_encode(&slider_uart.readable, &resp, sizeof(resp)); +} + +static HRESULT slider_req_auto_scan_start(void) +{ + assert(chuni_dll.slider_start != NULL); + + dprintf("Chunithm slider: Start slider notifications\n"); + chuni_dll.slider_start(slider_res_auto_scan); + + /* This message is not acknowledged */ + + return S_OK; +} + +static HRESULT slider_req_auto_scan_stop(void) +{ + struct slider_hdr resp; + + assert(chuni_dll.slider_stop != NULL); + + dprintf("Chunithm slider: Stop slider notifications\n"); + + /* IO DLL worker thread might attempt to invoke the callback (which needs + to take slider_lock, which we are currently holding) before noticing that + it needs to shut down. Unlock here so that we don't deadlock in that + situation. */ + + LeaveCriticalSection(&slider_lock); + chuni_dll.slider_stop(); + EnterCriticalSection(&slider_lock); + + resp.sync = SLIDER_FRAME_SYNC; + resp.cmd = SLIDER_CMD_AUTO_SCAN_STOP; + resp.nbytes = 0; + + return slider_frame_encode(&slider_uart.readable, &resp, sizeof(resp)); +} + +static HRESULT slider_req_set_led(const struct slider_req_set_led *req) +{ + assert(chuni_dll.slider_set_leds != NULL); + + chuni_dll.slider_set_leds(req->payload.rgb); + + /* This message is not acknowledged */ + + return S_OK; +} + +static void slider_res_auto_scan(const uint8_t *state) +{ + struct slider_resp_auto_scan resp; + + resp.hdr.sync = SLIDER_FRAME_SYNC; + resp.hdr.cmd = SLIDER_CMD_AUTO_SCAN; + resp.hdr.nbytes = sizeof(resp.pressure); + memcpy(resp.pressure, state, sizeof(resp.pressure)); + + EnterCriticalSection(&slider_lock); + slider_frame_encode(&slider_uart.readable, &resp, sizeof(resp)); + LeaveCriticalSection(&slider_lock); +} diff --git a/divahook/slider.h b/games/chusanhook/slider.h similarity index 100% rename from divahook/slider.h rename to games/chusanhook/slider.h diff --git a/games/cmhook/cm-dll.c b/games/cmhook/cm-dll.c new file mode 100644 index 0000000..d10719e --- /dev/null +++ b/games/cmhook/cm-dll.c @@ -0,0 +1,106 @@ +#include + +#include +#include + +#include "cmhook/cm-dll.h" + +#include "util/dll-bind.h" +#include "util/dprintf.h" + +const struct dll_bind_sym cm_dll_syms[] = { + { + .sym = "cm_io_init", + .off = offsetof(struct cm_dll, init), + }, { + .sym = "cm_io_poll", + .off = offsetof(struct cm_dll, poll), + }, { + .sym = "cm_io_get_opbtns", + .off = offsetof(struct cm_dll, get_opbtns), + } +}; + +struct cm_dll cm_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 cm_dll_init(const struct cm_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("CardMaker IO: Failed to load IO DLL: %lx: %S\n", + hr, + cfg->path); + + goto end; + } + + dprintf("CardMaker IO: Using custom IO DLL: %S\n", cfg->path); + src = owned; + } else { + owned = NULL; + src = self; + } + + get_api_version = (void *) GetProcAddress(src, "cm_io_get_api_version"); + + if (get_api_version != NULL) { + cm_dll.api_version = get_api_version(); + } else { + cm_dll.api_version = 0x0100; + dprintf("Custom IO DLL does not expose cm_io_get_api_version, " + "assuming API version 1.0.\n" + "Please ask the developer to update their DLL.\n"); + } + + if (cm_dll.api_version >= 0x0200) { + hr = E_NOTIMPL; + dprintf("CardMaker IO: Custom IO DLL implements an unsupported " + "API version (%#04x). Please update Segatools.\n", + cm_dll.api_version); + + goto end; + } + + sym = cm_dll_syms; + hr = dll_bind(&cm_dll, src, &sym, _countof(cm_dll_syms)); + + if (FAILED(hr)) { + if (src != self) { + dprintf("CardMaker 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/games/cmhook/cm-dll.h b/games/cmhook/cm-dll.h new file mode 100644 index 0000000..c217efd --- /dev/null +++ b/games/cmhook/cm-dll.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include "cmio/cmio.h" + +struct cm_dll { + uint16_t api_version; + HRESULT (*init)(void); + HRESULT (*poll)(void); + void (*get_opbtns)(uint8_t *opbtn); +}; + +struct cm_dll_config { + wchar_t path[MAX_PATH]; +}; + +extern struct cm_dll cm_dll; + +HRESULT cm_dll_init(const struct cm_dll_config *cfg, HINSTANCE self); diff --git a/games/cmhook/cmhook.def b/games/cmhook/cmhook.def new file mode 100644 index 0000000..ab7d3af --- /dev/null +++ b/games/cmhook/cmhook.def @@ -0,0 +1,73 @@ +LIBRARY cmhook + +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 + cm_io_get_api_version + cm_io_get_opbtns + cm_io_init + cm_io_poll + CFW_init + CFW_term + CFW_open + CFW_close + CFW_listupPrinter + CFW_listupPrinterSN + CFW_selectPrinter + CFW_selectPrinterSN + CFW_getPrinterInfo + CFW_status + CFW_statusAll + CFW_resetPrinter + CFW_updateFirmware + CFW_getFirmwareInfo + CHCUSB_init + CHCUSB_term + CHCUSB_MakeThread + CHCUSB_open + CHCUSB_close + CHCUSB_ReleaseThread + CHCUSB_listupPrinter + CHCUSB_listupPrinterSN + CHCUSB_selectPrinter + CHCUSB_selectPrinterSN + CHCUSB_getPrinterInfo + CHCUSB_imageformat + CHCUSB_setmtf + CHCUSB_makeGamma + CHCUSB_setIcctableProfile + CHCUSB_setIcctable + CHCUSB_copies + CHCUSB_status + CHCUSB_statusAll + CHCUSB_startpage + CHCUSB_endpage + CHCUSB_write + CHCUSB_writeLaminate + CHCUSB_writeHolo + CHCUSB_setPrinterInfo + CHCUSB_setPrinterToneCurve + CHCUSB_getGamma + CHCUSB_getMtf + CHCUSB_cancelCopies + CHCUSB_getPrinterToneCurve + CHCUSB_blinkLED + CHCUSB_resetPrinter + CHCUSB_AttachThreadCount + CHCUSB_getPrintIDStatus + CHCUSB_setPrintStandby + CHCUSB_testCardFeed + CHCUSB_exitCard + CHCUSB_getCardRfidTID + CHCUSB_commCardRfidReader + CHCUSB_updateCardRfidReader + CHCUSB_getErrorLog + CHCUSB_getErrorStatus diff --git a/mu3hook/config.c b/games/cmhook/config.c similarity index 61% rename from mu3hook/config.c rename to games/cmhook/config.c index 6e3991d..47fcfa8 100644 --- a/mu3hook/config.c +++ b/games/cmhook/config.c @@ -1,26 +1,25 @@ #include #include +#include #include "board/config.h" -#include "gfxhook/config.h" - #include "hooklib/config.h" #include "hooklib/dvd.h" -#include "mu3hook/config.h" +#include "cmhook/config.h" #include "platform/config.h" -void mu3_dll_config_load( - struct mu3_dll_config *cfg, +void cm_dll_config_load( + struct cm_dll_config *cfg, const wchar_t *filename) { assert(cfg != NULL); assert(filename != NULL); GetPrivateProfileStringW( - L"mu3io", + L"cmio", L"path", L"", cfg->path, @@ -28,8 +27,8 @@ void mu3_dll_config_load( filename); } -void mu3_hook_config_load( - struct mu3_hook_config *cfg, +void cm_hook_config_load( + struct cm_hook_config *cfg, const wchar_t *filename) { assert(cfg != NULL); @@ -39,6 +38,9 @@ void mu3_hook_config_load( aime_config_load(&cfg->aime, filename); dvd_config_load(&cfg->dvd, filename); io4_config_load(&cfg->io4, filename); - gfx_config_load(&cfg->gfx, filename); - mu3_dll_config_load(&cfg->dll, filename); + vfd_config_load(&cfg->vfd, filename); + touch_screen_config_load(&cfg->touch, filename); + printer_config_load(&cfg->printer, filename); + cm_dll_config_load(&cfg->dll, filename); + unity_config_load(&cfg->unity, filename); } diff --git a/games/cmhook/config.h b/games/cmhook/config.h new file mode 100644 index 0000000..26d9a7c --- /dev/null +++ b/games/cmhook/config.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include "board/config.h" + +#include "hooklib/dvd.h" +#include "hooklib/touch.h" +#include "hooklib/printer.h" + +#include "cmhook/cm-dll.h" + +#include "platform/config.h" + +#include "unityhook/config.h" + +struct cm_hook_config { + struct platform_config platform; + struct aime_config aime; + struct dvd_config dvd; + struct io4_config io4; + struct vfd_config vfd; + struct cm_dll_config dll; + struct touch_screen_config touch; + struct printer_config printer; + struct unity_config unity; +}; + +void cm_dll_config_load( + struct cm_dll_config *cfg, + const wchar_t *filename); + +void cm_hook_config_load( + struct cm_hook_config *cfg, + const wchar_t *filename); diff --git a/games/cmhook/dllmain.c b/games/cmhook/dllmain.c new file mode 100644 index 0000000..29d93af --- /dev/null +++ b/games/cmhook/dllmain.c @@ -0,0 +1,146 @@ +/* + "Card Maker" (cm) hook + + Devices + + USB: 837-15257-01 "Type 4" I/O Board + USB: 838-20006 "WinTouch" Controller Board + USB: 630-00009 Sinfonia CHC-C310 Printer + COM1: 837-15396 "Gen 3" Aime Reader + COM2: 200-6275 VFD GP1232A02A FUTABA Board + COM3: 220-5872 AS-6DB Coin Selector +*/ + +#include + +#include + +#include "board/io4.h" +#include "board/sg-reader.h" +#include "board/vfd.h" + +#include "hook/process.h" + +#include "hooklib/dvd.h" +#include "hooklib/touch.h" +#include "hooklib/serial.h" +#include "hooklib/spike.h" + +#include "cmhook/config.h" +#include "cmhook/io4.h" +#include "cmhook/cm-dll.h" + +#include "platform/platform.h" + +#include "unityhook/hook.h" + +#include "util/dprintf.h" +#include "util/env.h" + +static HMODULE cm_hook_mod; +static process_entry_t cm_startup; +static struct cm_hook_config cm_hook_cfg; + +static DWORD CALLBACK cm_pre_startup(void) +{ + HRESULT hr; + + dprintf("--- Begin cm_pre_startup ---\n"); + + /* Load config */ + + cm_hook_config_load(&cm_hook_cfg, get_config_path()); + + /* Hook Win32 APIs */ + + dvd_hook_init(&cm_hook_cfg.dvd, cm_hook_mod); + touch_screen_hook_init(&cm_hook_cfg.touch, cm_hook_mod); + serial_hook_init(); + + /* Hook external DLL APIs */ + + printer_hook_init(&cm_hook_cfg.printer, 0, cm_hook_mod); + + /* Initialize emulation hooks */ + + struct dipsw_config new_dipsw_config[8] = { + {L"Delivery Server", L"Client", L"Server"}, + }; + + // Set the system dip switch configuration + memcpy(cm_hook_cfg.platform.system.dipsw_config, new_dipsw_config, + sizeof(new_dipsw_config)); + + hr = platform_hook_init( + &cm_hook_cfg.platform, + "SDED", + "ACA1", + cm_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = cm_dll_init(&cm_hook_cfg.dll, cm_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = sg_reader_hook_init(&cm_hook_cfg.aime, 1, 1, cm_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = vfd_hook_init(&cm_hook_cfg.vfd, 2); + + if (FAILED(hr)) { + goto fail; + } + + hr = cm_io4_hook_init(&cm_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 `cmhook` initialization. */ + + unity_hook_init(&cm_hook_cfg.unity, cm_hook_mod, NULL); + + /* Initialize debug helpers */ + + spike_hook_init(get_config_path()); + + dprintf("--- End cm_pre_startup ---\n"); + + /* Jump to EXE start address */ + + return cm_startup(); + +fail: + ExitProcess(EXIT_FAILURE); +} + +BOOL WINAPI DllMain(HMODULE mod, DWORD cause, void *ctx) +{ + HRESULT hr; + + if (cause != DLL_PROCESS_ATTACH) { + return TRUE; + } + + cm_hook_mod = mod; + + hr = process_hijack_startup(cm_pre_startup, &cm_startup); + + if (!SUCCEEDED(hr)) { + dprintf("Failed to hijack process startup: %x\n", (int) hr); + } + + return SUCCEEDED(hr); +} diff --git a/games/cmhook/io4.c b/games/cmhook/io4.c new file mode 100644 index 0000000..073f681 --- /dev/null +++ b/games/cmhook/io4.c @@ -0,0 +1,69 @@ +#include + +#include +#include +#include + +#include "board/io4.h" + +#include "cmhook/cm-dll.h" + +#include "util/dprintf.h" + +static HRESULT cm_io4_poll(void *ctx, struct io4_state *state); +static uint16_t coins; + +static const struct io4_ops cm_io4_ops = { + .poll = cm_io4_poll, +}; + +HRESULT cm_io4_hook_init(const struct io4_config *cfg) +{ + HRESULT hr; + + assert(cm_dll.init != NULL); + + hr = io4_hook_init(cfg, &cm_io4_ops, NULL); + + if (FAILED(hr)) { + return hr; + } + + return cm_dll.init(); +} + +static HRESULT cm_io4_poll(void *ctx, struct io4_state *state) +{ + uint8_t opbtn; + HRESULT hr; + + assert(cm_dll.poll != NULL); + assert(cm_dll.get_opbtns != NULL); + + memset(state, 0, sizeof(*state)); + + hr = cm_dll.poll(); + + if (FAILED(hr)) { + return hr; + } + + opbtn = 0; + + cm_dll.get_opbtns(&opbtn); + + if (opbtn & CM_IO_OPBTN_TEST) { + state->buttons[0] |= IO4_BUTTON_TEST; + } + + if (opbtn & CM_IO_OPBTN_SERVICE) { + state->buttons[0] |= IO4_BUTTON_SERVICE; + } + + if (opbtn & CM_IO_OPBTN_COIN) { + coins++; + } + state->chutes[0] = coins << 8; + + return S_OK; +} diff --git a/games/cmhook/io4.h b/games/cmhook/io4.h new file mode 100644 index 0000000..fb06118 --- /dev/null +++ b/games/cmhook/io4.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "board/io4.h" + +HRESULT cm_io4_hook_init(const struct io4_config *cfg); diff --git a/games/cmhook/meson.build b/games/cmhook/meson.build new file mode 100644 index 0000000..8828da9 --- /dev/null +++ b/games/cmhook/meson.build @@ -0,0 +1,30 @@ +shared_library( + 'cmhook', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + vs_module_defs : 'cmhook.def', + dependencies : [ + capnhook.get_variable('hook_dep'), + capnhook.get_variable('hooklib_dep'), + xinput_lib, + ], + link_with : [ + aimeio_lib, + board_lib, + hooklib_lib, + cmio_lib, + platform_lib, + unityhook_lib, + util_lib, + ], + sources : [ + 'config.c', + 'config.h', + 'dllmain.c', + 'io4.c', + 'io4.h', + 'cm-dll.c', + 'cm-dll.h', + ], +) diff --git a/games/cmio/cmio.c b/games/cmio/cmio.c new file mode 100644 index 0000000..e1cacde --- /dev/null +++ b/games/cmio/cmio.c @@ -0,0 +1,55 @@ +#include +#include + +#include +#include + +#include "cmio/cmio.h" +#include "cmio/config.h" +#include "util/env.h" + +static uint8_t cm_opbtn; +static struct cm_io_config cm_io_cfg; +static bool cm_io_coin; + +uint16_t cm_io_get_api_version(void) +{ + return 0x0100; +} + +HRESULT cm_io_init(void) +{ + cm_io_config_load(&cm_io_cfg, get_config_path()); + return S_OK; +} + +HRESULT cm_io_poll(void) +{ + cm_opbtn = 0; + + if (GetAsyncKeyState(cm_io_cfg.vk_test) & 0x8000) { + cm_opbtn |= CM_IO_OPBTN_TEST; + } + + if (GetAsyncKeyState(cm_io_cfg.vk_service) & 0x8000) { + cm_opbtn |= CM_IO_OPBTN_SERVICE; + } + + if (GetAsyncKeyState(cm_io_cfg.vk_coin) & 0x8000) { + if (!cm_io_coin) { + cm_io_coin = true; + cm_opbtn |= CM_IO_OPBTN_COIN; + } + } else { + cm_io_coin = false; + } + + return S_OK; +} + +void cm_io_get_opbtns(uint8_t *opbtn) +{ + if (opbtn != NULL) { + *opbtn = cm_opbtn; + } +} diff --git a/games/cmio/cmio.h b/games/cmio/cmio.h new file mode 100644 index 0000000..86d8ac9 --- /dev/null +++ b/games/cmio/cmio.h @@ -0,0 +1,44 @@ +#pragma once + +#include + +#include + +enum { + CM_IO_OPBTN_TEST = 0x01, + CM_IO_OPBTN_SERVICE = 0x02, + CM_IO_OPBTN_COIN = 0x04, +}; + +/* Get the version of the CardMaker 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 cm_io_get_api_version(void); + +/* Initialize the IO DLL. This is the second function that will be called on + your DLL, after cm_io_get_api_version. + + All subsequent calls to this API may originate from arbitrary threads. + + Minimum API version: 0x0100 */ + +HRESULT cm_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 cm_io_poll(void); + +/* Get the state of the cabinet's operator buttons as of the last poll. See + CM_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 cm_io_get_opbtns(uint8_t *opbtn); diff --git a/games/cmio/config.c b/games/cmio/config.c new file mode 100644 index 0000000..452f3b3 --- /dev/null +++ b/games/cmio/config.c @@ -0,0 +1,22 @@ +#include + +#include +#include +#include + +#include "cmio/config.h" + +void cm_io_config_load( + struct cm_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", VK_F1, filename); + cfg->vk_service = GetPrivateProfileIntW(L"io4", L"service", VK_F2, filename); + cfg->vk_coin = GetPrivateProfileIntW(L"io4", L"coin", VK_F3, filename); +} diff --git a/chuniio/config.h b/games/cmio/config.h similarity index 54% rename from chuniio/config.h rename to games/cmio/config.h index fa2e2b0..d73f6c5 100644 --- a/chuniio/config.h +++ b/games/cmio/config.h @@ -3,14 +3,14 @@ #include #include -struct chuni_io_config { +#include + +struct cm_io_config { uint8_t vk_test; uint8_t vk_service; uint8_t vk_coin; - uint8_t vk_ir; - uint8_t vk_cell[32]; }; -void chuni_io_config_load( - struct chuni_io_config *cfg, +void cm_io_config_load( + struct cm_io_config *cfg, const wchar_t *filename); diff --git a/chuniio/meson.build b/games/cmio/meson.build similarity index 57% rename from chuniio/meson.build rename to games/cmio/meson.build index 9e61229..d7a5ba6 100644 --- a/chuniio/meson.build +++ b/games/cmio/meson.build @@ -1,12 +1,11 @@ -chuniio_lib = static_library( - 'chuniio', +cmio_lib = static_library( + 'cmio', name_prefix : '', include_directories : inc, implicit_include_directories : false, - c_pch : '../precompiled.h', sources : [ - 'chuniio.c', - 'chuniio.h', + 'cmio.c', + 'cmio.h', 'config.c', 'config.h', ], diff --git a/cxbhook/config.c b/games/cxbhook/config.c similarity index 68% rename from cxbhook/config.c rename to games/cxbhook/config.c index 9a513d1..98d326d 100644 --- a/cxbhook/config.c +++ b/games/cxbhook/config.c @@ -3,6 +3,7 @@ #include #include #include +#include #include "amex/amex.h" #include "amex/config.h" @@ -23,22 +24,32 @@ void cxb_dll_config_load( struct cxb_dll_config *cfg, const wchar_t *filename) { + assert(cfg != NULL); + assert(filename != NULL); + GetPrivateProfileStringW( + L"cxbio", + L"path", + L"", + cfg->path, + _countof(cfg->path), + filename); } void revio_config_load(struct revio_config *cfg, const wchar_t *filename) { + assert(cfg != NULL); + assert(filename != NULL); -} - -void network_config_load(struct network_config *cfg, const wchar_t *filename) -{ - + cfg->enable = GetPrivateProfileIntW(L"revio", L"enable", 1, filename); } void led_config_load(struct led_config *cfg, const wchar_t *filename) { + assert(cfg != NULL); + assert(filename != NULL); + cfg->enable = GetPrivateProfileIntW(L"led", L"enable", 1, filename); } void cxb_hook_config_load( @@ -56,6 +67,5 @@ void cxb_hook_config_load( gfx_config_load(&cfg->gfx, filename); cxb_dll_config_load(&cfg->dll, filename); revio_config_load(&cfg->revio, filename); - network_config_load(&cfg->network, filename); led_config_load(&cfg->led, filename); } \ No newline at end of file diff --git a/cxbhook/config.h b/games/cxbhook/config.h similarity index 85% rename from cxbhook/config.h rename to games/cxbhook/config.h index f2bb8ca..4c7d3af 100644 --- a/cxbhook/config.h +++ b/games/cxbhook/config.h @@ -10,7 +10,6 @@ #include "cxbhook/cxb-dll.h" #include "cxbhook/revio.h" #include "cxbhook/led.h" -#include "cxbhook/network.h" #include "gfxhook/gfx.h" @@ -23,7 +22,6 @@ struct cxb_hook_config { struct gfx_config gfx; struct cxb_dll_config dll; struct revio_config revio; - struct network_config network; struct led_config led; }; @@ -32,7 +30,6 @@ void cxb_dll_config_load( const wchar_t *filename); void revio_config_load(struct revio_config *cfg, const wchar_t *filename); -void network_config_load(struct network_config *cfg, const wchar_t *filename); void led_config_load(struct led_config *cfg, const wchar_t *filename); void cxb_hook_config_load( diff --git a/cxbhook/cxb-dll.c b/games/cxbhook/cxb-dll.c similarity index 100% rename from cxbhook/cxb-dll.c rename to games/cxbhook/cxb-dll.c diff --git a/cxbhook/cxb-dll.h b/games/cxbhook/cxb-dll.h similarity index 100% rename from cxbhook/cxb-dll.h rename to games/cxbhook/cxb-dll.h diff --git a/cxbhook/cxbhook.def b/games/cxbhook/cxbhook.def similarity index 100% rename from cxbhook/cxbhook.def rename to games/cxbhook/cxbhook.def diff --git a/cxbhook/dllmain.c b/games/cxbhook/dllmain.c similarity index 89% rename from cxbhook/dllmain.c rename to games/cxbhook/dllmain.c index b9c3056..d644b5e 100644 --- a/cxbhook/dllmain.c +++ b/games/cxbhook/dllmain.c @@ -9,7 +9,6 @@ #include "cxbhook/config.h" #include "cxbhook/revio.h" #include "cxbhook/led.h" -#include "cxbhook/network.h" #include "cxbio/cxbio.h" @@ -24,6 +23,7 @@ #include "platform/platform.h" #include "util/dprintf.h" +#include "util/env.h" static HMODULE cxb_hook_mod; static process_entry_t cxb_startup; @@ -59,7 +59,7 @@ static DWORD CALLBACK cxb_pre_startup(void) /* Config load */ - cxb_hook_config_load(&cxb_hook_cfg, L".\\segatools.ini"); + cxb_hook_config_load(&cxb_hook_cfg, get_config_path()); /* Hook Win32 APIs */ @@ -91,7 +91,7 @@ static DWORD CALLBACK cxb_pre_startup(void) goto fail; } - hr = sg_reader_hook_init(&cxb_hook_cfg.aime, 12, cxb_hook_mod); + hr = sg_reader_hook_init(&cxb_hook_cfg.aime, 12, 1, cxb_hook_mod); if (FAILED(hr)) { goto fail; @@ -103,12 +103,6 @@ static DWORD CALLBACK cxb_pre_startup(void) goto fail; } - hr = network_hook_init(&cxb_hook_cfg.network); - - if (FAILED(hr)) { - goto fail; - } - hr = led_hook_init(&cxb_hook_cfg.led); if (FAILED(hr)) { @@ -117,7 +111,7 @@ static DWORD CALLBACK cxb_pre_startup(void) /* Initialize debug helpers */ - spike_hook_init(L".\\segatools.ini"); + spike_hook_init(get_config_path()); dprintf("--- End cxb_pre_startup ---\n"); diff --git a/cxbhook/led.c b/games/cxbhook/led.c similarity index 85% rename from cxbhook/led.c rename to games/cxbhook/led.c index 39d6d4f..662f7f9 100644 --- a/cxbhook/led.c +++ b/games/cxbhook/led.c @@ -1,11 +1,14 @@ #include + +#include #include #include +#include #include "cxbhook/led.h" #include "cxbhook/cxb-dll.h" -#include "hooklib/procaddr.h" +#include "hook/procaddr.h" #include "hook/table.h" @@ -49,8 +52,14 @@ static struct hook_symbol lamp_syms[] = { HRESULT led_hook_init(struct led_config *cfg) { - dprintf("LED: Init\n"); - return proc_addr_table_push("CommLamp.dll", lamp_syms, _countof(lamp_syms)); + assert(cfg != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + dprintf("LED: Hook enabled.\n"); + return proc_addr_table_push(NULL, "CommLamp.dll", lamp_syms, _countof(lamp_syms)); } static int my_cCommLamp_Open(char *port) diff --git a/cxbhook/led.h b/games/cxbhook/led.h similarity index 100% rename from cxbhook/led.h rename to games/cxbhook/led.h diff --git a/cxbhook/meson.build b/games/cxbhook/meson.build similarity index 90% rename from cxbhook/meson.build rename to games/cxbhook/meson.build index 1c23a00..e69ffff 100644 --- a/cxbhook/meson.build +++ b/games/cxbhook/meson.build @@ -4,7 +4,6 @@ shared_library( include_directories : inc, implicit_include_directories : false, vs_module_defs : 'cxbhook.def', - c_pch : '../precompiled.h', dependencies : [ capnhook.get_variable('hook_dep'), capnhook.get_variable('hooklib_dep'), @@ -30,7 +29,5 @@ shared_library( 'revio.h', 'led.c', 'led.h', - 'network.c', - 'network.h', ], ) diff --git a/cxbhook/revio.c b/games/cxbhook/revio.c similarity index 91% rename from cxbhook/revio.c rename to games/cxbhook/revio.c index e72158d..84ab016 100644 --- a/cxbhook/revio.c +++ b/games/cxbhook/revio.c @@ -1,12 +1,15 @@ #include + +#include #include #include +#include #include #include "cxbhook/revio.h" #include "cxbhook/cxb-dll.h" -#include "hooklib/procaddr.h" +#include "hook/procaddr.h" #include "hook/table.h" @@ -82,8 +85,14 @@ static struct hook_symbol revio_syms[] = { HRESULT revio_hook_init(struct revio_config *cfg) { - dprintf("Revio: Init\n"); - return proc_addr_table_push("CommIo.dll", revio_syms, _countof(revio_syms)); + assert(cfg != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + dprintf("Revio: Hook enabled.\n"); + return proc_addr_table_push(NULL, "CommIo.dll", revio_syms, _countof(revio_syms)); } static int my_cCommIo_Open(char *port) @@ -154,7 +163,7 @@ static int my_cCommIo_GetTrigger() out &= ~last_triggers; - dprintf("Revio: GetTrigger %X\n", out); + // dprintf("Revio: GetTrigger %X\n", out); last_triggers = out; return out; } @@ -188,7 +197,7 @@ static int my_cCommIo_GetRelease() out &= ~btns; - dprintf("Revio: GetRelease %X\n", out); + // dprintf("Revio: GetRelease %X\n", out); last_triggers = btns; return out; } diff --git a/cxbhook/revio.h b/games/cxbhook/revio.h similarity index 100% rename from cxbhook/revio.h rename to games/cxbhook/revio.h diff --git a/cxbio/config.c b/games/cxbio/config.c similarity index 100% rename from cxbio/config.c rename to games/cxbio/config.c diff --git a/cxbio/config.h b/games/cxbio/config.h similarity index 100% rename from cxbio/config.h rename to games/cxbio/config.h diff --git a/cxbio/cxbio.c b/games/cxbio/cxbio.c similarity index 94% rename from cxbio/cxbio.c rename to games/cxbio/cxbio.c index d021d3c..fce594d 100644 --- a/cxbio/cxbio.c +++ b/games/cxbio/cxbio.c @@ -8,6 +8,7 @@ #include "cxbio/config.h" #include "util/dprintf.h" +#include "util/env.h" static bool cxb_io_coin; static int cxb_io_coins; @@ -21,7 +22,7 @@ uint16_t cxb_io_get_api_version(void) HRESULT cxb_io_revio_init(void) { dprintf("CXB IO: REVIO init\n"); - cxb_io_config_load(&cxb_io_cfg, L".\\segatools.ini"); + cxb_io_config_load(&cxb_io_cfg, get_config_path()); return S_OK; } @@ -75,4 +76,4 @@ HRESULT cxb_io_led_init(void) } void cxb_io_led_update(int id, int color) -{} \ No newline at end of file +{} diff --git a/cxbio/cxbio.h b/games/cxbio/cxbio.h similarity index 100% rename from cxbio/cxbio.h rename to games/cxbio/cxbio.h diff --git a/cxbio/meson.build b/games/cxbio/meson.build similarity index 88% rename from cxbio/meson.build rename to games/cxbio/meson.build index 2ce52d2..5933602 100644 --- a/cxbio/meson.build +++ b/games/cxbio/meson.build @@ -3,7 +3,6 @@ cxbio_lib = static_library( name_prefix : '', include_directories : inc, implicit_include_directories : false, - c_pch : '../precompiled.h', sources : [ 'cxbio.c', 'cxbio.h', diff --git a/divahook/config.c b/games/divahook/config.c similarity index 89% rename from divahook/config.c rename to games/divahook/config.c index 29ca5c8..142cf73 100644 --- a/divahook/config.c +++ b/games/divahook/config.c @@ -8,6 +8,9 @@ #include "board/config.h" #include "board/sg-reader.h" +#include "hooklib/config.h" +#include "hooklib/dvd.h" + #include "divahook/config.h" #include "platform/config.h" @@ -47,6 +50,8 @@ void diva_hook_config_load( platform_config_load(&cfg->platform, filename); amex_config_load(&cfg->amex, filename); aime_config_load(&cfg->aime, filename); + dvd_config_load(&cfg->dvd, filename); + gfx_config_load(&cfg->gfx, filename); diva_dll_config_load(&cfg->dll, filename); slider_config_load(&cfg->slider, filename); } diff --git a/divahook/config.h b/games/divahook/config.h similarity index 75% rename from divahook/config.h rename to games/divahook/config.h index a327f47..694b055 100644 --- a/divahook/config.h +++ b/games/divahook/config.h @@ -6,15 +6,23 @@ #include "board/sg-reader.h" +#include "hooklib/dvd.h" +#include "hooklib/touch.h" + +#include "gfxhook/config.h" + +#include "platform/config.h" + #include "divahook/diva-dll.h" #include "divahook/slider.h" -#include "platform/platform.h" - struct diva_hook_config { struct platform_config platform; struct amex_config amex; struct aime_config aime; + struct dvd_config dvd; + struct gfx_config gfx; + struct touch_screen_config touch; struct diva_dll_config dll; struct slider_config slider; }; diff --git a/divahook/diva-dll.c b/games/divahook/diva-dll.c similarity index 100% rename from divahook/diva-dll.c rename to games/divahook/diva-dll.c diff --git a/divahook/diva-dll.h b/games/divahook/diva-dll.h similarity index 100% rename from divahook/diva-dll.h rename to games/divahook/diva-dll.h diff --git a/divahook/divahook.def b/games/divahook/divahook.def similarity index 100% rename from divahook/divahook.def rename to games/divahook/divahook.def diff --git a/divahook/dllmain.c b/games/divahook/dllmain.c similarity index 65% rename from divahook/dllmain.c rename to games/divahook/dllmain.c index fb3c678..eb6e16f 100644 --- a/divahook/dllmain.c +++ b/games/divahook/dllmain.c @@ -1,3 +1,14 @@ +/* + "Hatsune Miku Project DIVA Arcade " (diva) hook + + Devices + + JVS: 837-14572 "Type 3" I/O Board + COM1: 3M Touch Systems 78-0011-2353-4 Touch Controller Board + COM10: TN32MSEC003S "Gen 1" Aime Reader + COM11: 837-15275 Touch Slider +*/ + #include #include @@ -11,6 +22,9 @@ #include "divahook/jvs.h" #include "divahook/slider.h" +#include "gfxhook/gfx.h" +#include "gfxhook/gl.h" + #include "hook/process.h" #include "hooklib/serial.h" @@ -19,6 +33,7 @@ #include "platform/platform.h" #include "util/dprintf.h" +#include "util/env.h" static HMODULE diva_hook_mod; static process_entry_t diva_startup; @@ -27,15 +42,30 @@ static struct diva_hook_config diva_hook_cfg; static DWORD CALLBACK diva_pre_startup(void) { HRESULT hr; + HMODULE dbghelp; dprintf("--- Begin diva_pre_startup ---\n"); + /* Pin dbghelp so the path hooks apply to it. */ + + dbghelp = LoadLibraryW(L"dbghelp.dll"); + + if (dbghelp != NULL) { + dprintf("Pinned debug helper library, hMod=%p\n", dbghelp); + } + else { + dprintf("Failed to load debug helper library!\n"); + } + /* Config load */ - diva_hook_config_load(&diva_hook_cfg, L".\\segatools.ini"); + diva_hook_config_load(&diva_hook_cfg, get_config_path()); /* Hook Win32 APIs */ + dvd_hook_init(&diva_hook_cfg.dvd, diva_hook_mod); + gfx_hook_init(&diva_hook_cfg.gfx); + gfx_gl_hook_init(&diva_hook_cfg.gfx, diva_hook_mod); serial_hook_init(); /* Initialize emulation hooks */ @@ -62,7 +92,7 @@ static DWORD CALLBACK diva_pre_startup(void) goto fail; } - hr = sg_reader_hook_init(&diva_hook_cfg.aime, 10, diva_hook_mod); + hr = sg_reader_hook_init(&diva_hook_cfg.aime, 10, 1, diva_hook_mod); if (FAILED(hr)) { goto fail; @@ -76,7 +106,7 @@ static DWORD CALLBACK diva_pre_startup(void) /* Initialize debug helpers */ - spike_hook_init(L".\\segatools.ini"); + spike_hook_init(get_config_path()); dprintf("--- End diva_pre_startup ---\n"); diff --git a/divahook/jvs.c b/games/divahook/jvs.c similarity index 86% rename from divahook/jvs.c rename to games/divahook/jvs.c index 14d222d..f718438 100644 --- a/divahook/jvs.c +++ b/games/divahook/jvs.c @@ -63,33 +63,33 @@ static void diva_jvs_read_switches(void *ctx, struct io3_switch_state *out) diva_dll.jvs_poll(&opbtn, &gamebtn); - if (gamebtn & 0x01) { + if (gamebtn & DIVA_IO_GAMEBTN_CIRCLE) { out->p1 |= 1 << 6; } - if (gamebtn & 0x02) { + if (gamebtn & DIVA_IO_GAMEBTN_CROSS) { out->p1 |= 1 << 7; } - if (gamebtn & 0x04) { + if (gamebtn & DIVA_IO_GAMEBTN_SQUARE) { out->p1 |= 1 << 8; } - if (gamebtn & 0x08) { + if (gamebtn & DIVA_IO_GAMEBTN_TRIANGLE) { out->p1 |= 1 << 9; } - if (gamebtn & 0x10) { + if (gamebtn & DIVA_IO_GAMEBTN_START) { out->p1 |= 1 << 15; } - if (opbtn & 0x01) { + if (opbtn & DIVA_IO_OPBTN_TEST) { out->system = 0x80; } else { out->system = 0; } - if (opbtn & 0x02) { + if (opbtn & DIVA_IO_OPBTN_SERVICE) { out->p1 |= 1 << 14; } } diff --git a/divahook/jvs.h b/games/divahook/jvs.h similarity index 100% rename from divahook/jvs.h rename to games/divahook/jvs.h diff --git a/divahook/meson.build b/games/divahook/meson.build similarity index 95% rename from divahook/meson.build rename to games/divahook/meson.build index 20c1ff2..4c66c00 100644 --- a/divahook/meson.build +++ b/games/divahook/meson.build @@ -4,7 +4,6 @@ shared_library( include_directories : inc, implicit_include_directories : false, vs_module_defs : 'divahook.def', - c_pch : '../precompiled.h', dependencies : [ capnhook.get_variable('hook_dep'), capnhook.get_variable('hooklib_dep'), @@ -14,6 +13,7 @@ shared_library( amex_lib, board_lib, divaio_lib, + gfxhook_lib, hooklib_lib, jvs_lib, platform_lib, diff --git a/divahook/slider.c b/games/divahook/slider.c similarity index 99% rename from divahook/slider.c rename to games/divahook/slider.c index 8294923..3046142 100644 --- a/divahook/slider.c +++ b/games/divahook/slider.c @@ -99,7 +99,7 @@ static HRESULT slider_handle_irp_locked(struct irp *irp) } for (;;) { -#if 0 +#if defined(LOG_DIVA_SLIDER) dprintf("TX Buffer:\n"); dump_iobuf(&slider_uart.written); #endif @@ -118,7 +118,7 @@ static HRESULT slider_handle_irp_locked(struct irp *irp) return hr; } -#if 0 +#if defined(LOG_DIVA_SLIDER) dprintf("Deframe Buffer:\n"); dump_iobuf(&req_iobuf); #endif diff --git a/games/divahook/slider.h b/games/divahook/slider.h new file mode 100644 index 0000000..7053e04 --- /dev/null +++ b/games/divahook/slider.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#include + +struct slider_config { + bool enable; +}; + +HRESULT slider_hook_init(const struct slider_config *cfg); diff --git a/divaio/config.c b/games/divaio/config.c similarity index 87% rename from divaio/config.c rename to games/divaio/config.c index 886277e..110392e 100644 --- a/divaio/config.c +++ b/games/divaio/config.c @@ -27,9 +27,9 @@ void diva_io_config_load( 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); + cfg->vk_test = GetPrivateProfileIntW(L"io3", L"test", VK_F1, filename); + cfg->vk_service = GetPrivateProfileIntW(L"io3", L"service", VK_F2, filename); + cfg->vk_coin = GetPrivateProfileIntW(L"io3", L"coin", VK_F3, filename); for (i = 0 ; i < _countof(cfg->vk_buttons) ; i++) { swprintf_s(key, _countof(key), L"key%i", i + 1); diff --git a/divaio/config.h b/games/divaio/config.h similarity index 100% rename from divaio/config.h rename to games/divaio/config.h diff --git a/divaio/divaio.c b/games/divaio/divaio.c similarity index 94% rename from divaio/divaio.c rename to games/divaio/divaio.c index 760e198..0001f2e 100644 --- a/divaio/divaio.c +++ b/games/divaio/divaio.c @@ -7,6 +7,7 @@ #include "divaio/divaio.h" #include "divaio/config.h" +#include "util/env.h" static unsigned int __stdcall diva_io_slider_thread_proc(void *ctx); @@ -23,7 +24,7 @@ uint16_t diva_io_get_api_version(void) HRESULT diva_io_jvs_init(void) { - diva_io_config_load(&diva_io_cfg, L".\\segatools.ini"); + diva_io_config_load(&diva_io_cfg, get_config_path()); return S_OK; } @@ -37,11 +38,11 @@ void diva_io_jvs_poll(uint8_t *opbtn_out, uint8_t *gamebtn_out) opbtn = 0; if (GetAsyncKeyState(diva_io_cfg.vk_test) & 0x8000) { - opbtn |= 1; + opbtn |= DIVA_IO_OPBTN_TEST; } if (GetAsyncKeyState(diva_io_cfg.vk_service) & 0x8000) { - opbtn |= 2; + opbtn |= DIVA_IO_OPBTN_SERVICE; } for (i = 0 ; i < _countof(diva_io_cfg.vk_buttons) ; i++) { diff --git a/divaio/divaio.h b/games/divaio/divaio.h similarity index 93% rename from divaio/divaio.h rename to games/divaio/divaio.h index bac3627..7dd13b8 100644 --- a/divaio/divaio.h +++ b/games/divaio/divaio.h @@ -5,6 +5,19 @@ #include #include +enum { + DIVA_IO_OPBTN_TEST = 0x01, + DIVA_IO_OPBTN_SERVICE = 0x02 +}; + +enum { + DIVA_IO_GAMEBTN_CIRCLE = 0x01, + DIVA_IO_GAMEBTN_CROSS = 0x02, + DIVA_IO_GAMEBTN_SQUARE = 0x04, + DIVA_IO_GAMEBTN_TRIANGLE = 0x08, + DIVA_IO_GAMEBTN_START = 0x10, +}; + /* Get the version of the Project Diva 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 diff --git a/divaio/meson.build b/games/divaio/meson.build similarity index 88% rename from divaio/meson.build rename to games/divaio/meson.build index edac3b6..3d4f611 100644 --- a/divaio/meson.build +++ b/games/divaio/meson.build @@ -3,7 +3,6 @@ divaio_lib = static_library( name_prefix : '', include_directories : inc, implicit_include_directories : false, - c_pch : '../precompiled.h', sources : [ 'divaio.c', 'divaio.h', diff --git a/games/fgohook/config.c b/games/fgohook/config.c new file mode 100644 index 0000000..766240c --- /dev/null +++ b/games/fgohook/config.c @@ -0,0 +1,129 @@ +#include +#include +#include + +#include "board/config.h" + +#include "hooklib/config.h" +#include "hooklib/dvd.h" + +#include "fgohook/config.h" + +#include "platform/config.h" + +void fgo_dll_config_load( + struct fgo_dll_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + GetPrivateProfileStringW( + L"fgoio", + L"path", + L"", + cfg->path, + _countof(cfg->path), + filename); +} + +void ftdi_config_load(struct ftdi_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"ftdi", L"enable", 1, filename); + cfg->port_no = GetPrivateProfileIntW(L"ftdi", L"portNo", 0, filename); +} + +void led15093_config_load(struct led15093_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + wchar_t tmpstr[16]; + + memset(cfg->board_number, ' ', sizeof(cfg->board_number)); + memset(cfg->chip_number, ' ', sizeof(cfg->chip_number)); + memset(cfg->boot_chip_number, ' ', sizeof(cfg->boot_chip_number)); + + cfg->enable = GetPrivateProfileIntW(L"led15093", L"enable", 1, filename); + cfg->port_no[0] = GetPrivateProfileIntW(L"led15093", L"portNo1", 0, filename); + cfg->port_no[1] = GetPrivateProfileIntW(L"led15093", L"portNo2", 0, filename); + cfg->high_baudrate = GetPrivateProfileIntW(L"led15093", L"highBaud", 0, filename); + cfg->fw_ver = GetPrivateProfileIntW(L"led15093", L"fwVer", 0xA0, filename); + cfg->fw_sum = GetPrivateProfileIntW(L"led15093", L"fwSum", 0xAA53, filename); + + GetPrivateProfileStringW( + L"led15093", + L"boardNumber", + L"15093-06", + tmpstr, + _countof(tmpstr), + filename); + + size_t n = wcstombs(cfg->board_number, tmpstr, sizeof(cfg->board_number)); + for (int i = n; i < sizeof(cfg->board_number); i++) + { + cfg->board_number[i] = ' '; + } + + GetPrivateProfileStringW( + L"led15093", + L"chipNumber", + L"6710A", + tmpstr, + _countof(tmpstr), + filename); + + n = wcstombs(cfg->chip_number, tmpstr, sizeof(cfg->chip_number)); + for (int i = n; i < sizeof(cfg->chip_number); i++) + { + cfg->chip_number[i] = ' '; + } + + GetPrivateProfileStringW( + L"led15093", + L"bootChipNumber", + L"6709 ", + tmpstr, + _countof(tmpstr), + filename); + + n = wcstombs(cfg->boot_chip_number, tmpstr, sizeof(cfg->boot_chip_number)); + for (int i = n; i < sizeof(cfg->boot_chip_number); i++) + { + cfg->boot_chip_number[i] = ' '; + } +} + +void fgo_deck_config_load( + struct deck_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"deck", L"enable", 1, filename); +} + +void fgo_hook_config_load( + struct fgo_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); + vfd_config_load(&cfg->vfd, filename); + touch_screen_config_load(&cfg->touch, filename); + printer_config_load(&cfg->printer, filename); + fgo_deck_config_load(&cfg->deck, filename); + ftdi_config_load(&cfg->ftdi, filename); + led15093_config_load(&cfg->led15093, filename); + gfx_config_load(&cfg->gfx, filename); + fgo_dll_config_load(&cfg->dll, filename); +} diff --git a/games/fgohook/config.h b/games/fgohook/config.h new file mode 100644 index 0000000..ca8895a --- /dev/null +++ b/games/fgohook/config.h @@ -0,0 +1,41 @@ +#pragma once + +#include + +#include "board/config.h" +#include "board/led15093.h" + +#include "hooklib/dvd.h" +#include "hooklib/touch.h" +#include "hooklib/printer.h" + +#include "gfxhook/config.h" + +#include "fgohook/deck.h" +#include "fgohook/ftdi.h" +#include "fgohook/fgo-dll.h" + +#include "platform/config.h" + +struct fgo_hook_config { + struct platform_config platform; + struct aime_config aime; + struct dvd_config dvd; + struct io4_config io4; + struct vfd_config vfd; + struct touch_screen_config touch; + struct printer_config printer; + struct deck_config deck; + struct ftdi_config ftdi; + struct led15093_config led15093; + struct gfx_config gfx; + struct fgo_dll_config dll; +}; + +void fgo_dll_config_load( + struct fgo_dll_config *cfg, + const wchar_t *filename); + +void fgo_hook_config_load( + struct fgo_hook_config *cfg, + const wchar_t *filename); diff --git a/games/fgohook/deck.c b/games/fgohook/deck.c new file mode 100644 index 0000000..b8e4bed --- /dev/null +++ b/games/fgohook/deck.c @@ -0,0 +1,513 @@ +/* + SEGA 837-15345 RFID Deck Reader emulator + + Credits: + + OLEG + Coburn + Mitsuhide +*/ + +#include + +#include + +#include "board/sg-frame.h" + +#include "fgohook/deck.h" + +#include "hook/iobuf.h" +#include "hook/iohook.h" + +#include "hooklib/uart.h" + +#include "util/dprintf.h" +#include "util/dump.h" + +#define MAX_CARDS 30 + +// request format: +// 0xe0 - sync +// 0x?? - command +// 0x?? - payload length +// ... - payload +// 0x?? - checksum (sum of everything except the sync byte) +// +// response format: +// 0xe0 - sync +// 0x?? - command +// 0x?? - status code +// 0x?? - payload length +// ... - payload +// 0x?? - checksum + +enum { + DECK_CMD_RESET = 0x41, + DECK_CMD_GET_BOOT_FW_VERSION = 0x84, + DECK_CMD_GET_BOARD_INFO = 0x85, + DECK_CMD_INIT_UNK1 = 0x81, + DECK_CMD_GET_APP_FW_VERSION = 0x42, + DECK_CMD_INIT_UNK2 = 0x04, + DECK_CMD_INIT_UNK3 = 0x05, + DECK_CMD_READ = 0x06 +}; + +enum { + DECK_READ_START = 0x81, + DECK_READ_DATA = 0x82, + DECK_READ_END = 0x83 +}; + +struct deck_hdr { + uint8_t sync; + uint8_t cmd; + uint8_t nbytes; +}; + +struct deck_resp_hdr { + uint8_t sync; + uint8_t cmd; + uint8_t status; + uint8_t nbytes; +}; + +struct deck_resp_get_boot_fw_version { + struct deck_resp_hdr hdr; + uint8_t boot_fw_version; +}; + +struct deck_resp_get_app_fw_version { + struct deck_resp_hdr hdr; + uint8_t app_fw_version; +}; + +struct deck_resp_get_board_info { + struct deck_resp_hdr hdr; + uint8_t board[9]; +}; + +struct deck_resp_read { + struct deck_resp_hdr hdr; + uint8_t card_data[44]; +}; + +union deck_req_any { + struct deck_hdr hdr; + uint8_t bytes[520]; +}; + +struct card_collection { + uint8_t nCards; + uint8_t cards[MAX_CARDS][44]; +}; + +static HRESULT init_mmf(void); + +static HRESULT deck_handle_irp(struct irp *irp); +static HRESULT deck_handle_irp_locked(struct irp *irp); +static HRESULT deck_req_dispatch(const union deck_req_any* req); +static HRESULT deck_req_get_boot_fw_version(void); +static HRESULT deck_req_get_app_fw_version(void); +static HRESULT deck_req_get_board_info(void); +static HRESULT deck_req_read(void); +static HRESULT deck_req_nop(uint8_t cmd); +static void deck_read_one(void); + +static HRESULT deck_frame_accept(const struct iobuf* dest); +static void deck_frame_sync(struct iobuf* src); +static HRESULT deck_frame_decode(struct iobuf *dest, struct iobuf *src); +static HRESULT deck_frame_encode(struct iobuf* dest, const void* ptr, size_t nbytes); +static HRESULT deck_frame_encode_byte(struct iobuf* dest, uint8_t byte); + +static CRITICAL_SECTION deck_lock; +static struct uart deck_uart; +static uint8_t deck_written_bytes[1024]; +static uint8_t deck_readable_bytes[1024]; +static HANDLE mutex; +static HANDLE mmf; +static struct card_collection* cards_ptr; +static uint8_t current_card_idx = 0; +static bool read_pending = false; + +HRESULT deck_hook_init(const struct deck_config *cfg, unsigned int port_no) +{ + assert(cfg != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + InitializeCriticalSection(&deck_lock); + + uart_init(&deck_uart, port_no); + deck_uart.written.bytes = deck_written_bytes; + deck_uart.written.nbytes = sizeof(deck_written_bytes); + deck_uart.readable.bytes = deck_readable_bytes; + deck_uart.readable.nbytes = sizeof(deck_readable_bytes); + + if (FAILED(init_mmf())) { + return S_FALSE; + } + + dprintf("Deck Reader: hook enabled.\n"); + + return iohook_push_handler(deck_handle_irp); +} + +static HRESULT init_mmf(void) { + mutex = CreateMutexA(NULL, FALSE, "FGODeckMutex"); + if (mutex == NULL) { + return S_FALSE; + } + + mmf = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, MAX_CARDS * 44 + 1, "FGODeck"); + if (mmf == NULL) { + return S_FALSE; + } + + cards_ptr = (struct card_collection*)MapViewOfFile(mmf, FILE_MAP_ALL_ACCESS, 0, 0, MAX_CARDS * 44 + 1); + + return S_OK; +} + +static HRESULT deck_handle_irp(struct irp *irp) +{ + HRESULT hr; + + assert(irp != NULL); + + if (!uart_match_irp(&deck_uart, irp)) { + return iohook_invoke_next(irp); + } + + EnterCriticalSection(&deck_lock); + hr = deck_handle_irp_locked(irp); + LeaveCriticalSection(&deck_lock); + + return hr; +} + +static HRESULT deck_handle_irp_locked(struct irp *irp) +{ + uint8_t req[1024]; + struct iobuf req_iobuf; + HRESULT hr; + + if (irp->op == IRP_OP_OPEN) { + dprintf("Deck Reader: Starting backend\n"); + } + + hr = uart_handle_irp(&deck_uart, irp); + + if (SUCCEEDED(hr) && irp->op == IRP_OP_READ && read_pending == true) { + deck_read_one(); + return S_OK; + } + + if (FAILED(hr) || irp->op != IRP_OP_WRITE) { + return hr; + } + + for (;;) { + // if (deck_uart.written.pos != 0) { + // dprintf("Deck Reader: TX Buffer:\n"); + // dump_iobuf(&deck_uart.written); + // } + + req_iobuf.bytes = req; + req_iobuf.nbytes = sizeof(req); + req_iobuf.pos = 0; + + hr = deck_frame_decode(&req_iobuf, &deck_uart.written); + + if (hr != S_OK) { + if (FAILED(hr)) { + dprintf("Deck Reader: Deframe error: %x, %d %d\n", (int) hr, (int) req_iobuf.nbytes, (int) req_iobuf.pos); + } + + return hr; + } + + // dprintf("Deck Reader: Deframe Buffer:\n"); + // dump_iobuf(&req_iobuf); + + hr = deck_req_dispatch((const union deck_req_any *) &req); + + if (FAILED(hr)) { + dprintf("Deck Reader: Processing error: %x\n", (int) hr); + } + + // dprintf("Deck Reader: Written bytes:\n"); + // dump_iobuf(&deck_uart.readable); + } +} + +static HRESULT deck_req_dispatch(const union deck_req_any *req) { + switch (req->hdr.cmd) { + case DECK_CMD_RESET: + case DECK_CMD_INIT_UNK1: + case DECK_CMD_INIT_UNK2: + case DECK_CMD_INIT_UNK3: + return deck_req_nop(req->hdr.cmd); + + case DECK_CMD_GET_BOOT_FW_VERSION: + return deck_req_get_boot_fw_version(); + + case DECK_CMD_GET_APP_FW_VERSION: + return deck_req_get_app_fw_version(); + + case DECK_CMD_GET_BOARD_INFO: + return deck_req_get_board_info(); + + case DECK_CMD_READ: + return deck_req_read(); + + default: + dprintf("Deck Reader: Unhandled command %#02x\n", req->hdr.cmd); + + return S_OK; + } +} + +static HRESULT deck_req_get_boot_fw_version(void) { + struct deck_resp_get_boot_fw_version resp; + + dprintf("Deck Reader: Get Boot FW Version\n"); + + resp.hdr.sync = 0xE0; + resp.hdr.cmd = DECK_CMD_GET_BOOT_FW_VERSION; + resp.hdr.status = 0; + resp.hdr.nbytes = 1; + resp.boot_fw_version = 0x90; + + return deck_frame_encode(&deck_uart.readable, &resp, sizeof(resp)); +} + +static HRESULT deck_req_get_app_fw_version(void) { + struct deck_resp_get_app_fw_version resp; + + dprintf("Deck Reader: Get App FW Version\n"); + + resp.hdr.sync = 0xE0; + resp.hdr.cmd = DECK_CMD_GET_APP_FW_VERSION; + resp.hdr.status = 0; + resp.hdr.nbytes = 1; + resp.app_fw_version = 0x91; + + return deck_frame_encode(&deck_uart.readable, &resp, sizeof(resp)); +} + +static HRESULT deck_req_get_board_info(void) { + struct deck_resp_get_board_info resp; + + dprintf("Deck Reader: Get Board Info\n"); + + resp.hdr.sync = 0xE0; + resp.hdr.cmd = DECK_CMD_GET_BOARD_INFO; + resp.hdr.status = 0; + resp.hdr.nbytes = 9; + memcpy(resp.board, (void*)"837-15345", 9); + + return deck_frame_encode(&deck_uart.readable, &resp, sizeof(resp)); +} + +static HRESULT deck_req_read(void) { + struct deck_resp_read resp; + + dprintf("Deck Reader: Read Card\n"); + + resp.hdr.sync = 0xE0; + resp.hdr.cmd = DECK_CMD_READ; + resp.hdr.status = DECK_READ_START; + resp.hdr.nbytes = 0; + + ReleaseMutex(mutex); + WaitForSingleObject(mutex, INFINITE); + current_card_idx = 0; + read_pending = true; + + return deck_frame_encode(&deck_uart.readable, &resp.hdr, sizeof(resp.hdr)); +} + +static void deck_read_one(void) { + struct deck_resp_read resp; + + resp.hdr.sync = 0xE0; + resp.hdr.cmd = DECK_CMD_READ; + + if (current_card_idx < cards_ptr->nCards) { + resp.hdr.status = DECK_READ_DATA; + resp.hdr.nbytes = 44; + memcpy(resp.card_data, cards_ptr->cards[current_card_idx], 44); + dump(resp.card_data, 44); + + deck_frame_encode(&deck_uart.readable, &resp, sizeof(resp)); + current_card_idx++; + } else { + resp.hdr.status = DECK_READ_END; + resp.hdr.nbytes = 0; + deck_frame_encode(&deck_uart.readable, &resp.hdr, sizeof(resp.hdr)); + read_pending = false; + ReleaseMutex(mutex); + } +} + +static HRESULT deck_req_nop(uint8_t cmd) { + struct deck_resp_hdr resp; + + dprintf("Deck Reader: No-op cmd %#02x\n", cmd); + + resp.sync = 0xE0; + resp.cmd = cmd; + resp.status = 0; + resp.nbytes = 0; + + return deck_frame_encode(&deck_uart.readable, &resp, sizeof(resp)); +} + + +static void deck_frame_sync(struct iobuf* src) +{ + size_t i; + + for (i = 0; i < src->pos && src->bytes[i] != 0xE0; i++); + + src->pos -= i; + memmove(&src->bytes[0], &src->bytes[i], i); +} + +static HRESULT deck_frame_accept(const struct iobuf* dest) +{ + if (dest->pos < 2 || dest->pos != dest->bytes[2] + 4) { + return S_FALSE; + } + + return S_OK; +} + +static HRESULT deck_frame_decode(struct iobuf *dest, struct iobuf *src) { + uint8_t byte; + bool escape; + size_t i; + HRESULT hr; + + assert(dest != NULL); + assert(dest->bytes != NULL || dest->nbytes == 0); + assert(dest->pos <= dest->nbytes); + assert(src != NULL); + assert(src->bytes != NULL || src->nbytes == 0); + assert(src->pos <= src->nbytes); + + deck_frame_sync(src); + + dest->pos = 0; + escape = false; + + for (i = 0, hr = S_FALSE; i < src->pos && hr == S_FALSE; i++) { + /* Step the FSM to unstuff another byte */ + + byte = src->bytes[i]; + + if (dest->pos >= dest->nbytes) { + hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + else if (i == 0) { + dest->bytes[dest->pos++] = byte; + } + else if (byte == 0xE0) { + hr = E_FAIL; + } + else if (byte == 0xD0) { + if (escape) { + hr = E_FAIL; + } + + escape = true; + } + else if (escape) { + dest->bytes[dest->pos++] = byte + 1; + escape = false; + } + else { + dest->bytes[dest->pos++] = byte; + } + + /* Try to accept the packet we've built up so far */ + + if (SUCCEEDED(hr)) { + hr = deck_frame_accept(dest); + } + } + + /* Handle FSM terminal state */ + + if (hr != S_FALSE) { + /* Frame was either accepted or rejected, remove it from src */ + memmove(&src->bytes[0], &src->bytes[i], src->pos - i); + src->pos -= i; + } + + return hr; +} + +static HRESULT deck_frame_encode( + struct iobuf* dest, + const void* ptr, + size_t nbytes) +{ + const uint8_t* src; + uint8_t checksum; + uint8_t byte; + size_t i; + HRESULT hr; + + assert(dest != NULL); + assert(dest->bytes != NULL || dest->nbytes == 0); + assert(dest->pos <= dest->nbytes); + assert(ptr != NULL); + + src = ptr; + + assert(nbytes >= 2 && src[0] == 0xE0 && src[3] + 4 == nbytes); + + if (dest->pos >= dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = 0xE0; + checksum = 0x0; + + for (i = 1; i < nbytes; i++) { + byte = src[i]; + checksum += byte; + + hr = deck_frame_encode_byte(dest, byte); + + if (FAILED(hr)) { + return hr; + } + } + + return deck_frame_encode_byte(dest, checksum); +} + +static HRESULT deck_frame_encode_byte(struct iobuf* dest, uint8_t byte) +{ + if (byte == 0xD0 || byte == 0xE0) { + if (dest->pos + 2 > dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = 0xD0; + dest->bytes[dest->pos++] = byte - 1; + } + else { + if (dest->pos + 1 > dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = byte; + } + + return S_OK; +} diff --git a/games/fgohook/deck.h b/games/fgohook/deck.h new file mode 100644 index 0000000..c8e5f6d --- /dev/null +++ b/games/fgohook/deck.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#include + +struct deck_config { + bool enable; +}; + +HRESULT deck_hook_init(const struct deck_config *cfg, unsigned int port_no); diff --git a/games/fgohook/dllmain.c b/games/fgohook/dllmain.c new file mode 100644 index 0000000..fcf5132 --- /dev/null +++ b/games/fgohook/dllmain.c @@ -0,0 +1,183 @@ +/* + "Fate Grand/Order Arcade" (fgo) hook + + Devices + + USB: 837-15257 "Type 4" I/O Board + USB: 838-15405 "WinTouch" Controller Board + USB: 630-00008 Sinfonia CHC-C330 Printer + USB: 837-14509-02 USB-SER I/F BD Mini-B FTDI Board + connected to + 837-15093-06 LED Controller Board + COM1: 200-6275 VFD GP1232A02A FUTABA Board + COM2: 837-15345 RFID Deck Reader Noard + COM3: 837-15396 "Gen 3" Aime Reader + COM4: 837-15347 RFID Reader/Writer Board (inside the printer) +*/ + +#include + +#include + +#include "board/io4.h" +#include "board/led15093.h" +#include "board/sg-reader.h" +#include "board/vfd.h" + +#include "hook/process.h" + +#include "hooklib/dll.h" +#include "hooklib/dvd.h" +#include "hooklib/touch.h" +#include "hooklib/printer.h" +#include "hooklib/createprocess.h" +#include "hooklib/serial.h" +#include "hooklib/spike.h" + +#include "gfxhook/gfx.h" + +#include "fgohook/config.h" +#include "fgohook/io4.h" +#include "fgohook/fgo-dll.h" +#include "fgohook/deck.h" + +#include "platform/platform.h" + +#include "util/dprintf.h" +#include "util/env.h" + +static HMODULE fgo_hook_mod; +static process_entry_t fgo_startup; +static struct fgo_hook_config fgo_hook_cfg; + +static DWORD CALLBACK fgo_pre_startup(void) +{ + HRESULT hr; + HMODULE dbghelp; + + dprintf("--- Begin fgo_pre_startup ---\n"); + + /* Pin dbghelp so the path hooks apply to it. */ + + dbghelp = LoadLibraryW(L"dbghelp.dll"); + + if (dbghelp != NULL) { + dprintf("Pinned debug helper library, hMod=%p\n", dbghelp); + } + else { + dprintf("Failed to load debug helper library!\n"); + } + + /* Load config */ + + fgo_hook_config_load(&fgo_hook_cfg, get_config_path()); + + /* Hook Win32 APIs */ + + dvd_hook_init(&fgo_hook_cfg.dvd, fgo_hook_mod); + gfx_hook_init(&fgo_hook_cfg.gfx); + touch_screen_hook_init(&fgo_hook_cfg.touch, fgo_hook_mod); + serial_hook_init(); + + /* Hook external DLL APIs */ + + printer_hook_init(&fgo_hook_cfg.printer, 4, fgo_hook_mod); + if (fgo_hook_cfg.printer.enable) { + dll_hook_push(fgo_hook_mod, L"C330Ausb.dll"); + dll_hook_push(fgo_hook_mod, L"C330AFWDLusb.dll"); + } + + /* Initialize emulation hooks */ + + hr = platform_hook_init( + &fgo_hook_cfg.platform, + "SDEJ", + "ACA1", + fgo_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = sg_reader_hook_init(&fgo_hook_cfg.aime, 3, 3, fgo_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = vfd_hook_init(&fgo_hook_cfg.vfd, 1); + + if (FAILED(hr)) { + goto fail; + } + + hr = fgo_dll_init(&fgo_hook_cfg.dll, fgo_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = fgo_io4_hook_init(&fgo_hook_cfg.io4); + + if (FAILED(hr)) { + goto fail; + } + + hr = deck_hook_init(&fgo_hook_cfg.deck, 2); + + if (FAILED(hr)) { + goto fail; + } + + hr = ftdi_hook_init(&fgo_hook_cfg.ftdi, 17); + + if (FAILED(hr)) { + goto fail; + } + + unsigned int led_port_no[2] = {17, 0}; + hr = led15093_hook_init(&fgo_hook_cfg.led15093, + fgo_dll.led_init, fgo_dll.led_set_leds, led_port_no); + + if (FAILED(hr)) { + goto fail; + } + + hr = createprocess_push_hook_a("am/amdaemon.exe", "inject -d -k fgohook.dll ", "", false); + + if (FAILED(hr)) { + goto fail; + } + + /* Initialize debug helpers */ + + spike_hook_init(get_config_path()); + + dprintf("--- End fgo_pre_startup ---\n"); + + /* Jump to EXE start address */ + + return fgo_startup(); + +fail: + ExitProcess(EXIT_FAILURE); +} + +BOOL WINAPI DllMain(HMODULE mod, DWORD cause, void *ctx) +{ + HRESULT hr; + + if (cause != DLL_PROCESS_ATTACH) { + return TRUE; + } + + fgo_hook_mod = mod; + + hr = process_hijack_startup(fgo_pre_startup, &fgo_startup); + + if (!SUCCEEDED(hr)) { + dprintf("Failed to hijack process startup: %x\n", (int) hr); + } + + return SUCCEEDED(hr); +} diff --git a/games/fgohook/fgo-dll.c b/games/fgohook/fgo-dll.c new file mode 100644 index 0000000..991ec06 --- /dev/null +++ b/games/fgohook/fgo-dll.c @@ -0,0 +1,118 @@ +#include + +#include +#include + +#include "fgohook/fgo-dll.h" + +#include "util/dll-bind.h" +#include "util/dprintf.h" + +const struct dll_bind_sym fgo_dll_syms[] = { + { + .sym = "fgo_io_init", + .off = offsetof(struct fgo_dll, init), + }, { + .sym = "fgo_io_poll", + .off = offsetof(struct fgo_dll, poll), + }, { + .sym = "fgo_io_get_opbtns", + .off = offsetof(struct fgo_dll, get_opbtns), + }, { + .sym = "fgo_io_get_gamebtns", + .off = offsetof(struct fgo_dll, get_gamebtns), + }, { + .sym = "fgo_io_get_analogs", + .off = offsetof(struct fgo_dll, get_analogs), + }, { + .sym = "fgo_io_led_init", + .off = offsetof(struct fgo_dll, led_init), + }, { + .sym = "fgo_io_led_set_colors", + .off = offsetof(struct fgo_dll, led_set_leds), + } +}; + +struct fgo_dll fgo_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 fgo_dll_init(const struct fgo_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("Ongeki IO: Failed to load IO DLL: %lx: %S\n", + hr, + cfg->path); + + goto end; + } + + dprintf("Ongeki IO: Using custom IO DLL: %S\n", cfg->path); + src = owned; + } else { + owned = NULL; + src = self; + } + + get_api_version = (void *) GetProcAddress(src, "fgo_io_get_api_version"); + + if (get_api_version != NULL) { + fgo_dll.api_version = get_api_version(); + } else { + fgo_dll.api_version = 0x0100; + dprintf("Custom IO DLL does not expose fgo_io_get_api_version, " + "assuming API version 1.0.\n" + "Please ask the developer to update their DLL.\n"); + } + + if (fgo_dll.api_version >= 0x0200) { + hr = E_NOTIMPL; + dprintf("Ongeki IO: Custom IO DLL implements an unsupported " + "API version (%#04x). Please update Segatools.\n", + fgo_dll.api_version); + + goto end; + } + + sym = fgo_dll_syms; + hr = dll_bind(&fgo_dll, src, &sym, _countof(fgo_dll_syms)); + + if (FAILED(hr)) { + if (src != self) { + dprintf("Ongeki 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/games/fgohook/fgo-dll.h b/games/fgohook/fgo-dll.h new file mode 100644 index 0000000..b7e4f3d --- /dev/null +++ b/games/fgohook/fgo-dll.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include "fgoio/fgoio.h" + +struct fgo_dll { + uint16_t api_version; + HRESULT (*init)(void); + HRESULT (*poll)(void); + void (*get_opbtns)(uint8_t *opbtn); + void (*get_gamebtns)(uint8_t *gamebtn); + void (*get_analogs)(int16_t *stick_x, int16_t *stick_y); + HRESULT (*led_init)(void); + void (*led_set_leds)(uint8_t board, uint8_t *rgb); +}; + +struct fgo_dll_config { + wchar_t path[MAX_PATH]; +}; + +extern struct fgo_dll fgo_dll; + +HRESULT fgo_dll_init(const struct fgo_dll_config *cfg, HINSTANCE self); diff --git a/games/fgohook/fgohook.def b/games/fgohook/fgohook.def new file mode 100644 index 0000000..3e77ccc --- /dev/null +++ b/games/fgohook/fgohook.def @@ -0,0 +1,84 @@ +LIBRARY fgohook + +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 + fgo_io_get_api_version + fgo_io_get_gamebtns + fgo_io_get_analogs + fgo_io_get_opbtns + fgo_io_init + fgo_io_poll + fgo_io_led_init + fgo_io_led_set_colors + fwdlusb_open + fwdlusb_close + fwdlusb_listupPrinter + fwdlusb_listupPrinterSN + fwdlusb_selectPrinter + fwdlusb_selectPrinterSN + fwdlusb_getPrinterInfo + fwdlusb_status + fwdlusb_statusAll + fwdlusb_resetPrinter + fwdlusb_updateFirmware + fwdlusb_getFirmwareInfo + fwdlusb_MakeThread + fwdlusb_ReleaseThread + fwdlusb_AttachThreadCount + fwdlusb_getErrorLog + chcusb_MakeThread + chcusb_open + chcusb_close + chcusb_ReleaseThread + chcusb_listupPrinter + chcusb_listupPrinterSN + chcusb_selectPrinter + chcusb_selectPrinterSN + chcusb_getPrinterInfo + chcusb_imageformat=chcusb_imageformat_330 + chcusb_setmtf + chcusb_makeGamma + chcusb_setIcctable + chcusb_copies + chcusb_status + chcusb_statusAll + chcusb_startpage + chcusb_endpage + chcusb_write + chcusb_writeLaminate + chcusb_writeHolo + chcusb_setPrinterInfo + chcusb_getGamma + chcusb_getMtf + chcusb_cancelCopies + chcusb_setPrinterToneCurve + chcusb_getPrinterToneCurve + chcusb_blinkLED + chcusb_resetPrinter + chcusb_AttachThreadCount + chcusb_getPrintIDStatus + chcusb_setPrintStandby + chcusb_testCardFeed + chcusb_exitCard + chcusb_getCardRfidTID + chcusb_commCardRfidReader + chcusb_updateCardRfidReader + chcusb_getErrorLog + chcusb_getErrorStatus + chcusb_setCutList + chcusb_setLaminatePattern + chcusb_color_adjustment + chcusb_color_adjustmentEx + chcusb_getEEPROM + chcusb_setParameter + chcusb_getParameter + chcusb_universal_command diff --git a/games/fgohook/ftdi.c b/games/fgohook/ftdi.c new file mode 100644 index 0000000..e39a7c2 --- /dev/null +++ b/games/fgohook/ftdi.c @@ -0,0 +1,229 @@ +/* + SEGA 837-14509-02 USB -> Serial Adapter hook + + The 837-15093-06 LED controller is connected to the ALLS with an adapter. + This tiny board has a FTDI FT232BL chip, and is referenced in schematics as + "USB-SER I/F BD". + + The game queries the presence of the FTDI board itself, followed by a + registry check to see which port number is assigned to the FTDI board. + If these fail, the "CABINET LED" check on startup will always return "NG". + + Credits: + + OLEG +*/ + +#include +#include + +#include +#include +#include +#include + +#include "fgohook/ftdi.h" + +#include "hook/iohook.h" +#include "hook/table.h" + +#include "hooklib/setupapi.h" + +#include "util/dprintf.h" + +static struct ftdi_config ftdi_cfg; + + +static BOOL WINAPI hook_SetupDiGetDeviceRegistryPropertyA( + HDEVINFO DeviceInfoSet, + PSP_DEVINFO_DATA DeviceInfoData, + uint32_t Property, + uint32_t *PropertyRegDataType, + void *PropertyBuffer, + uint32_t PropertyBufferSize, + uint32_t *RequiredSize +); + +static HKEY WINAPI hook_SetupDiOpenDevRegKey( + HDEVINFO DeviceInfoSet, + PSP_DEVINFO_DATA DeviceInfoData, + uint32_t Scope, + uint32_t HwProfile, + uint32_t KeyType, + REGSAM samDesired +); + +static LSTATUS WINAPI hook_RegQueryValueExA( + HKEY handle, + const char *name, + void *reserved, + uint32_t *type, + void *bytes, + uint32_t *nbytes); + +static LSTATUS WINAPI hook_RegCloseKey(HKEY handle); + + +static BOOL (WINAPI *next_SetupDiGetDeviceRegistryPropertyA)( + HDEVINFO DeviceInfoSet, + PSP_DEVINFO_DATA DeviceInfoData, + uint32_t Property, + uint32_t *PropertyRegDataType, + void *PropertyBuffer, + uint32_t PropertyBufferSize, + uint32_t *RequiredSize +); + +static HKEY (WINAPI *next_SetupDiOpenDevRegKey)( + HDEVINFO DeviceInfoSet, + PSP_DEVINFO_DATA DeviceInfoData, + uint32_t Scope, + uint32_t HwProfile, + uint32_t KeyType, + REGSAM samDesired +); + +static LSTATUS (WINAPI *next_RegQueryValueExA)( + HKEY handle, + const char *name, + void *reserved, + uint32_t *type, + void *bytes, + uint32_t *nbytes); + +static LSTATUS (WINAPI *next_RegCloseKey)(HKEY handle); + + +static const struct hook_symbol setupapi_syms[] = { + { + .name = "SetupDiGetDeviceRegistryPropertyA", + .patch = hook_SetupDiGetDeviceRegistryPropertyA, + .link = (void *) &next_SetupDiGetDeviceRegistryPropertyA, + }, { + .name = "SetupDiOpenDevRegKey", + .patch = hook_SetupDiOpenDevRegKey, + .link = (void *) &next_SetupDiOpenDevRegKey, + } +}; + +static const struct hook_symbol reg_syms[] = { + { + .name = "RegQueryValueExA", + .patch = hook_RegQueryValueExA, + .link = (void *) &next_RegQueryValueExA, + }, { + .name = "RegCloseKey", + .patch = hook_RegCloseKey, + .link = (void *) &next_RegCloseKey, + } +}; + +const size_t device_fake_key = 0xDEADBEEF; + +static HANDLE ftdi_fd; +static char port_name[8]; + +HRESULT ftdi_hook_init(const struct ftdi_config *cfg, unsigned int port_no) { + HRESULT hr; + + assert(cfg != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + if (cfg->port_no != 0) { + port_no = cfg->port_no; + } + + hook_table_apply( + NULL, + "setupapi.dll", + setupapi_syms, + _countof(setupapi_syms)); + + hook_table_apply( + NULL, + "advapi32.dll", + reg_syms, + _countof(reg_syms)); + + memcpy(&ftdi_cfg, cfg, sizeof(*cfg)); + + hr = iohook_open_nul_fd(&ftdi_fd); + + if (FAILED(hr)) { + return hr; + } + + hr = setupapi_add_phantom_dev(&ftdi_guid, L"$ftdi"); + + if (FAILED(hr)) { + return hr; + } + + sprintf(port_name, "COM%d", port_no); + + dprintf("FTDI: Hook enabled.\n"); + return S_OK; +} + +static BOOL WINAPI hook_SetupDiGetDeviceRegistryPropertyA( + HDEVINFO DeviceInfoSet, + PSP_DEVINFO_DATA DeviceInfoData, + uint32_t Property, + uint32_t *PropertyRegDataType, + void *PropertyBuffer, + uint32_t PropertyBufferSize, + uint32_t *RequiredSize +) { + if (!PropertyBuffer || PropertyBufferSize == 0) { + *RequiredSize = 16; + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return FALSE; + } + + *PropertyRegDataType = 1; + return TRUE; +} + +static HKEY WINAPI hook_SetupDiOpenDevRegKey( + HDEVINFO DeviceInfoSet, + PSP_DEVINFO_DATA DeviceInfoData, + uint32_t Scope, + uint32_t HwProfile, + uint32_t KeyType, + REGSAM samDesired +) { + return (HKEY) device_fake_key; +} + +static LSTATUS WINAPI hook_RegCloseKey(HKEY handle) { + if (handle == (HKEY) device_fake_key) + return ERROR_SUCCESS; + + return next_RegCloseKey(handle); +} + +static LSTATUS WINAPI hook_RegQueryValueExA( + HKEY handle, + const char *name, + void *reserved, + uint32_t *type, + void *bytes, + uint32_t *nbytes) { + if (handle == (HKEY) device_fake_key && !strcmp(name, "PortName")) { + strcpy(bytes, port_name); + *nbytes = 5; + *type = 1; + return ERROR_SUCCESS; + } + + return next_RegQueryValueExA( + handle, + name, + reserved, + type, + bytes, + nbytes); +} diff --git a/games/fgohook/ftdi.h b/games/fgohook/ftdi.h new file mode 100644 index 0000000..18e5d73 --- /dev/null +++ b/games/fgohook/ftdi.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +#include +#include + +struct ftdi_config { + bool enable; + uint32_t port_no; +}; + +DEFINE_GUID( + ftdi_guid, + 0x86E0D1E0, + 0x8089, + 0x11D0, + 0x9C, 0xE4, 0x08, 0x00, 0x3E, 0x30, 0x1F, 0x73); + +HRESULT ftdi_hook_init(const struct ftdi_config *cfg, unsigned int port_no); diff --git a/games/fgohook/io4.c b/games/fgohook/io4.c new file mode 100644 index 0000000..67dfba4 --- /dev/null +++ b/games/fgohook/io4.c @@ -0,0 +1,104 @@ +#include + +#include +#include +#include + +#include "board/io4.h" + +#include "fgohook/fgo-dll.h" + +#include "util/dprintf.h" + +static HRESULT fgo_io4_poll(void *ctx, struct io4_state *state); +static uint16_t coins; + +static const struct io4_ops fgo_io4_ops = { + .poll = fgo_io4_poll, +}; + +HRESULT fgo_io4_hook_init(const struct io4_config *cfg) +{ + HRESULT hr; + + assert(fgo_dll.init != NULL); + + hr = io4_hook_init(cfg, &fgo_io4_ops, NULL); + + if (FAILED(hr)) { + return hr; + } + + return fgo_dll.init(); +} + +static HRESULT fgo_io4_poll(void *ctx, struct io4_state *state) +{ + uint8_t opbtn; + uint8_t gamebtn; + int16_t stick_x; + int16_t stick_y; + HRESULT hr; + + assert(fgo_dll.poll != NULL); + assert(fgo_dll.get_opbtns != NULL); + assert(fgo_dll.get_gamebtns != NULL); + assert(fgo_dll.get_analogs != NULL); + + memset(state, 0, sizeof(*state)); + + hr = fgo_dll.poll(); + + if (FAILED(hr)) { + return hr; + } + + opbtn = 0; + gamebtn = 0; + stick_x = 0; + stick_y = 0; + + fgo_dll.get_opbtns(&opbtn); + fgo_dll.get_gamebtns(&gamebtn); + fgo_dll.get_analogs(&stick_x, &stick_y); + + if (opbtn & FGO_IO_OPBTN_TEST) { + state->buttons[0] |= IO4_BUTTON_TEST; + } + + if (opbtn & FGO_IO_OPBTN_SERVICE) { + state->buttons[0] |= IO4_BUTTON_SERVICE; + } + + if (opbtn & FGO_IO_OPBTN_COIN) { + coins++; + } + state->chutes[0] = coins << 8; + + if (gamebtn & FGO_IO_GAMEBTN_SPEED_UP) { + state->buttons[0] |= 1 << 4; + } + + if (gamebtn & FGO_IO_GAMEBTN_TARGET) { + state->buttons[0] |= 1 << 5; + } + + if (gamebtn & FGO_IO_GAMEBTN_ATTACK) { + state->buttons[0] |= 1 << 1; + } + + if (gamebtn & FGO_IO_GAMEBTN_NOBLE_PHANTASM) { + state->buttons[0] |= 1 << 0; + } + + if (gamebtn & FGO_IO_GAMEBTN_CAMERA) { + state->buttons[0] |= 1 << 14; + } + + /* Stick x and y movement */ + + state->adcs[0] = 0x8000 - stick_x; + state->adcs[4] = 0x8000 + stick_y; + + return S_OK; +} diff --git a/games/fgohook/io4.h b/games/fgohook/io4.h new file mode 100644 index 0000000..9739e89 --- /dev/null +++ b/games/fgohook/io4.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "board/io4.h" + +HRESULT fgo_io4_hook_init(const struct io4_config *cfg); diff --git a/games/fgohook/meson.build b/games/fgohook/meson.build new file mode 100644 index 0000000..3d3e067 --- /dev/null +++ b/games/fgohook/meson.build @@ -0,0 +1,34 @@ +shared_library( + 'fgohook', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + vs_module_defs : 'fgohook.def', + dependencies : [ + capnhook.get_variable('hook_dep'), + capnhook.get_variable('hooklib_dep'), + xinput_lib, + ], + link_with : [ + aimeio_lib, + board_lib, + gfxhook_lib, + hooklib_lib, + fgoio_lib, + platform_lib, + util_lib, + ], + sources : [ + 'config.c', + 'config.h', + 'dllmain.c', + 'io4.c', + 'io4.h', + 'fgo-dll.c', + 'fgo-dll.h', + 'deck.c', + 'deck.h', + 'ftdi.c', + 'ftdi.h', + ], +) diff --git a/games/fgoio/backend.h b/games/fgoio/backend.h new file mode 100644 index 0000000..578bda4 --- /dev/null +++ b/games/fgoio/backend.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +#include "fgoio/fgoio.h" + +struct fgo_io_backend { + void (*get_gamebtns)(uint8_t *gamebtn); + void (*get_analogs)(int16_t *x, int16_t *y); +}; diff --git a/games/fgoio/config.c b/games/fgoio/config.c new file mode 100644 index 0000000..437b7be --- /dev/null +++ b/games/fgoio/config.c @@ -0,0 +1,58 @@ +#include + +#include +#include +#include + +#include "fgoio/config.h" + +#include + + +void fgo_kb_config_load( + struct fgo_kb_config *cfg, + const wchar_t *filename) { + + cfg->vk_attack = GetPrivateProfileIntW(L"keyboard", L"attack", ' ', filename); + cfg->vk_dash = GetPrivateProfileIntW(L"keyboard", L"dash", VK_LSHIFT, filename); + cfg->vk_target = GetPrivateProfileIntW(L"keyboard", L"target", 'J', filename); + cfg->vk_camera = GetPrivateProfileIntW(L"keyboard", L"camera", 'K', filename); + cfg->vk_np = GetPrivateProfileIntW(L"keyboard", L"np", 'L', filename); + + // Standard WASD + cfg->vk_right = GetPrivateProfileIntW(L"keyboard", L"right", 'D', filename); + cfg->vk_left = GetPrivateProfileIntW(L"keyboard", L"left", 'A', filename); + cfg->vk_down = GetPrivateProfileIntW(L"keyboard", L"down", 'S', filename); + cfg->vk_up = GetPrivateProfileIntW(L"keyboard", L"up", 'W', filename); +} + +void fgo_xi_config_load( + struct fgo_xi_config *cfg, + const wchar_t *filename) { + + cfg->stick_deadzone = GetPrivateProfileIntW(L"xinput", L"stickDeadzone", XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, filename); + +} + +void fgo_io_config_load( + struct fgo_io_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->vk_test = GetPrivateProfileIntW(L"io4", L"test", VK_F1, filename); + cfg->vk_service = GetPrivateProfileIntW(L"io4", L"service", VK_F2, filename); + cfg->vk_coin = GetPrivateProfileIntW(L"io4", L"coin", VK_F3, filename); + + GetPrivateProfileStringW( + L"io4", + L"mode", + L"xinput", + cfg->mode, + _countof(cfg->mode), + filename); + + fgo_xi_config_load(&cfg->xi, filename); + fgo_kb_config_load(&cfg->kb, filename); +} diff --git a/games/fgoio/config.h b/games/fgoio/config.h new file mode 100644 index 0000000..d824ff5 --- /dev/null +++ b/games/fgoio/config.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +#include + +struct fgo_kb_config { + uint8_t vk_np; + uint8_t vk_target; + uint8_t vk_dash; + uint8_t vk_attack; + uint8_t vk_camera; + uint8_t vk_right; + uint8_t vk_left; + uint8_t vk_down; + uint8_t vk_up; +}; + +struct fgo_xi_config { + uint16_t stick_deadzone; +}; + +struct fgo_io_config { + uint8_t vk_test; + uint8_t vk_service; + uint8_t vk_coin; + + wchar_t mode[12]; + struct fgo_kb_config kb; + struct fgo_xi_config xi; +}; + +void fgo_kb_config_load(struct fgo_kb_config *cfg, const wchar_t *filename); +void fgo_xi_config_load(struct fgo_xi_config *cfg, const wchar_t *filename); +void fgo_io_config_load( + struct fgo_io_config *cfg, + const wchar_t *filename); diff --git a/games/fgoio/fgoio.c b/games/fgoio/fgoio.c new file mode 100644 index 0000000..4817bc0 --- /dev/null +++ b/games/fgoio/fgoio.c @@ -0,0 +1,103 @@ +#include +#include +#include + +#include + +#include "fgoio/fgoio.h" + +#include + +#include "keyboard.h" +#include "xi.h" +#include "fgoio/config.h" +#include "util/dprintf.h" +#include "util/env.h" +#include "util/str.h" + +static uint8_t fgo_opbtn; +static uint8_t fgo_gamebtn; +static int16_t fgo_stick_x; +static int16_t fgo_stick_y; +static struct fgo_io_config fgo_io_cfg; +static const struct fgo_io_backend* fgo_io_backend; +static bool fgo_io_coin; + +uint16_t fgo_io_get_api_version(void) { + return 0x0100; +} + +HRESULT fgo_io_init(void) { + fgo_io_config_load(&fgo_io_cfg, get_config_path()); + + HRESULT hr; + + if (wstr_ieq(fgo_io_cfg.mode, L"keyboard")) { + hr = fgo_kb_init(&fgo_io_cfg.kb, &fgo_io_backend); + } else if (wstr_ieq(fgo_io_cfg.mode, L"xinput")) { + hr = fgo_xi_init(&fgo_io_cfg.xi, &fgo_io_backend); + } else { + hr = E_INVALIDARG; + dprintf("FGO IO: Invalid IO mode \"%S\", use keyboard or xinput\n", + fgo_io_cfg.mode); + } + + return hr; +} + +HRESULT fgo_io_poll(void) { + assert(fgo_io_backend != NULL); + + fgo_opbtn = 0; + fgo_gamebtn = 0; + fgo_stick_x = 0; + fgo_stick_y = 0; + + if (GetAsyncKeyState(fgo_io_cfg.vk_test) & 0x8000) { + fgo_opbtn |= FGO_IO_OPBTN_TEST; + } + + if (GetAsyncKeyState(fgo_io_cfg.vk_service) & 0x8000) { + fgo_opbtn |= FGO_IO_OPBTN_SERVICE; + } + + if (GetAsyncKeyState(fgo_io_cfg.vk_coin) & 0x8000) { + if (!fgo_io_coin) { + fgo_io_coin = true; + fgo_opbtn |= FGO_IO_OPBTN_COIN; + } + } else { + fgo_io_coin = false; + } + + return S_OK; +} + +void fgo_io_get_opbtns(uint8_t* opbtn) { + if (opbtn != NULL) { + *opbtn = fgo_opbtn; + } +} + +void fgo_io_get_gamebtns(uint8_t* btn) { + assert(fgo_io_backend != NULL); + assert(btn != NULL); + + fgo_io_backend->get_gamebtns(btn); +} + +void fgo_io_get_analogs(int16_t* stick_x, int16_t* stick_y) { + assert(fgo_io_backend != NULL); + assert(stick_x != NULL); + assert(stick_y != NULL); + + fgo_io_backend->get_analogs(stick_x, stick_y); +} + +HRESULT fgo_io_led_init(void) { + return S_OK; +} + +void fgo_io_led_set_colors(uint8_t board, uint8_t* rgb) { + return; +} diff --git a/games/fgoio/fgoio.h b/games/fgoio/fgoio.h new file mode 100644 index 0000000..65a11b0 --- /dev/null +++ b/games/fgoio/fgoio.h @@ -0,0 +1,82 @@ +#pragma once + +#include + +#include + +enum { + FGO_IO_OPBTN_TEST = 0x01, + FGO_IO_OPBTN_SERVICE = 0x02, + FGO_IO_OPBTN_COIN = 0x04, +}; + +enum { + FGO_IO_GAMEBTN_SPEED_UP = 0x01, + FGO_IO_GAMEBTN_TARGET = 0x02, + FGO_IO_GAMEBTN_ATTACK = 0x04, + FGO_IO_GAMEBTN_NOBLE_PHANTASM = 0x08, + FGO_IO_GAMEBTN_CAMERA = 0x10, +}; + +/* Get the version of the Fate Grand Order 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 fgo_io_get_api_version(void); + +/* Initialize the IO DLL. This is the second function that will be called on + your DLL, after fgo_io_get_api_version. + + All subsequent calls to this API may originate from arbitrary threads. + + Minimum API version: 0x0100 */ + +HRESULT fgo_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 fgo_io_poll(void); + +/* Get the state of the cabinet's operator buttons as of the last poll. See + FGO_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 fgo_io_get_opbtns(uint8_t *opbtn); + +/* Get the state of the cabinet's gameplay buttons as of the last poll. See + FGO_IO_GAMEBTN enum above: this contains bit mask definitions for button + states returned in *gamebtn. All buttons are active-high. + + Minimum API version: 0x0100 */ + +void fgo_io_get_gamebtns(uint8_t *gamebtn); + +/* Get the position of the cabinet stick as of the last poll. The center + position should be equal to or close to 32767. + + Minimum API version: 0x0100 */ + +void fgo_io_get_analogs(int16_t *stick_x, int16_t *stick_y); + +/* Initialize LED emulation. This function will be called before any + other fgo_io_led_*() function calls. + + All subsequent calls may originate from arbitrary threads and some may + overlap with each other. Ensuring synchronization inside your IO DLL is + your responsibility. */ + +HRESULT fgo_io_led_init(void); + +/* Update the RGB LEDs. + + Exact layout is TBD. */ + +void fgo_io_led_set_colors(uint8_t board, uint8_t *rgb); diff --git a/games/fgoio/keyboard.c b/games/fgoio/keyboard.c new file mode 100644 index 0000000..6d403dd --- /dev/null +++ b/games/fgoio/keyboard.c @@ -0,0 +1,82 @@ +#include + +#include +#include +#include +#include + +#include "fgoio/backend.h" +#include "fgoio/config.h" +#include "fgoio/fgoio.h" +#include "fgoio/keyboard.h" + +#include "util/dprintf.h" + +static void fgo_kb_get_gamebtns(uint8_t* gamebtn_out); +static void fgo_kb_get_analogs(int16_t* x, int16_t* y); + +static const struct fgo_io_backend fgo_kb_backend = { + .get_gamebtns = fgo_kb_get_gamebtns, + .get_analogs = fgo_kb_get_analogs +}; + +static struct fgo_kb_config config; + +HRESULT fgo_kb_init(const struct fgo_kb_config* cfg, const struct fgo_io_backend** backend) { + assert(cfg != NULL); + assert(backend != NULL); + + dprintf("Keyboard: Using keyboard input\n"); + *backend = &fgo_kb_backend; + config = *cfg; + + return S_OK; +} + +static void fgo_kb_get_gamebtns(uint8_t* gamebtn_out) { + assert(gamebtn_out != NULL); + + uint8_t gamebtn = 0; + + if (GetAsyncKeyState(config.vk_np) & 0x8000) { + gamebtn |= FGO_IO_GAMEBTN_NOBLE_PHANTASM; + } + + if (GetAsyncKeyState(config.vk_target) & 0x8000) { + gamebtn |= FGO_IO_GAMEBTN_TARGET; + } + + if (GetAsyncKeyState(config.vk_dash) & 0x8000) { + gamebtn |= FGO_IO_GAMEBTN_SPEED_UP; + } + + if (GetAsyncKeyState(config.vk_attack) & 0x8000) { + gamebtn |= FGO_IO_GAMEBTN_ATTACK; + } + + if (GetAsyncKeyState(config.vk_camera) & 0x8000) { + gamebtn |= FGO_IO_GAMEBTN_CAMERA; + } + + *gamebtn_out = gamebtn; +} + +static void fgo_kb_get_analogs(int16_t* x, int16_t* y) { + assert(x != NULL); + assert(y != NULL); + + if (GetAsyncKeyState(config.vk_left) & 0x8000) { + *x = SHRT_MIN + 1; + } else if (GetAsyncKeyState(config.vk_right) & 0x8000) { + *x = SHRT_MAX - 1; + } else { + *x = 0; + } + if (GetAsyncKeyState(config.vk_down) & 0x8000) { + *y = SHRT_MIN + 1; + } else if (GetAsyncKeyState(config.vk_up) & 0x8000) { + *y = SHRT_MAX - 1; + } else { + *y = 0; + } +} diff --git a/games/fgoio/keyboard.h b/games/fgoio/keyboard.h new file mode 100644 index 0000000..1b46a64 --- /dev/null +++ b/games/fgoio/keyboard.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +#include "fgoio/backend.h" +#include "fgoio/config.h" + +HRESULT fgo_kb_init(const struct fgo_kb_config *cfg, const struct fgo_io_backend **backend); diff --git a/games/fgoio/meson.build b/games/fgoio/meson.build new file mode 100644 index 0000000..5847686 --- /dev/null +++ b/games/fgoio/meson.build @@ -0,0 +1,20 @@ +fgoio_lib = static_library( + 'fgoio', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + dependencies : [ + xinput_lib, + ], + sources : [ + 'fgoio.c', + 'fgoio.h', + 'config.c', + 'config.h', + 'backend.h', + 'keyboard.c', + 'keyboard.h', + 'xi.c', + 'xi.h', + ], +) diff --git a/games/fgoio/xi.c b/games/fgoio/xi.c new file mode 100644 index 0000000..91a78a8 --- /dev/null +++ b/games/fgoio/xi.c @@ -0,0 +1,132 @@ +#include +#include + +#include +#include +#include + +#include "fgoio/backend.h" +#include "fgoio/config.h" +#include "fgoio/fgoio.h" +#include "fgoio/xi.h" + +#include "util/dprintf.h" + +static void fgo_xi_get_gamebtns(uint8_t* gamebtn_out); +static void fgo_xi_get_analogs(int16_t* x, int16_t* y); +static HRESULT fgo_xi_config_apply(const struct fgo_xi_config* cfg); + +static const struct fgo_io_backend fgo_xi_backend = { + .get_gamebtns = fgo_xi_get_gamebtns, + .get_analogs = fgo_xi_get_analogs +}; + +static float fgo_xi_stick_deadzone; + +const uint16_t max_stick_value = 32767; + +HRESULT fgo_xi_init(const struct fgo_xi_config* cfg, const struct fgo_io_backend** backend) { + assert(cfg != NULL); + assert(backend != NULL); + + HRESULT hr = fgo_xi_config_apply(cfg); + + if (FAILED(hr)) { + return hr; + } + + dprintf("XInput: Using XInput controller\n"); + *backend = &fgo_xi_backend; + + return S_OK; +} + +static HRESULT fgo_xi_config_apply(const struct fgo_xi_config* cfg) { + /* Deadzone check */ + if (cfg->stick_deadzone > 32767 || cfg->stick_deadzone < 0) { + dprintf("XInput: Stick deadzone is too large or negative\n"); + return E_INVALIDARG; + } + + dprintf("XInput: --- Begin configuration ---\n"); + dprintf("XInput: Left Deadzone . . . . : %i\n", cfg->stick_deadzone); + dprintf("XInput: --- End configuration ---\n"); + + fgo_xi_stick_deadzone = cfg->stick_deadzone; + + return S_OK; +} + +static void fgo_xi_get_gamebtns(uint8_t* gamebtn_out) { + assert(gamebtn_out != NULL); + + XINPUT_STATE xi; + memset(&xi, 0, sizeof(xi)); + XInputGetState(0, &xi); + + uint8_t gamebtn = 0; + WORD xb = xi.Gamepad.wButtons; + + if (xi.Gamepad.bLeftTrigger > 64) { + gamebtn |= FGO_IO_GAMEBTN_SPEED_UP; + } + + if (xb & XINPUT_GAMEPAD_LEFT_SHOULDER) { + gamebtn |= FGO_IO_GAMEBTN_TARGET; + } + + if (xb & XINPUT_GAMEPAD_A || xb & XINPUT_GAMEPAD_B) { + gamebtn |= FGO_IO_GAMEBTN_ATTACK; + } + + if (xb & XINPUT_GAMEPAD_Y || xb & XINPUT_GAMEPAD_X) { + gamebtn |= FGO_IO_GAMEBTN_NOBLE_PHANTASM; + } + + if (xb & XINPUT_GAMEPAD_LEFT_THUMB) { + gamebtn |= FGO_IO_GAMEBTN_CAMERA; + } + + *gamebtn_out = gamebtn; +} + +static void fgo_xi_get_analogs(int16_t* x, int16_t* y) { + + assert(x != NULL); + assert(y != NULL); + + XINPUT_STATE xi; + memset(&xi, 0, sizeof(xi)); + XInputGetState(0, &xi); + + float LX = xi.Gamepad.sThumbLX; + float LY = xi.Gamepad.sThumbLY; + + // determine how far the controller is pushed + float magnitude = sqrt(LX * LX + LY * LY); + + // determine the direction the controller is pushed + float normalizedLX = LX / magnitude; + float normalizedLY = LY / magnitude; + + float normalizedMagnitude = 0; + + // check if the controller is outside a circular dead zone + if (magnitude > fgo_xi_stick_deadzone) { + // clip the magnitude at its expected maximum value + if (magnitude > 32767) magnitude = 32767; + + // adjust magnitude relative to the end of the dead zone + magnitude -= fgo_xi_stick_deadzone; + + // optionally normalize the magnitude with respect to its expected range + // giving a magnitude value of 0.0 to 1.0 + normalizedMagnitude = magnitude / (32767 - fgo_xi_stick_deadzone); + } else // if the controller is in the deadzone zero out the magnitude + { + normalizedMagnitude = 0; + } + + *x = (int16_t) (normalizedLX * normalizedMagnitude * 32767); + *y = (int16_t) (normalizedLY * normalizedMagnitude * 32767); +} diff --git a/games/fgoio/xi.h b/games/fgoio/xi.h new file mode 100644 index 0000000..ebb8859 --- /dev/null +++ b/games/fgoio/xi.h @@ -0,0 +1,10 @@ +#pragma once + +/* Can't call this xinput.h or it will conflict with */ + +#include + +#include "fgoio/backend.h" +#include "fgoio/config.h" + +HRESULT fgo_xi_init(const struct fgo_xi_config *cfg, const struct fgo_io_backend **backend); diff --git a/games/idachook/config.c b/games/idachook/config.c new file mode 100644 index 0000000..0e8dc88 --- /dev/null +++ b/games/idachook/config.c @@ -0,0 +1,105 @@ +#include +#include +#include + +#include "board/config.h" +#include "board/sg-reader.h" + +#include "hooklib/config.h" +#include "hooklib/dvd.h" + +#include "idachook/config.h" +#include "idachook/idac-dll.h" + +#include "platform/config.h" +#include "platform/platform.h" + + +void led15070_config_load(struct led15070_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + wchar_t tmpstr[16]; + + cfg->enable = GetPrivateProfileIntW(L"led15070", L"enable", 1, filename); + cfg->port_no[0] = GetPrivateProfileIntW(L"led15070", L"portNo1", 0, filename); + cfg->port_no[1] = GetPrivateProfileIntW(L"led15070", L"portNo2", 0, filename); + cfg->fw_ver = GetPrivateProfileIntW(L"led15070", L"fwVer", 0x90, filename); + /* TODO: Unknown, no firmware file available */ + cfg->fw_sum = GetPrivateProfileIntW(L"led15070", L"fwSum", 0x0000, filename); + + GetPrivateProfileStringW( + L"led15070", + L"boardNumber", + L"15070-02", + tmpstr, + _countof(tmpstr), + filename); + + size_t n = wcstombs(cfg->board_number, tmpstr, sizeof(cfg->board_number)); + for (int i = n; i < sizeof(cfg->board_number); i++) + { + cfg->board_number[i] = ' '; + } + + GetPrivateProfileStringW( + L"led15070", + L"eepromPath", + L"DEVICE", + cfg->eeprom_path, + _countof(cfg->eeprom_path), + filename); +} + +void idac_dll_config_load( + struct idac_dll_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + GetPrivateProfileStringW( + L"idacio", + L"path", + L"", + cfg->path, + _countof(cfg->path), + filename); +} + +void indrun_config_load( + struct indrun_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"indrun", L"enable", 1, filename); +} + +void idac_hook_config_load( + struct idac_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); + idac_dll_config_load(&cfg->dll, filename); + zinput_config_load(&cfg->zinput, filename); + dvd_config_load(&cfg->dvd, filename); + io4_config_load(&cfg->io4, filename); + ffb_config_load(&cfg->ffb, filename); + led15070_config_load(&cfg->led15070, filename); + indrun_config_load(&cfg->indrun, filename); +} + +void zinput_config_load(struct zinput_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"zinput", L"enable", 1, filename); +} diff --git a/games/idachook/config.h b/games/idachook/config.h new file mode 100644 index 0000000..baae2e5 --- /dev/null +++ b/games/idachook/config.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +#include "board/config.h" +#include "board/led15070.h" + +#include "hooklib/dvd.h" + +#include "idachook/idac-dll.h" +#include "idachook/zinput.h" +#include "idachook/indrun.h" + +#include "platform/platform.h" + +struct idac_hook_config { + struct platform_config platform; + struct aime_config aime; + struct dvd_config dvd; + struct io4_config io4; + struct ffb_config ffb; + struct idac_dll_config dll; + struct zinput_config zinput; + struct led15070_config led15070; + struct indrun_config indrun; +}; + +void idac_dll_config_load( + struct idac_dll_config *cfg, + const wchar_t *filename); + +void idac_hook_config_load( + struct idac_hook_config *cfg, + const wchar_t *filename); + +void zinput_config_load( + struct zinput_config *cfg, + const wchar_t *filename); + +void indrun_config_load( + struct indrun_config *cfg, + const wchar_t *filename); diff --git a/games/idachook/dllmain.c b/games/idachook/dllmain.c new file mode 100644 index 0000000..448ea5b --- /dev/null +++ b/games/idachook/dllmain.c @@ -0,0 +1,163 @@ +/* + "Initial D THE ARCADE" (idac) hook + + Devices + + USB: 837-15257 "Type 4" I/O Board + COM1: 838-15069 MOTOR DRIVE BD RS232/422 Board + COM2: 837-15070-02 IC BD LED Controller Board (DIPSW2 OFF) + OR + 837-15070-04 IC BD LED Controller Board (DIPSW2 ON) + COM3: 837-15286 "Gen 2" Aime Reader (DIPSW2 OFF) + OR + 837-15396 "Gen 3" Aime Reader (DIPSW2 ON) +*/ + +#include +#include + +#include + +#include "board/sg-reader.h" +#include "board/io4.h" + +#include "hook/process.h" + +#include "hooklib/dvd.h" +#include "hooklib/serial.h" +#include "hooklib/spike.h" + +#include "idachook/config.h" +#include "idachook/idac-dll.h" +#include "idachook/io4.h" +#include "idachook/ffb.h" +#include "idachook/zinput.h" + +#include "platform/platform.h" + +#include "util/dprintf.h" +#include "util/env.h" + +static HMODULE idac_hook_mod; +static process_entry_t idac_startup; +static struct idac_hook_config idac_hook_cfg; + +static DWORD CALLBACK idac_pre_startup(void) +{ + HRESULT hr; + + dprintf("--- Begin idac_pre_startup ---\n"); + + /* Config load */ + + idac_hook_config_load(&idac_hook_cfg, get_config_path()); + + /* Hook Win32 APIs */ + + serial_hook_init(); + zinput_hook_init(&idac_hook_cfg.zinput); + dvd_hook_init(&idac_hook_cfg.dvd, idac_hook_mod); + + /* Initialize emulation hooks */ + + struct dipsw_config new_dipsw_config[8] = { + {L"Delivery Server", L"Server", L"Client"}, + {L"Cabinet Type", L"SWDC CVT", L"DZero CVT"}, + {L"Single Seat", L"ON", L"OFF"}, + {L"Seat Setting 1", L"ON", L"OFF"}, + {L"Seat Setting 2", L"ON", L"OFF"}, + }; + + // Set the system dip switch configuration + memcpy(idac_hook_cfg.platform.system.dipsw_config, new_dipsw_config, + sizeof(new_dipsw_config)); + + hr = platform_hook_init( + &idac_hook_cfg.platform, + "SDGT", + "ACA4", + idac_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + bool *dipsw = &idac_hook_cfg.platform.system.dipsw[0]; + bool is_swdc_cvt = dipsw[1]; + + if (!dipsw[2]) { + // the next two bit are the seat number most significant bit first + dprintf("System: Seat Number: %d\n", ((dipsw[4] << 1) | dipsw[3]) + 1); + } + + hr = idac_dll_init(&idac_hook_cfg.dll, idac_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = sg_reader_hook_init(&idac_hook_cfg.aime, 3, is_swdc_cvt ? 3 : 2, idac_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = idac_io4_hook_init(&idac_hook_cfg.io4); + + if (FAILED(hr)) { + goto fail; + } + + hr = idac_ffb_hook_init(&idac_hook_cfg.ffb, 1); + + if (FAILED(hr)) { + goto fail; + } + + unsigned int led_port_no[2] = {2, 0}; + hr = led15070_hook_init(&idac_hook_cfg.led15070, idac_dll.led_init, + idac_dll.led_set_fet_output, NULL, idac_dll.led_gs_update, led_port_no); + + if (FAILED(hr)) { + goto fail; + } + + /* Initialize native plugin DLL hooks + + There seems to be an issue with other DLL hooks if `LoadLibraryW` is + hooked earlier in the initialization. */ + + indrun_hook_init(&idac_hook_cfg.indrun); + + /* Initialize debug helpers */ + + spike_hook_init(get_config_path()); + + dprintf("--- End idac_pre_startup ---\n"); + + /* Jump to EXE start address */ + + return idac_startup(); + +fail: + ExitProcess(EXIT_FAILURE); +} + +BOOL WINAPI DllMain(HMODULE mod, DWORD cause, void *ctx) +{ + HRESULT hr; + + if (cause != DLL_PROCESS_ATTACH) { + return TRUE; + } + + idac_hook_mod = mod; + + hr = process_hijack_startup(idac_pre_startup, &idac_startup); + + if (!SUCCEEDED(hr)) { + dprintf("Failed to hijack process startup: %x\n", (int) hr); + } + + return SUCCEEDED(hr); +} diff --git a/games/idachook/ffb.c b/games/idachook/ffb.c new file mode 100644 index 0000000..8befaa6 --- /dev/null +++ b/games/idachook/ffb.c @@ -0,0 +1,59 @@ +#include + +#include +#include +#include +#include + +#include "board/ffb.h" + +#include "idachook/idac-dll.h" + +#include "util/dprintf.h" + +static void idac_ffb_toggle(bool active); +static void idac_ffb_constant_force(uint8_t direction, uint8_t force); +static void idac_ffb_rumble(uint8_t force, uint8_t period); +static void idac_ffb_damper(uint8_t force); + +static const struct ffb_ops idac_ffb_ops = { + .toggle = idac_ffb_toggle, + .constant_force = idac_ffb_constant_force, + .rumble = idac_ffb_rumble, + .damper = idac_ffb_damper +}; + +HRESULT idac_ffb_hook_init(const struct ffb_config *cfg, unsigned int port_no) +{ + HRESULT hr; + + assert(idac_dll.init != NULL); + + hr = ffb_hook_init(cfg, &idac_ffb_ops, port_no); + + if (FAILED(hr)) { + return hr; + } + + return idac_dll.ffb_init(); +} + +static void idac_ffb_toggle(bool active) +{ + idac_dll.ffb_toggle(active); +} + +static void idac_ffb_constant_force(uint8_t direction, uint8_t force) +{ + idac_dll.ffb_constant_force(direction, force); +} + +static void idac_ffb_rumble(uint8_t force, uint8_t period) +{ + idac_dll.ffb_rumble(force, period); +} + +static void idac_ffb_damper(uint8_t force) +{ + idac_dll.ffb_damper(force); +} diff --git a/games/idachook/ffb.h b/games/idachook/ffb.h new file mode 100644 index 0000000..aff0d2d --- /dev/null +++ b/games/idachook/ffb.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "board/ffb.h" + +HRESULT idac_ffb_hook_init(const struct ffb_config *cfg, unsigned int port_no); diff --git a/games/idachook/idac-dll.c b/games/idachook/idac-dll.c new file mode 100644 index 0000000..224bca6 --- /dev/null +++ b/games/idachook/idac-dll.c @@ -0,0 +1,139 @@ +#include + +#include +#include + +#include "idachook/idac-dll.h" + +#include "util/dll-bind.h" +#include "util/dprintf.h" + +const struct dll_bind_sym idac_dll_syms[] = { + { + .sym = "idac_io_init", + .off = offsetof(struct idac_dll, init), + }, { + .sym = "idac_io_get_opbtns", + .off = offsetof(struct idac_dll, get_opbtns), + }, { + .sym = "idac_io_get_gamebtns", + .off = offsetof(struct idac_dll, get_gamebtns), + }, { + .sym = "idac_io_get_shifter", + .off = offsetof(struct idac_dll, get_shifter), + }, { + .sym = "idac_io_get_analogs", + .off = offsetof(struct idac_dll, get_analogs), + }, { + .sym = "idac_io_led_init", + .off = offsetof(struct idac_dll, led_init), + }, { + .sym = "idac_io_led_set_fet_output", + .off = offsetof(struct idac_dll, led_set_fet_output), + }, { + .sym = "idac_io_led_gs_update", + .off = offsetof(struct idac_dll, led_gs_update), + }, { + .sym = "idac_io_led_set_leds", + .off = offsetof(struct idac_dll, led_set_leds), + }, { + .sym = "idac_io_ffb_init", + .off = offsetof(struct idac_dll, ffb_init), + }, { + .sym = "idac_io_ffb_toggle", + .off = offsetof(struct idac_dll, ffb_toggle), + }, { + .sym = "idac_io_ffb_constant_force", + .off = offsetof(struct idac_dll, ffb_constant_force), + }, { + .sym = "idac_io_ffb_rumble", + .off = offsetof(struct idac_dll, ffb_rumble), + }, { + .sym = "idac_io_ffb_damper", + .off = offsetof(struct idac_dll, ffb_damper), + } +}; + +struct idac_dll idac_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 idac_dll_init(const struct idac_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("IDAC IO: Failed to load IO DLL: %lx: %S\n", + hr, + cfg->path); + + goto end; + } + + dprintf("IDAC IO: Using custom IO DLL: %S\n", cfg->path); + src = owned; + } else { + owned = NULL; + src = self; + } + + get_api_version = (void *) GetProcAddress(src, "idac_io_get_api_version"); + + if (get_api_version != NULL) { + idac_dll.api_version = get_api_version(); + } else { + idac_dll.api_version = 0x0100; + dprintf("Custom IO DLL does not expose idac_io_get_api_version, " + "assuming API version 1.0.\n" + "Please ask the developer to update their DLL.\n"); + } + + if (idac_dll.api_version >= 0x0200) { + hr = E_NOTIMPL; + dprintf("IDAC IO: Custom IO DLL implements an unsupported " + "API version (%#04x). Please update Segatools.\n", + idac_dll.api_version); + + goto end; + } + + sym = idac_dll_syms; + hr = dll_bind(&idac_dll, src, &sym, _countof(idac_dll_syms)); + + if (FAILED(hr)) { + if (src != self) { + dprintf("IDAC 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/games/idachook/idac-dll.h b/games/idachook/idac-dll.h new file mode 100644 index 0000000..92aa664 --- /dev/null +++ b/games/idachook/idac-dll.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +#include "idacio/idacio.h" + +struct idac_dll { + uint16_t api_version; + HRESULT (*init)(void); + void (*get_opbtns)(uint8_t *opbtn); + void (*get_gamebtns)(uint8_t *gamebtn); + void (*get_shifter)(uint8_t *gear); + void (*get_analogs)(struct idac_io_analog_state *out); + HRESULT (*led_init)(void); + void (*led_set_fet_output)(uint8_t board, const uint8_t *rgb); + void (*led_gs_update)(uint8_t board, const uint8_t *rgb); + void (*led_set_leds)(uint8_t board, const uint8_t *rgb); + HRESULT (*ffb_init)(void); + void (*ffb_toggle)(bool active); + void (*ffb_constant_force)(uint8_t direction, uint8_t force); + void (*ffb_rumble)(uint8_t period, uint8_t force); + void (*ffb_damper)(uint8_t force); +}; + +struct idac_dll_config { + wchar_t path[MAX_PATH]; +}; + +extern struct idac_dll idac_dll; + +HRESULT idac_dll_init(const struct idac_dll_config *cfg, HINSTANCE self); diff --git a/games/idachook/idachook.def b/games/idachook/idachook.def new file mode 100644 index 0000000..4653c37 --- /dev/null +++ b/games/idachook/idachook.def @@ -0,0 +1,28 @@ +LIBRARY idachook + +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 + idac_io_get_api_version + idac_io_init + idac_io_get_opbtns + idac_io_get_gamebtns + idac_io_get_shifter + idac_io_get_analogs + idac_io_led_init + idac_io_led_set_fet_output + idac_io_led_gs_update + idac_io_led_set_leds + idac_io_ffb_init + idac_io_ffb_toggle + idac_io_ffb_constant_force + idac_io_ffb_rumble + idac_io_ffb_damper diff --git a/games/idachook/indrun.c b/games/idachook/indrun.c new file mode 100644 index 0000000..758a677 --- /dev/null +++ b/games/idachook/indrun.c @@ -0,0 +1,261 @@ +#include +#include +#include + +#include "hook/table.h" + +#include "hooklib/dll.h" + +#include "util/dprintf.h" + +#include "indrun.h" + +static const wchar_t *target_modules[] = { + L"IndRun.dll", +}; + +static const size_t target_modules_len = _countof(target_modules); + +static void dll_hook_insert_hooks(HMODULE target); +static void app_hook_insert_hooks(HMODULE target); + +static HMODULE WINAPI hook_LoadLibraryW(const wchar_t *name); +static HMODULE (WINAPI *next_LoadLibraryW)(const wchar_t *name); + +static int WINAPI hook_GetSystemMetrics(int nIndex); +static int (WINAPI *next_GetSystemMetrics)(int nIndex); + +static BOOL WINAPI hook_GetComputerNameW(LPWSTR lpBuffer, LPDWORD nSize); +static DWORD WINAPI hook_GetCurrentDirectoryW( DWORD nBufferLength, LPWSTR lpBuffer); +static BOOL WINAPI hook_GetVersionExW(LPOSVERSIONINFOW lpVersionInformation); +static int (WINAPI *next_GetVersionExW)(LPOSVERSIONINFOW lpVersionInformation); +static BOOL WINAPI hook_VerifyVersionInfoW(LPOSVERSIONINFOEXW lpVersionInformation, DWORD dwTypeMask, DWORDLONG dwlConditionMask); +static BOOL (WINAPI *next_VerifyVersionInfoW)(LPOSVERSIONINFOEXW lpVersionInformation, DWORD dwTypeMask, DWORDLONG dwlConditionMask); +static BOOL WINAPI hook_K32EnumProcesses(DWORD *lpidProcess, DWORD cb, LPDWORD lpcbNeeded); +static BOOL (WINAPI *next_K32EnumProcesses)(DWORD *lpidProcess, DWORD cb, LPDWORD lpcbNeeded); + +static BOOL WINAPI hook_GetUserNameW(LPWSTR lpBuffer, LPDWORD pcbBuffer); + +static const struct hook_symbol idac_app_user32_syms[] = { + { + .name = "GetSystemMetrics", + .patch = hook_GetSystemMetrics, + .link = (void **) &next_GetSystemMetrics, + } +}; + +static const struct hook_symbol idac_app_kernel32_syms[] = { + { + .name = "GetComputerNameW", + .patch = hook_GetComputerNameW, + }, + { + .name = "GetCurrentDirectoryW", + .patch = hook_GetCurrentDirectoryW, + }, + { + .name = "GetVersionExW", + .patch = hook_GetVersionExW, + .link = (void **) &next_GetVersionExW, + }, + { + .name = "VerifyVersionInfoW", + .patch = hook_VerifyVersionInfoW, + .link = (void **) &next_VerifyVersionInfoW, + }, + { + .name = "K32EnumProcesses", + .patch = hook_K32EnumProcesses, + .link = (void **) &next_K32EnumProcesses, + } +}; + +static const struct hook_symbol idac_app_advapi32_syms[] = { + { + .name = "GetUserNameW", + .patch = hook_GetUserNameW, + } +}; + +static const struct hook_symbol indrun_kernel32_syms[] = { + { + .name = "LoadLibraryW", + .patch = hook_LoadLibraryW, + .link = (void **) &next_LoadLibraryW, + } +}; + +void indrun_hook_init(struct indrun_config *cfg) +{ + assert(cfg != NULL); + + if (!cfg->enable) { + return; + } + + dprintf("IDAC: Hooks enabled.\n"); + + // GameProject-Win64-Shipping.exe hooks + app_hook_insert_hooks(NULL); + + // IndRun.dll hooks + dll_hook_insert_hooks(NULL); +} + +static void dll_hook_insert_hooks(HMODULE target) { + hook_table_apply( + target, + "kernel32.dll", + indrun_kernel32_syms, + _countof(indrun_kernel32_syms)); +} + +void app_hook_insert_hooks(HMODULE target) { + hook_table_apply( + target, + "user32.dll", + idac_app_user32_syms, + _countof(idac_app_user32_syms)); + + hook_table_apply( + target, + "kernel32.dll", + idac_app_kernel32_syms, + _countof(idac_app_kernel32_syms)); + + hook_table_apply( + target, + "advapi32.dll", + idac_app_advapi32_syms, + _countof(idac_app_advapi32_syms)); +} + +static HMODULE WINAPI hook_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("IDAC: Hooked %S\n", target_module); + + dll_hook_insert_hooks(result); + app_hook_insert_hooks(result); + } + } + + return result; +} + +static int WINAPI hook_GetSystemMetrics(int nIndex) { + int ret = next_GetSystemMetrics(nIndex); + + // Disable mouse buttons detection + if (nIndex == SM_CMOUSEBUTTONS) { + dprintf("IDAC: GetSystemMetrics(%d) -> 0\n", nIndex); + return 0; + } + + return ret; +} + +static BOOL WINAPI hook_GetComputerNameW(LPWSTR lpBuffer, LPDWORD nSize) { + dprintf("IDAC: GetComputerNameW -> ACAE01A99999999\n"); + + // Fake the computer name as ACAE01A999999999 + wcscpy(lpBuffer, L"ACAE01A999999999"); + *nSize = _countof(L"ACAE01A99999999"); + + return TRUE; +} + +static DWORD WINAPI hook_GetCurrentDirectoryW(DWORD nBufferLength, LPWSTR lpBuffer) { + dprintf("IDAC: GetCurrentDirectoryW -> X:\\\n"); + + // Fake the current diretory as X: + wcscpy(lpBuffer, L"X"); + return 1; +} + +static BOOL WINAPI hook_GetVersionExW(LPOSVERSIONINFOW lpVersionInformation) { + int result = next_GetVersionExW(lpVersionInformation); + + // Fake the version as Windows 10 1809 + if (result) { + dprintf("IDAC: GetVersionExW -> Windows 10 1809\n"); + lpVersionInformation->dwMajorVersion = 10; + lpVersionInformation->dwMinorVersion = 0; + lpVersionInformation->dwBuildNumber = 17763; + return TRUE; + } + + return result; +} + +static BOOL WINAPI hook_GetUserNameW(LPWSTR lpBuffer, LPDWORD pcbBuffer) { + dprintf("IDAC: GetUserNameW -> AppUser\n"); + + // Fake the user name as AppUser + wcscpy(lpBuffer, L"AppUser"); + *pcbBuffer = _countof(L"AppUser"); + + return TRUE; +} + +static BOOL WINAPI hook_VerifyVersionInfoW(LPOSVERSIONINFOEXW lpVersionInformation, DWORD dwTypeMask, DWORDLONG dwlConditionMask) { + BOOL result = next_VerifyVersionInfoW(lpVersionInformation, dwTypeMask, dwlConditionMask); + + // Fake the version as Windows 10 1809 + if (lpVersionInformation->dwBuildNumber == 17763) { + dprintf("IDAC: VerifyVersionInfoW -> Windows 10 1809\n"); + return TRUE; + } + + return result; +} + +static BOOL WINAPI hook_K32EnumProcesses(DWORD *lpidProcess, DWORD cb, LPDWORD lpcbNeeded) { + BOOL result = next_K32EnumProcesses(lpidProcess, cb, lpcbNeeded); + + // Rteurn an empy process list + dprintf("IDAC: K32EnumProcesses -> NULL\n"); + lpidProcess = NULL; + *lpcbNeeded = 0; + + return TRUE; +} diff --git a/games/idachook/indrun.h b/games/idachook/indrun.h new file mode 100644 index 0000000..4605285 --- /dev/null +++ b/games/idachook/indrun.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +struct indrun_config { + bool enable; +}; + +void indrun_hook_init(struct indrun_config *cfg); diff --git a/games/idachook/io4.c b/games/idachook/io4.c new file mode 100644 index 0000000..3861ef8 --- /dev/null +++ b/games/idachook/io4.c @@ -0,0 +1,165 @@ +#include + +#include +#include +#include + +#include "board/io4.h" + +#include "idachook/idac-dll.h" + +#include "util/dprintf.h" + +static HRESULT idac_io4_poll(void *ctx, struct io4_state *state); +static HRESULT idac_io4_write_gpio(uint8_t* payload, size_t len); +static uint16_t coins; + +static const struct io4_ops idac_io4_ops = { + .poll = idac_io4_poll, + .write_gpio = idac_io4_write_gpio +}; + +static const uint16_t idac_gear_signals[] = { + /* Neutral */ + 0x0000, + /* 1: Left|Up */ + 0x0028, + /* 2: Left|Down */ + 0x0018, + /* 3: Up */ + 0x0020, + /* 4: Down */ + 0x0010, + /* 5: Right|Up */ + 0x0024, + /* 6: Right|Down */ + 0x0014, +}; + +HRESULT idac_io4_hook_init(const struct io4_config *cfg) +{ + HRESULT hr; + + assert(idac_dll.init != NULL); + + hr = io4_hook_init(cfg, &idac_io4_ops, NULL); + + if (FAILED(hr)) { + return hr; + } + + return idac_dll.init(); +} + +static HRESULT idac_io4_poll(void *ctx, struct io4_state *state) +{ + uint8_t opbtn; + uint8_t gamebtn; + uint8_t gear; + struct idac_io_analog_state analog_state; + HRESULT hr; + + assert(idac_dll.get_opbtns != NULL); + assert(idac_dll.get_gamebtns != NULL); + assert(idac_dll.get_analogs != NULL); + assert(idac_dll.get_shifter != NULL); + + memset(state, 0, sizeof(*state)); + memset(&analog_state, 0, sizeof(analog_state)); + opbtn = 0; + gamebtn = 0; + gear = 0; + + idac_dll.get_opbtns(&opbtn); + idac_dll.get_gamebtns(&gamebtn); + idac_dll.get_shifter(&gear); + idac_dll.get_analogs(&analog_state); + + if (opbtn & IDAC_IO_OPBTN_TEST) { + state->buttons[0] |= IO4_BUTTON_TEST; + } + + if (opbtn & IDAC_IO_OPBTN_SERVICE) { + state->buttons[0] |= IO4_BUTTON_SERVICE; + } + + if (opbtn & IDAC_IO_OPBTN_COIN) { + coins++; + } + state->chutes[0] = coins << 8; + + if (gamebtn & IDAC_IO_GAMEBTN_START) { + state->buttons[0] |= 1 << 7; + } + + if (gamebtn & IDAC_IO_GAMEBTN_VIEW_CHANGE) { + state->buttons[0] |= 1 << 1; + } + + if (gamebtn & IDAC_IO_GAMEBTN_UP) { + state->buttons[0] |= 1 << 5; + } + + if (gamebtn & IDAC_IO_GAMEBTN_DOWN) { + state->buttons[0] |= 1 << 4; + } + + if (gamebtn & IDAC_IO_GAMEBTN_LEFT) { + state->buttons[0] |= 1 << 3; + } + + if (gamebtn & IDAC_IO_GAMEBTN_RIGHT) { + state->buttons[0] |= 1 << 2; + } + + /* Update simulated six-speed shifter */ + + if (gear > 6) { + gear = 6; + } + + state->buttons[1] = idac_gear_signals[gear]; + + /* Steering wheel increases left-to-right. + + Use 0x8000 as the center point. */ + + state->adcs[0] = 0x8000 + analog_state.wheel; + state->adcs[1] = analog_state.accel; + state->adcs[2] = analog_state.brake; + + return S_OK; +} + +static HRESULT idac_io4_write_gpio(uint8_t* payload, size_t len) +{ + assert(idac_dll.led_set_leds != NULL); + + // Just fast fail if there aren't enough bytes in the payload + if (len < 3) + return S_OK; + + // This command is used for lights in IDAC, but it only contains button lights, + // and only in the first 3 bytes of the payload; everything else is padding to + // make the payload 62 bytes. The rest of the cabinet lights and the side button + // lights are handled separately, by the 15070 lights controller. + uint32_t lights_data = (uint32_t) ((uint8_t)(payload[0]) << 24 | + (uint8_t)(payload[1]) << 16 | + (uint8_t)(payload[2]) << 8); + + // Since Sega uses an odd ordering for the first part of the bitfield, + // let's normalize the data and just send over bytes for the receiver + // to interpret as ON/OFF values. + uint8_t rgb_out[6] = { + lights_data & IDAC_IO_LED_START ? 0xFF : 0x00, + lights_data & IDAC_IO_LED_VIEW_CHANGE ? 0xFF : 0x00, + lights_data & IDAC_IO_LED_UP ? 0xFF : 0x00, + lights_data & IDAC_IO_LED_DOWN ? 0xFF : 0x00, + lights_data & IDAC_IO_LED_RIGHT ? 0xFF : 0x00, + lights_data & IDAC_IO_LED_LEFT ? 0xFF : 0x00, + }; + + idac_dll.led_set_leds(0, rgb_out); + + return S_OK; +} diff --git a/games/idachook/io4.h b/games/idachook/io4.h new file mode 100644 index 0000000..af2ded1 --- /dev/null +++ b/games/idachook/io4.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "board/io4.h" + +HRESULT idac_io4_hook_init(const struct io4_config *cfg); diff --git a/games/idachook/meson.build b/games/idachook/meson.build new file mode 100644 index 0000000..2cb6456 --- /dev/null +++ b/games/idachook/meson.build @@ -0,0 +1,35 @@ +shared_library( + 'idachook', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + vs_module_defs : 'idachook.def', + dependencies : [ + capnhook.get_variable('hook_dep'), + capnhook.get_variable('hooklib_dep'), + xinput_lib, + ], + link_with : [ + aimeio_lib, + board_lib, + hooklib_lib, + idacio_lib, + platform_lib, + util_lib, + ], + sources : [ + 'config.c', + 'config.h', + 'dllmain.c', + 'idac-dll.c', + 'idac-dll.h', + 'io4.c', + 'io4.h', + 'zinput.c', + 'zinput.h', + 'indrun.c', + 'indrun.h', + 'ffb.c', + 'ffb.h', + ], +) diff --git a/games/idachook/zinput.c b/games/idachook/zinput.c new file mode 100644 index 0000000..a425307 --- /dev/null +++ b/games/idachook/zinput.c @@ -0,0 +1,187 @@ +#include +#include + +#include +#include +#include +#include + +#include "idachook/config.h" +#include "idachook/zinput.h" + +#include "hook/table.h" + +#include "util/dprintf.h" + +HRESULT WINAPI hook_DirectInput8Create( + HINSTANCE hinst, + DWORD dwVersion, + REFIID riidltf, + LPVOID *ppvOut, + LPUNKNOWN punkOuter); + +static unsigned long WINAPI hook_AddRef(IUnknown *self); + +static unsigned long WINAPI hook_Release(IUnknown *self); + +static HRESULT WINAPI hook_CreateDevice( + IDirectInput8W *self, + REFGUID rguid, + LPDIRECTINPUTDEVICE8W * lplpDirectInputDevice, + LPUNKNOWN pUnkOuter); + +static HRESULT WINAPI hook_EnumDevices( + IDirectInput8W *self, + DWORD dwDevType, + LPDIENUMDEVICESCALLBACKW lpCallback, + LPVOID pvRef, + DWORD dwFlags); + +static HRESULT WINAPI hook_SetDataFormat( + IDirectInputDevice8W *self, + LPCDIDATAFORMAT lpdf); + +static HRESULT WINAPI hook_SetCooperativeLevel( + IDirectInputDevice8W *self, + HWND hwnd, + DWORD flags); + +static HRESULT WINAPI hook_Acquire(IDirectInputDevice8W *self); + +static HRESULT WINAPI hook_Unacquire(IDirectInputDevice8W *self); + +static HRESULT WINAPI hook_GetDeviceState( + IDirectInputDevice8W *self, + DWORD cbData, + LPVOID lpvData); + +static const IDirectInput8WVtbl api_vtbl = { + .AddRef = (void *) hook_AddRef, + .Release = (void *) hook_Release, + .CreateDevice = hook_CreateDevice, + .EnumDevices = hook_EnumDevices, +}; + +static const IDirectInput8W api = { (void *) &api_vtbl }; + +static const IDirectInputDevice8WVtbl dev_vtbl = { + .AddRef = (void *) hook_AddRef, + .Release = (void *) hook_Release, + .SetDataFormat = hook_SetDataFormat, + .SetCooperativeLevel= hook_SetCooperativeLevel, + .Acquire = hook_Acquire, + .Unacquire = hook_Unacquire, + .GetDeviceState = hook_GetDeviceState, +}; + +static const IDirectInputDevice8W dev = { (void *) &dev_vtbl }; + +static const struct hook_symbol zinput_hook_syms[] = { + { + .name = "DirectInput8Create", + .patch = hook_DirectInput8Create, + } +}; + +HRESULT zinput_hook_init(struct zinput_config *cfg) +{ + assert(cfg != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + hook_table_apply( + NULL, + "dinput8.dll", + zinput_hook_syms, + _countof(zinput_hook_syms)); + + return S_OK; +} + +HRESULT WINAPI hook_DirectInput8Create( + HINSTANCE hinst, + DWORD dwVersion, + REFIID riidltf, + LPVOID *ppvOut, + LPUNKNOWN punkOuter) +{ + dprintf("ZInput: Blocking built-in DirectInput support\n"); + *ppvOut = (void *) &api; + + return S_OK; +} + +static unsigned long WINAPI hook_AddRef(IUnknown *self) +{ + return 1; +} + +static unsigned long WINAPI hook_Release(IUnknown *self) +{ + return 1; +} + +static HRESULT WINAPI hook_CreateDevice( + IDirectInput8W *self, + REFGUID rguid, + LPDIRECTINPUTDEVICE8W *lplpDirectInputDevice, + LPUNKNOWN pUnkOuter) +{ + dprintf("ZInput: %s\n", __func__); + *lplpDirectInputDevice = (void *) &dev; + + return S_OK; +} + +static HRESULT WINAPI hook_EnumDevices( + IDirectInput8W *self, + DWORD dwDevType, + LPDIENUMDEVICESCALLBACKW lpCallback, + LPVOID pvRef, + DWORD dwFlags) +{ + dprintf("ZInput: %s\n", __func__); + + return S_OK; +} + +static HRESULT WINAPI hook_SetDataFormat( + IDirectInputDevice8W *self, + LPCDIDATAFORMAT lpdf) +{ + dprintf("ZInput: %s\n", __func__); + + return S_OK; +} + +static HRESULT WINAPI hook_SetCooperativeLevel( + IDirectInputDevice8W *self, + HWND hwnd, + DWORD flags) +{ + dprintf("ZInput: %s\n", __func__); + + return S_OK; +} + +static HRESULT WINAPI hook_Acquire(IDirectInputDevice8W *self) +{ + return S_OK; +} + +static HRESULT WINAPI hook_Unacquire(IDirectInputDevice8W *self) +{ + return S_OK; +} + +static HRESULT WINAPI hook_GetDeviceState( + IDirectInputDevice8W *self, + DWORD cbData, + LPVOID lpvData) +{ + memset(lpvData, 0, cbData); + + return S_OK; +} diff --git a/idzhook/zinput.h b/games/idachook/zinput.h similarity index 100% rename from idzhook/zinput.h rename to games/idachook/zinput.h diff --git a/games/idacio/backend.h b/games/idacio/backend.h new file mode 100644 index 0000000..ee65fae --- /dev/null +++ b/games/idacio/backend.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#include "idacio/idacio.h" + +struct idac_io_backend { + void (*get_opbtns)(uint8_t *opbtn); + void (*get_gamebtns)(uint8_t *gamebtn); + void (*get_shifter)(uint8_t *gear); + void (*get_analogs)(struct idac_io_analog_state *state); + HRESULT (*ffb_init)(void); + void (*ffb_toggle)(bool active); + void (*ffb_constant_force)(uint8_t direction, uint8_t force); + void (*ffb_rumble)(uint8_t period, uint8_t force); + void (*ffb_damper)(uint8_t force); +}; diff --git a/games/idacio/config.c b/games/idacio/config.c new file mode 100644 index 0000000..f862840 --- /dev/null +++ b/games/idacio/config.c @@ -0,0 +1,173 @@ +#include + +#include +#include +#include +#include +#include + +#include "idacio/config.h" + +void idac_di_config_load(struct idac_di_config *cfg, const wchar_t *filename) +{ + wchar_t key[8]; + int i; + + assert(cfg != NULL); + assert(filename != NULL); + + GetPrivateProfileStringW( + L"dinput", + L"deviceName", + L"", + cfg->device_name, + _countof(cfg->device_name), + filename); + + GetPrivateProfileStringW( + L"dinput", + L"pedalsName", + L"", + cfg->pedals_name, + _countof(cfg->pedals_name), + filename); + + GetPrivateProfileStringW( + L"dinput", + L"shifterName", + L"", + cfg->shifter_name, + _countof(cfg->shifter_name), + filename); + + GetPrivateProfileStringW( + L"dinput", + L"brakeAxis", + L"RZ", + cfg->brake_axis, + _countof(cfg->brake_axis), + filename); + + GetPrivateProfileStringW( + L"dinput", + L"accelAxis", + L"Y", + cfg->accel_axis, + _countof(cfg->accel_axis), + filename); + + cfg->start = GetPrivateProfileIntW(L"dinput", L"start", 0, filename); + cfg->view_chg = GetPrivateProfileIntW(L"dinput", L"viewChg", 0, filename); + cfg->left = GetPrivateProfileIntW(L"dinput", L"left", 0, filename); + cfg->right = GetPrivateProfileIntW(L"dinput", L"right", 0, filename); + cfg->shift_dn = GetPrivateProfileIntW(L"dinput", L"shiftDn", 0, filename); + cfg->shift_up = GetPrivateProfileIntW(L"dinput", L"shiftUp", 0, filename); + + cfg->reverse_brake_axis = GetPrivateProfileIntW( + L"dinput", + L"reverseBrakeAxis", + 0, + filename); + cfg->reverse_accel_axis = GetPrivateProfileIntW( + L"dinput", + L"reverseAccelAxis", + 0, + filename); + + for (i = 0 ; i < 6 ; i++) { + swprintf_s(key, _countof(key), L"gear%i", i + 1); + cfg->gear[i] = GetPrivateProfileIntW(L"dinput", key, i + 1, filename); + } + + // FFB configuration + cfg->ffb_constant_force_strength = GetPrivateProfileIntW( + L"dinput", + L"constantForceStrength", + 100, + filename); + + cfg->ffb_rumble_strength = GetPrivateProfileIntW( + L"dinput", + L"rumbleStrength", + 100, + filename); + + cfg->ffb_damper_strength = GetPrivateProfileIntW( + L"dinput", + L"damperStrength", + 100, + filename); + + cfg->ffb_rumble_duration = GetPrivateProfileIntW( + L"dinput", + L"rumbleDuration", + 1000, + filename); +} + +void idac_xi_config_load(struct idac_xi_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->single_stick_steering = GetPrivateProfileIntW( + L"xinput", + L"singleStickSteering", + 0, + filename); + + cfg->linear_steering = GetPrivateProfileIntW( + L"xinput", + L"linearSteering", + 0, + filename); + + cfg->left_stick_deadzone = GetPrivateProfileIntW( + L"xinput", + L"leftStickDeadzone", + 7849, + filename); + + cfg->right_stick_deadzone = GetPrivateProfileIntW( + L"xinput", + L"rightStickDeadzone", + 8689, + filename); +} + +void idac_io_config_load(struct idac_io_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->vk_test = GetPrivateProfileIntW(L"io4", L"test", VK_F1, filename); + cfg->vk_service = GetPrivateProfileIntW(L"io4", L"service", VK_F2, filename); + cfg->vk_coin = GetPrivateProfileIntW(L"io4", L"coin", VK_F3, filename); + cfg->restrict_ = GetPrivateProfileIntW(L"io4", L"restrict", 128, filename); + + GetPrivateProfileStringW( + L"io4", + L"mode", + L"xinput", + cfg->mode, + _countof(cfg->mode), + filename); + + idac_shifter_config_load(&cfg->shifter, filename); + idac_di_config_load(&cfg->di, filename); + idac_xi_config_load(&cfg->xi, filename); +} + +void idac_shifter_config_load( + struct idac_shifter_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->auto_neutral = GetPrivateProfileIntW( + L"io4", + L"autoNeutral", + 0, + filename); +} diff --git a/games/idacio/config.h b/games/idacio/config.h new file mode 100644 index 0000000..88a1d6a --- /dev/null +++ b/games/idacio/config.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include +#include + +struct idac_shifter_config { + bool auto_neutral; +}; + +struct idac_di_config { + wchar_t device_name[64]; + wchar_t pedals_name[64]; + wchar_t shifter_name[64]; + wchar_t brake_axis[16]; + wchar_t accel_axis[16]; + uint8_t start; + uint8_t view_chg; + uint8_t left; + uint8_t right; + uint8_t shift_dn; + uint8_t shift_up; + uint8_t gear[6]; + bool reverse_brake_axis; + bool reverse_accel_axis; + + // FFB configuration + uint8_t ffb_constant_force_strength; + uint8_t ffb_rumble_strength; + uint8_t ffb_damper_strength; + + uint32_t ffb_rumble_duration; +}; + +struct idac_xi_config { + bool single_stick_steering; + bool linear_steering; + uint16_t left_stick_deadzone; + uint16_t right_stick_deadzone; +}; + +struct idac_io_config { + uint8_t vk_test; + uint8_t vk_service; + uint8_t vk_coin; + wchar_t mode[8]; + int restrict_; + struct idac_shifter_config shifter; + struct idac_di_config di; + struct idac_xi_config xi; +}; + +void idac_di_config_load(struct idac_di_config *cfg, const wchar_t *filename); +void idac_xi_config_load(struct idac_xi_config *cfg, const wchar_t *filename); +void idac_io_config_load(struct idac_io_config *cfg, const wchar_t *filename); +void idac_shifter_config_load( + struct idac_shifter_config *cfg, + const wchar_t *filename); diff --git a/games/idacio/di-dev.c b/games/idacio/di-dev.c new file mode 100644 index 0000000..5f959f6 --- /dev/null +++ b/games/idacio/di-dev.c @@ -0,0 +1,371 @@ +#include +#include +#include +#include + +#include "idacio/di-dev.h" + +#include "util/dprintf.h" + +const struct idac_di_config *idac_di_cfg; +static HWND idac_di_wnd; +static IDirectInputDevice8W *idac_di_dev; + +/* Individual DI Effects */ +static IDirectInputEffect *idac_di_fx; +static IDirectInputEffect *idac_di_fx_rumble; +static IDirectInputEffect *idac_di_fx_damper; + +/* Max FFB Board value is 127 */ +static const double idac_di_ffb_scale = 127.0; + +HRESULT idac_di_dev_init( + const struct idac_di_config *cfg, + IDirectInputDevice8W *dev, + HWND wnd) +{ + HRESULT hr; + + assert(dev != NULL); + assert(wnd != NULL); + + idac_di_cfg = cfg; + idac_di_dev = dev; + idac_di_wnd = wnd; + + return S_OK; +} + +HRESULT idac_di_dev_poll( + IDirectInputDevice8W *dev, + HWND wnd, + union idac_di_state *out) +{ + HRESULT hr; + MSG msg; + + assert(dev != NULL); + assert(wnd != NULL); + assert(out != NULL); + + memset(out, 0, sizeof(*out)); + + /* Pump our dummy window's message queue just in case DirectInput or an + IHV DirectInput driver somehow relies on it */ + + while (PeekMessageW(&msg, wnd, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + hr = IDirectInputDevice8_GetDeviceState( + dev, + sizeof(out->st), + &out->st); + + if (FAILED(hr)) { + dprintf("DirectInput: GetDeviceState error: %08x\n", (int)hr); + } + + return hr; +} + +HRESULT idac_di_dev_start(IDirectInputDevice8W *dev, HWND wnd) { + HRESULT hr; + + assert(dev != NULL); + assert(wnd != NULL); + + hr = IDirectInputDevice8_SetCooperativeLevel( + dev, + wnd, + DISCL_BACKGROUND | DISCL_EXCLUSIVE); + + if (FAILED(hr)) { + dprintf("DirectInput: SetCooperativeLevel failed: %08x\n", (int)hr); + return hr; + } + + hr = IDirectInputDevice8_SetDataFormat(dev, &c_dfDIJoystick); + + if (FAILED(hr)) { + dprintf("DirectInput: SetDataFormat failed: %08x\n", (int)hr); + return hr; + } + + hr = IDirectInputDevice8_Acquire(dev); + + if (FAILED(hr)) { + dprintf("DirectInput: Acquire failed: %08x\n", (int)hr); + return hr; + } + + return hr; +} + +HRESULT idac_di_ffb_init(void) +{ + HRESULT hr; + + hr = idac_di_dev_start(idac_di_dev, idac_di_wnd); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} + +void idac_di_ffb_toggle(bool active) +{ + if (active) { + return; + } + + /* Stop and release all effects */ + /* I never programmed DirectInput Effects, so this might be bad practice. */ + if (idac_di_fx != NULL) { + IDirectInputEffect_Stop(idac_di_fx); + IDirectInputEffect_Release(idac_di_fx); + idac_di_fx = NULL; + } + + if (idac_di_fx_rumble != NULL) { + IDirectInputEffect_Stop(idac_di_fx_rumble); + IDirectInputEffect_Release(idac_di_fx_rumble); + idac_di_fx_rumble = NULL; + } + + if (idac_di_fx_damper != NULL) { + IDirectInputEffect_Stop(idac_di_fx_damper); + IDirectInputEffect_Release(idac_di_fx_damper); + idac_di_fx_damper = NULL; + } +} + +void idac_di_ffb_constant_force(uint8_t direction_ffb, uint8_t force) +{ + /* DI expects a magnitude in the range of -10.000 to 10.000 */ + uint16_t ffb_strength = idac_di_cfg->ffb_constant_force_strength * 100; + if (ffb_strength == 0) { + return; + } + + DWORD axis; + LONG direction; + DIEFFECT fx; + DICONSTANTFORCE cf; + HRESULT hr; + + /* Direction 0: move to the right, 1: move to the left */ + LONG magnitude = (LONG)(((double)force / idac_di_ffb_scale) * ffb_strength); + cf.lMagnitude = (direction_ffb == 0) ? -magnitude : magnitude; + + axis = DIJOFS_X; + direction = 0; + + memset(&fx, 0, sizeof(fx)); + fx.dwSize = sizeof(fx); + fx.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; + fx.dwDuration = INFINITE; + fx.dwGain = DI_FFNOMINALMAX; + fx.dwTriggerButton = DIEB_NOTRIGGER; + fx.dwTriggerRepeatInterval = INFINITE; + fx.cAxes = 1; + fx.rgdwAxes = &axis; + fx.rglDirection = &direction; + fx.cbTypeSpecificParams = sizeof(cf); + fx.lpvTypeSpecificParams = &cf; + + /* Check if the effect already exists */ + if (idac_di_fx != NULL) { + hr = IDirectInputEffect_SetParameters(idac_di_fx, &fx, DIEP_TYPESPECIFICPARAMS); + if (SUCCEEDED(hr)) { + return; // Successfully updated existing effect + } + else { + dprintf("DirectInput: Failed to update constant force feedback, recreating effect: %08x\n", (int)hr); + IDirectInputEffect_Stop(idac_di_fx); + IDirectInputEffect_Release(idac_di_fx); + idac_di_fx = NULL; // Reset the pointer + } + } + + /* Create a new constant force effect */ + IDirectInputEffect *obj; + hr = IDirectInputDevice8_CreateEffect( + idac_di_dev, + &GUID_ConstantForce, + &fx, + &obj, + NULL); + + if (FAILED(hr)) { + dprintf("DirectInput: Constant force feedback creation failed: %08x\n", (int)hr); + return; + } + + /* Start the effect */ + hr = IDirectInputEffect_Start(obj, INFINITE, 0); + if (FAILED(hr)) { + dprintf("DirectInput: Constant force feedback start failed: %08x\n", (int)hr); + IDirectInputEffect_Release(obj); + return; + } + + idac_di_fx = obj; +} + +void idac_di_ffb_rumble(uint8_t force, uint8_t period) +{ + /* DI expects a magnitude in the range of -10.000 to 10.000 */ + uint16_t ffb_strength = idac_di_cfg->ffb_rumble_strength * 100; + if (ffb_strength == 0) { + return; + } + + uint32_t ffb_duration = idac_di_cfg->ffb_rumble_duration; + + DWORD axis; + LONG direction; + DIEFFECT fx; + DIPERIODIC pe; + HRESULT hr; + + DWORD duration = (DWORD)((double)force * ffb_duration); + + memset(&pe, 0, sizeof(pe)); + pe.dwMagnitude = (DWORD)(((double)force / idac_di_ffb_scale) * ffb_strength); + pe.lOffset = 0; + pe.dwPhase = 0; + pe.dwPeriod = duration; + + axis = DIJOFS_X; + direction = 0; + + memset(&fx, 0, sizeof(fx)); + fx.dwSize = sizeof(fx); + fx.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; + fx.dwDuration = INFINITE; + fx.dwGain = DI_FFNOMINALMAX; + fx.dwTriggerButton = DIEB_NOTRIGGER; + fx.dwTriggerRepeatInterval = INFINITE; + fx.cAxes = 1; + fx.rgdwAxes = &axis; + fx.rglDirection = &direction; + fx.cbTypeSpecificParams = sizeof(pe); + fx.lpvTypeSpecificParams = &pe; + + /* Check if the effect already exists */ + if (idac_di_fx_rumble != NULL) { + hr = IDirectInputEffect_SetParameters(idac_di_fx_rumble, &fx, DIEP_TYPESPECIFICPARAMS); + if (SUCCEEDED(hr)) { + return; + } + else { + dprintf("DirectInput: Failed to update rumble feedback, recreating effect: %08x\n", (int)hr); + IDirectInputEffect_Stop(idac_di_fx_rumble); + IDirectInputEffect_Release(idac_di_fx_rumble); + idac_di_fx_rumble = NULL; + } + } + + /* Create a new rumble effect */ + IDirectInputEffect *obj; + hr = IDirectInputDevice8_CreateEffect( + idac_di_dev, + &GUID_Sine, + &fx, + &obj, + NULL); + + if (FAILED(hr)) { + dprintf("DirectInput: Rumble effect creation failed: %08x\n", (int)hr); + return; + } + + /* Start the effect */ + hr = IDirectInputEffect_Start(obj, INFINITE, 0); + if (FAILED(hr)) { + dprintf("DirectInput: Rumble effect start failed: %08x\n", (int)hr); + IDirectInputEffect_Release(obj); + return; + } + + idac_di_fx_rumble = obj; +} + +void idac_di_ffb_damper(uint8_t force) +{ + /* DI expects a coefficient in the range of -10.000 to 10.000 */ + uint16_t ffb_strength = idac_di_cfg->ffb_damper_strength * 100; + if (ffb_strength == 0) { + return; + } + + DWORD axis; + LONG direction; + DIEFFECT fx; + DICONDITION cond; + HRESULT hr; + + memset(&cond, 0, sizeof(cond)); + cond.lOffset = 0; + cond.lPositiveCoefficient = (LONG)(((double)force / idac_di_ffb_scale) * ffb_strength); + cond.lNegativeCoefficient = (LONG)(((double)force / idac_di_ffb_scale) * ffb_strength); + /* Not sure on this one */ + cond.dwPositiveSaturation = DI_FFNOMINALMAX; + cond.dwNegativeSaturation = DI_FFNOMINALMAX; + cond.lDeadBand = 0; + + axis = DIJOFS_X; + direction = 0; + + memset(&fx, 0, sizeof(fx)); + fx.dwSize = sizeof(fx); + fx.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; + fx.dwDuration = INFINITE; + fx.dwGain = DI_FFNOMINALMAX; + fx.dwTriggerButton = DIEB_NOTRIGGER; + fx.dwTriggerRepeatInterval = INFINITE; + fx.cAxes = 1; + fx.rgdwAxes = &axis; + fx.rglDirection = &direction; + fx.cbTypeSpecificParams = sizeof(cond); + fx.lpvTypeSpecificParams = &cond; + + /* Check if the damper effect already exists */ + if (idac_di_fx_damper != NULL) { + hr = IDirectInputEffect_SetParameters(idac_di_fx_damper, &fx, DIEP_TYPESPECIFICPARAMS); + if (SUCCEEDED(hr)) { + return; + } + else { + IDirectInputEffect_Stop(idac_di_fx_damper); + IDirectInputEffect_Release(idac_di_fx_damper); + idac_di_fx_damper = NULL; + } + } + + /* Create a new damper effect */ + IDirectInputEffect *obj; + hr = IDirectInputDevice8_CreateEffect( + idac_di_dev, + &GUID_Damper, + &fx, + &obj, + NULL); + + if (FAILED(hr)) { + return; + } + + /* Start the effect */ + hr = IDirectInputEffect_Start(obj, fx.dwDuration, 0); + if (FAILED(hr)) { + IDirectInputEffect_Release(obj); + return; + } + + idac_di_fx_damper = obj; +} diff --git a/games/idacio/di-dev.h b/games/idacio/di-dev.h new file mode 100644 index 0000000..f26b802 --- /dev/null +++ b/games/idacio/di-dev.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +#include + +#include "idacio/config.h" + +union idac_di_state { + DIJOYSTATE st; + uint8_t bytes[sizeof(DIJOYSTATE)]; +}; + +HRESULT idac_di_dev_init( + const struct idac_di_config *cfg, + IDirectInputDevice8W *dev, + HWND wnd); + +HRESULT idac_di_dev_start(IDirectInputDevice8W *dev, HWND wnd); +HRESULT idac_di_dev_poll( + IDirectInputDevice8W *dev, + HWND wnd, + union idac_di_state *out); + +HRESULT idac_di_ffb_init(void); +void idac_di_ffb_toggle(bool active); +void idac_di_ffb_constant_force(uint8_t direction, uint8_t force); +void idac_di_ffb_rumble(uint8_t force, uint8_t period); +void idac_di_ffb_damper(uint8_t force); diff --git a/games/idacio/di.c b/games/idacio/di.c new file mode 100644 index 0000000..8a464ed --- /dev/null +++ b/games/idacio/di.c @@ -0,0 +1,659 @@ +#include +#include + +#include +#include +#include +#include +#include + +#include "idacio/backend.h" +#include "idacio/config.h" +#include "idacio/di.h" +#include "idacio/di-dev.h" +#include "idacio/idacio.h" +#include "idacio/shifter.h" +#include "idacio/wnd.h" + +#include "util/dprintf.h" +#include "util/str.h" + +struct idac_di_axis { + wchar_t name[4]; + size_t off; +}; + +static HRESULT idac_di_config_apply(const struct idac_di_config *cfg); +static const struct idac_di_axis *idac_di_get_axis(const wchar_t *name); +static BOOL CALLBACK idac_di_enum_callback( + const DIDEVICEINSTANCEW *dev, + void *ctx); +static BOOL CALLBACK idac_di_enum_callback_pedals( + const DIDEVICEINSTANCEW *dev, + void *ctx); +static BOOL CALLBACK idac_di_enum_callback_shifter( + const DIDEVICEINSTANCEW *dev, + void *ctx); +static void idac_di_get_buttons(uint8_t *gamebtn_out); +static uint8_t idac_di_decode_pov(DWORD pov); +static void idac_di_get_shifter(uint8_t *gear); +static void idac_di_get_shifter_pos(uint8_t *gear); +static void idac_di_get_shifter_virt(uint8_t *gear); +static void idac_di_get_analogs(struct idac_io_analog_state *out); + +static const struct idac_di_axis idac_di_axes[] = { + /* Just map DIJOYSTATE for now, we can map DIJOYSTATE2 later if needed */ + { .name = L"X", .off = DIJOFS_X }, + { .name = L"Y", .off = DIJOFS_Y }, + { .name = L"Z", .off = DIJOFS_Z }, + { .name = L"RX", .off = DIJOFS_RX }, + { .name = L"RY", .off = DIJOFS_RY }, + { .name = L"RZ", .off = DIJOFS_RZ }, + { .name = L"U", .off = DIJOFS_SLIDER(0) }, + { .name = L"V", .off = DIJOFS_SLIDER(1) }, +}; + +static const struct idac_io_backend idac_di_backend = { + .get_gamebtns = idac_di_get_buttons, + .get_shifter = idac_di_get_shifter, + .get_analogs = idac_di_get_analogs, + .ffb_init = idac_di_ffb_init, + .ffb_toggle = idac_di_ffb_toggle, + .ffb_constant_force = idac_di_ffb_constant_force, + .ffb_rumble = idac_di_ffb_rumble, + .ffb_damper = idac_di_ffb_damper +}; + +static HWND idac_di_wnd; +static IDirectInput8W *idac_di_api; +static IDirectInputDevice8W *idac_di_dev; +static IDirectInputDevice8W *idac_di_pedals; +static IDirectInputDevice8W *idac_di_shifter; +static size_t idac_di_off_brake; +static size_t idac_di_off_accel; +static uint8_t idac_di_shift_dn; +static uint8_t idac_di_shift_up; +static uint8_t idac_di_view_chg; +static uint8_t idac_di_start; +static uint8_t idac_di_left; +static uint8_t idac_di_right; +static uint8_t idac_di_gear[6]; +static bool idac_di_use_pedals; +static bool idac_di_reverse_brake_axis; +static bool idac_di_reverse_accel_axis; + +HRESULT idac_di_init( + const struct idac_di_config *cfg, + HINSTANCE inst, + const struct idac_io_backend **backend) +{ + HRESULT hr; + HMODULE dinput8; + HRESULT (WINAPI *api_entry)(HINSTANCE,DWORD,REFIID,LPVOID *,LPUNKNOWN); + wchar_t dll_path[MAX_PATH]; + UINT path_pos; + + assert(cfg != NULL); + assert(backend != NULL); + + *backend = NULL; + + hr = idac_di_config_apply(cfg); + + if (FAILED(hr)) { + return hr; + } + + hr = idac_io_wnd_create(inst, &idac_di_wnd); + + if (FAILED(hr)) { + return hr; + } + + /* Initial D THE ARCADE has some built-in DirectInput support that is not + particularly useful. idachook shorts this out by redirecting dinput8.dll + to a no-op implementation of DirectInput. However, idacio does need to + talk to the real operating system implementation of DirectInput without + the stub DLL interfering, so build a path to + C:\Windows\System32\dinput.dll here. */ + + dll_path[0] = L'\0'; + path_pos = GetSystemDirectoryW(dll_path, _countof(dll_path)); + wcscat_s( + dll_path + path_pos, + _countof(dll_path) - path_pos, + L"\\dinput8.dll"); + + dinput8 = LoadLibraryW(dll_path); + + if (dinput8 == NULL) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("DirectInput: LoadLibrary failed: %08x\n", (int) hr); + + return hr; + } + + api_entry = (void *) GetProcAddress(dinput8, "DirectInput8Create"); + + if (api_entry == NULL) { + dprintf("DirectInput: GetProcAddress failed\n"); + + return E_FAIL; + } + + hr = api_entry( + inst, + DIRECTINPUT_VERSION, + &IID_IDirectInput8W, + (void **) &idac_di_api, + NULL); + + if (FAILED(hr)) { + dprintf("DirectInput: API create failed: %08x\n", (int) hr); + + return hr; + } + + hr = IDirectInput8_EnumDevices( + idac_di_api, + DI8DEVCLASS_GAMECTRL, + idac_di_enum_callback, + (void *) cfg, + DIEDFL_ATTACHEDONLY); + + if (FAILED(hr)) { + dprintf("DirectInput: EnumDevices failed: %08x\n", (int) hr); + + return hr; + } + + if (idac_di_dev == NULL) { + dprintf("Wheel: Controller not found\n"); + + return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + } + + hr = idac_di_dev_init(cfg, idac_di_dev, idac_di_wnd); + + if (FAILED(hr)) { + return hr; + } + + if (cfg->pedals_name[0] != L'\0') { + hr = IDirectInput8_EnumDevices( + idac_di_api, + DI8DEVCLASS_GAMECTRL, + idac_di_enum_callback_pedals, + (void *) cfg, + DIEDFL_ATTACHEDONLY); + + if (FAILED(hr)) { + dprintf("DirectInput: EnumDevices failed: %08x\n", (int) hr); + + return hr; + } + + if (idac_di_dev == NULL) { + dprintf("Pedals: Controller not found\n"); + + return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + } + + hr = idac_di_dev_start(idac_di_pedals, idac_di_wnd); + + if (FAILED(hr)) { + return hr; + } + + idac_di_use_pedals = true; + } else { + idac_di_use_pedals = false; + } + + if (cfg->shifter_name[0] != L'\0') { + hr = IDirectInput8_EnumDevices( + idac_di_api, + DI8DEVCLASS_GAMECTRL, + idac_di_enum_callback_shifter, + (void *) cfg, + DIEDFL_ATTACHEDONLY); + + if (FAILED(hr)) { + dprintf("DirectInput: EnumDevices failed: %08x\n", (int) hr); + + return hr; + } + + if (idac_di_dev == NULL) { + dprintf("Shifter: Controller not found\n"); + + return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + } + + hr = idac_di_dev_start(idac_di_shifter, idac_di_wnd); + + if (FAILED(hr)) { + return hr; + } + } + + dprintf("DirectInput: Controller initialized\n"); + + *backend = &idac_di_backend; + + return S_OK; +} + +static HRESULT idac_di_config_apply(const struct idac_di_config *cfg) +{ + const struct idac_di_axis *brake_axis; + const struct idac_di_axis *accel_axis; + int i; + + brake_axis = idac_di_get_axis(cfg->brake_axis); + accel_axis = idac_di_get_axis(cfg->accel_axis); + + if (brake_axis == NULL) { + dprintf("Wheel: Invalid brake axis: %S\n", cfg->brake_axis); + + return E_INVALIDARG; + } + + if (accel_axis == NULL) { + dprintf("Wheel: Invalid accel axis: %S\n", cfg->accel_axis); + + return E_INVALIDARG; + } + + if (cfg->start > 32) { + dprintf("Wheel: Invalid start button: %i\n", cfg->start); + + return E_INVALIDARG; + } + + if (cfg->view_chg > 32) { + dprintf("Wheel: Invalid view change button: %i\n", cfg->view_chg); + + return E_INVALIDARG; + } + + if (cfg->left > 32) { + dprintf("Wheel: Invalid left button: %i\n", cfg->left); + + return E_INVALIDARG; + } + + if (cfg->right > 32) { + dprintf("Wheel: Invalid right button: %i\n", cfg->right); + + return E_INVALIDARG; + } + + if (cfg->shift_dn > 32) { + dprintf("Wheel: Invalid shift down button: %i\n", cfg->shift_dn); + + return E_INVALIDARG; + } + + if (cfg->shift_up > 32) { + dprintf("Wheel: Invalid shift up button: %i\n", cfg->shift_up); + + return E_INVALIDARG; + } + + for (i = 0 ; i < 6 ; i++) { + if (cfg->gear[i] > 32) { + dprintf("Shifter: Invalid gear %i button: %i\n", + i + 1, + cfg->gear[i]); + + return E_INVALIDARG; + } + } + + /* Print some debug output to make sure config works... */ + + dprintf("Wheel: --- Begin configuration ---\n"); + dprintf("Wheel: Device name . . . . : Contains \"%S\"\n", + cfg->device_name); + if (cfg->pedals_name[0] == L'\0') { + dprintf("Wheel: Brake axis . . . . : %S\n", brake_axis->name); + dprintf("Wheel: Accel axis . . . . : %S\n", accel_axis->name); + } + dprintf("Wheel: Start button . . . : %i\n", cfg->start); + dprintf("Wheel: View Change button : %i\n", cfg->view_chg); + dprintf("Wheel: Left button . . . . : %i\n", cfg->left); + dprintf("Wheel: Right button . . . : %i\n", cfg->right); + dprintf("Wheel: Shift Down button . : %i\n", cfg->shift_dn); + dprintf("Wheel: Shift Up button . . : %i\n", cfg->shift_up); + dprintf("Wheel: Reverse Brake Axis : %i\n", cfg->reverse_brake_axis); + dprintf("Wheel: Reverse Accel Axis : %i\n", cfg->reverse_accel_axis); + dprintf("Wheel: --- End configuration ---\n"); + + if (cfg->pedals_name[0] != L'\0') { + dprintf("Pedals: --- Begin configuration ---\n"); + dprintf("Pedals: Device name . . . : Contains \"%S\"\n", + cfg->pedals_name); + dprintf("Pedals: Brake axis . . . . : %S\n", brake_axis->name); + dprintf("Pedals: Accel axis . . . . : %S\n", accel_axis->name); + dprintf("Pedals: --- End configuration ---\n"); + } + + if (cfg->shifter_name[0] != L'\0') { + dprintf("Shifter: --- Begin configuration ---\n"); + dprintf("Shifter: Device name . . . : Contains \"%S\"\n", + cfg->shifter_name); + dprintf("Shifter: Gear buttons . . : %i %i %i %i %i %i\n", + cfg->gear[0], + cfg->gear[1], + cfg->gear[2], + cfg->gear[3], + cfg->gear[4], + cfg->gear[5]); + dprintf("Shifter: --- End configuration ---\n"); + } + + idac_di_off_brake = brake_axis->off; + idac_di_off_accel = accel_axis->off; + idac_di_start = cfg->start; + idac_di_view_chg = cfg->view_chg; + idac_di_left = cfg->left; + idac_di_right = cfg->right; + idac_di_shift_dn = cfg->shift_dn; + idac_di_shift_up = cfg->shift_up; + idac_di_reverse_brake_axis = cfg->reverse_brake_axis; + idac_di_reverse_accel_axis = cfg->reverse_accel_axis; + + for (i = 0 ; i < 6 ; i++) { + idac_di_gear[i] = cfg->gear[i]; + } + + /* FFB configuration */ + if (cfg->ffb_constant_force_strength < 0 || cfg->ffb_constant_force_strength > 100) { + dprintf("Wheel: Invalid constant force strength: %i\n", cfg->ffb_constant_force_strength); + + return E_INVALIDARG; + } + + if (cfg->ffb_rumble_strength < 0 || cfg->ffb_rumble_strength > 100) { + dprintf("Wheel: Invalid rumble strength: %i\n", cfg->ffb_rumble_strength); + + return E_INVALIDARG; + } + + if (cfg->ffb_damper_strength < 0 || cfg->ffb_damper_strength > 100) { + dprintf("Wheel: Invalid damper strength: %i\n", cfg->ffb_damper_strength); + + return E_INVALIDARG; + } + + return S_OK; +} + +static const struct idac_di_axis *idac_di_get_axis(const wchar_t *name) +{ + const struct idac_di_axis *axis; + size_t i; + + for (i = 0 ; i < _countof(idac_di_axes) ; i++) { + axis = &idac_di_axes[i]; + + if (wstr_ieq(name, axis->name)) { + return axis; + } + } + + return NULL; +} + +static BOOL CALLBACK idac_di_enum_callback( + const DIDEVICEINSTANCEW *dev, + void *ctx) +{ + const struct idac_di_config *cfg; + HRESULT hr; + + cfg = ctx; + + if (wcsstr(dev->tszProductName, cfg->device_name) == NULL) { + return DIENUM_CONTINUE; + } + + dprintf("Wheel: Using DirectInput device \"%S\"\n", dev->tszProductName); + + hr = IDirectInput8_CreateDevice( + idac_di_api, + &dev->guidInstance, + &idac_di_dev, + NULL); + + if (FAILED(hr)) { + dprintf("Wheel: CreateDevice failed: %08x\n", (int) hr); + } + + return DIENUM_STOP; +} + +static BOOL CALLBACK idac_di_enum_callback_pedals( + const DIDEVICEINSTANCEW *dev, + void *ctx) +{ + const struct idac_di_config *cfg; + HRESULT hr; + + cfg = ctx; + + if (wcsstr(dev->tszProductName, cfg->pedals_name) == NULL) { + return DIENUM_CONTINUE; + } + + dprintf("Pedals: Using DirectInput device \"%S\"\n", dev->tszProductName); + + hr = IDirectInput8_CreateDevice( + idac_di_api, + &dev->guidInstance, + &idac_di_pedals, + NULL); + + if (FAILED(hr)) { + dprintf("Pedals: CreateDevice failed: %08x\n", (int) hr); + } + + return DIENUM_STOP; +} + +static BOOL CALLBACK idac_di_enum_callback_shifter( + const DIDEVICEINSTANCEW *dev, + void *ctx) +{ + const struct idac_di_config *cfg; + HRESULT hr; + + cfg = ctx; + + if (wcsstr(dev->tszProductName, cfg->shifter_name) == NULL) { + return DIENUM_CONTINUE; + } + + dprintf("Shifter: Using DirectInput device \"%S\"\n", dev->tszProductName); + + hr = IDirectInput8_CreateDevice( + idac_di_api, + &dev->guidInstance, + &idac_di_shifter, + NULL); + + if (FAILED(hr)) { + dprintf("Shifter: CreateDevice failed: %08x\n", (int) hr); + } + + return DIENUM_STOP; +} + +static void idac_di_get_buttons(uint8_t *gamebtn_out) +{ + union idac_di_state state; + uint8_t gamebtn; + HRESULT hr; + + assert(gamebtn_out != NULL); + + hr = idac_di_dev_poll(idac_di_dev, idac_di_wnd, &state); + + if (FAILED(hr)) { + return; + } + + gamebtn = idac_di_decode_pov(state.st.rgdwPOV[0]); + + if (idac_di_start && state.st.rgbButtons[idac_di_start - 1]) { + gamebtn |= IDAC_IO_GAMEBTN_START; + } + + if (idac_di_view_chg && state.st.rgbButtons[idac_di_view_chg - 1]) { + gamebtn |= IDAC_IO_GAMEBTN_VIEW_CHANGE; + } + + if (idac_di_left && state.st.rgbButtons[idac_di_left - 1]) { + gamebtn |= IDAC_IO_GAMEBTN_LEFT; + } + + if (idac_di_right && state.st.rgbButtons[idac_di_right - 1]) { + gamebtn |= IDAC_IO_GAMEBTN_RIGHT; + } + + *gamebtn_out = gamebtn; +} + +static uint8_t idac_di_decode_pov(DWORD pov) +{ + switch (pov) { + case 0: return IDAC_IO_GAMEBTN_UP; + case 4500: return IDAC_IO_GAMEBTN_UP | IDAC_IO_GAMEBTN_RIGHT; + case 9000: return IDAC_IO_GAMEBTN_RIGHT; + case 13500: return IDAC_IO_GAMEBTN_RIGHT | IDAC_IO_GAMEBTN_DOWN; + case 18000: return IDAC_IO_GAMEBTN_DOWN; + case 22500: return IDAC_IO_GAMEBTN_DOWN | IDAC_IO_GAMEBTN_LEFT; + case 27000: return IDAC_IO_GAMEBTN_LEFT; + case 31500: return IDAC_IO_GAMEBTN_LEFT | IDAC_IO_GAMEBTN_UP; + default: return 0; + } +} + +static void idac_di_get_shifter(uint8_t *gear) +{ + assert(gear != NULL); + + if (idac_di_shifter != NULL) { + idac_di_get_shifter_pos(gear); + } else { + idac_di_get_shifter_virt(gear); + } +} + +static void idac_di_get_shifter_pos(uint8_t *out) +{ + union idac_di_state state; + uint8_t btn_no; + uint8_t gear; + uint8_t i; + HRESULT hr; + + assert(out != NULL); + assert(idac_di_shifter != NULL); + + hr = idac_di_dev_poll(idac_di_shifter, idac_di_wnd, &state); + + if (FAILED(hr)) { + return; + } + + gear = 0; + + for (i = 0 ; i < 6 ; i++) { + btn_no = idac_di_gear[i]; + + if (btn_no && state.st.rgbButtons[btn_no - 1]) { + gear = i + 1; + } + } + + *out = gear; +} + +static void idac_di_get_shifter_virt(uint8_t *gear) +{ + union idac_di_state state; + bool shift_dn; + bool shift_up; + HRESULT hr; + + assert(gear != NULL); + + hr = idac_di_dev_poll(idac_di_dev, idac_di_wnd, &state); + + if (FAILED(hr)) { + return; + } + + if (idac_di_shift_dn) { + shift_dn = state.st.rgbButtons[idac_di_shift_dn - 1]; + } else { + shift_dn = false; + } + + if (idac_di_shift_up) { + shift_up = state.st.rgbButtons[idac_di_shift_up - 1]; + } else { + shift_up = false; + } + + idac_shifter_update(shift_dn, shift_up); + + *gear = idac_shifter_current_gear(); +} + +static void idac_di_get_analogs(struct idac_io_analog_state *out) +{ + union idac_di_state state; + union idac_di_state pedals_state; + const LONG *brake; + const LONG *accel; + HRESULT hr; + + assert(out != NULL); + + hr = idac_di_dev_poll(idac_di_dev, idac_di_wnd, &state); + + if (FAILED(hr)) { + return; + } + + if (idac_di_use_pedals) { + hr = idac_di_dev_poll(idac_di_pedals, idac_di_wnd, &pedals_state); + + if (FAILED(hr)) { + return; + } + + brake = (LONG *) &pedals_state.bytes[idac_di_off_brake]; + accel = (LONG *) &pedals_state.bytes[idac_di_off_accel]; + } else { + brake = (LONG *) &state.bytes[idac_di_off_brake]; + accel = (LONG *) &state.bytes[idac_di_off_accel]; + } + + out->wheel = state.st.lX - 32768; + + if (idac_di_reverse_brake_axis) { + out->brake = *brake; + } else { + out->brake = 65535 - *brake; + } + + if (idac_di_reverse_accel_axis) { + out->accel = *accel; + } else { + out->accel = 65535 - *accel; + } +} diff --git a/games/idacio/di.h b/games/idacio/di.h new file mode 100644 index 0000000..fc03e21 --- /dev/null +++ b/games/idacio/di.h @@ -0,0 +1,9 @@ +#pragma once + +#include "idacio/backend.h" +#include "idacio/config.h" + +HRESULT idac_di_init( + const struct idac_di_config *cfg, + HINSTANCE inst, + const struct idac_io_backend **backend); diff --git a/games/idacio/dllmain.c b/games/idacio/dllmain.c new file mode 100644 index 0000000..fe8246e --- /dev/null +++ b/games/idacio/dllmain.c @@ -0,0 +1,199 @@ +#include + +#include +#include +#include + +#include "idacio/backend.h" +#include "idacio/config.h" +#include "idacio/di.h" +#include "idacio/idacio.h" +#include "idacio/xi.h" + +#include "util/dprintf.h" +#include "util/str.h" +#include "util/env.h" + +static struct idac_io_config idac_io_cfg; +static const struct idac_io_backend *idac_io_backend; +static bool idac_io_coin; + +uint16_t idac_io_get_api_version(void) +{ + return 0x0102; +} + +HRESULT idac_io_init(void) +{ + HINSTANCE inst; + HRESULT hr; + + assert(idac_io_backend == NULL); + + inst = GetModuleHandleW(NULL); + + if (inst == NULL) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("GetModuleHandleW failed: %lx\n", hr); + + return hr; + } + + idac_io_config_load(&idac_io_cfg, get_config_path()); + + if (wstr_ieq(idac_io_cfg.mode, L"dinput")) { + hr = idac_di_init(&idac_io_cfg.di, inst, &idac_io_backend); + } else if (wstr_ieq(idac_io_cfg.mode, L"xinput")) { + hr = idac_xi_init(&idac_io_cfg.xi, &idac_io_backend); + } else { + hr = E_INVALIDARG; + dprintf("IDAC IO: Invalid IO mode \"%S\", use dinput or xinput\n", + idac_io_cfg.mode); + } + + return hr; +} + +void idac_io_get_opbtns(uint8_t *opbtn_out) +{ + uint8_t opbtn; + + assert(idac_io_backend != NULL); + assert(opbtn_out != NULL); + + opbtn = 0; + + /* Common operator buttons, not backend-specific */ + + if (GetAsyncKeyState(idac_io_cfg.vk_test) & 0x8000) { + opbtn |= IDAC_IO_OPBTN_TEST; + } + + if (GetAsyncKeyState(idac_io_cfg.vk_service) & 0x8000) { + opbtn |= IDAC_IO_OPBTN_SERVICE; + } + + if (GetAsyncKeyState(idac_io_cfg.vk_coin) & 0x8000) { + if (!idac_io_coin) { + idac_io_coin = true; + opbtn |= IDAC_IO_OPBTN_COIN; + } + } else { + idac_io_coin = false; + } + + *opbtn_out = opbtn; +} + + +void idac_io_get_gamebtns(uint8_t *gamebtn_out) +{ + assert(idac_io_backend != NULL); + assert(gamebtn_out != NULL); + + idac_io_backend->get_gamebtns(gamebtn_out); +} + +void idac_io_get_shifter(uint8_t *gear) +{ + assert(gear != NULL); + assert(idac_io_backend != NULL); + + idac_io_backend->get_shifter(gear); +} + +void idac_io_get_analogs(struct idac_io_analog_state *out) +{ + struct idac_io_analog_state tmp; + + assert(out != NULL); + assert(idac_io_backend != NULL); + + idac_io_backend->get_analogs(&tmp); + + /* Apply steering wheel restriction. Real cabs only report about 77% of + the IO-3's max ADC output value when the wheel is turned to either of + its maximum positions. To match this behavior we set the default value + for the wheel restriction config parameter to 97 (out of 128). This + scaling factor is applied using fixed-point arithmetic below. */ + + out->wheel = (tmp.wheel * idac_io_cfg.restrict_) / 128; + out->accel = tmp.accel; + out->brake = tmp.brake; +} + +HRESULT idac_io_led_init(void) +{ + return S_OK; +} + +void idac_io_led_set_fet_output(uint8_t board, const uint8_t *rgb) +{ +#if 0 + dprintf("IDAC LED: LEFT SEAT LED: %02X\n", rgb[0]); + dprintf("IDAC LED: RIGHT SEAT LED: %02X\n", rgb[1]); +#endif + + return; +} + +void idac_io_led_gs_update(uint8_t board, const uint8_t *rgb) +{ +#if 0 + for (int i = 0; i < 9; i++) { + dprintf("IDAC LED: LED %d: %02X %02X %02X Speed: %02X\n", + i, rgb[i * 4], rgb[i * 4 + 1], rgb[i * 4 + 2], rgb[i * 4 + 3]); + } +#endif + + return; +} + +void idac_io_led_set_leds(uint8_t board, const uint8_t *rgb) +{ +#if 0 + dprintf("IDAC LED: START: %02X\n", rgb[0]); + dprintf("IDAC LED: VIEW CHANGE: %02X\n", rgb[1]); + dprintf("IDAC LED: UP: %02X\n", rgb[2]); + dprintf("IDAC LED: DOWN: %02X\n", rgb[3]); + dprintf("IDAC LED: RIGHT: %02X\n", rgb[4]); + dprintf("IDAC LED: LEFT: %02X\n", rgb[5]); +#endif + + return; +} + +HRESULT idac_io_ffb_init(void) +{ + assert(idac_io_backend != NULL); + + return idac_io_backend->ffb_init(); +} + +void idac_io_ffb_toggle(bool active) +{ + assert(idac_io_backend != NULL); + + idac_io_backend->ffb_toggle(active); +} + +void idac_io_ffb_constant_force(uint8_t direction, uint8_t force) +{ + assert(idac_io_backend != NULL); + + idac_io_backend->ffb_constant_force(direction, force); +} + +void idac_io_ffb_rumble(uint8_t period, uint8_t force) +{ + assert(idac_io_backend != NULL); + + idac_io_backend->ffb_rumble(period, force); +} + +void idac_io_ffb_damper(uint8_t force) +{ + assert(idac_io_backend != NULL); + + idac_io_backend->ffb_damper(force); +} diff --git a/games/idacio/idacio.def b/games/idacio/idacio.def new file mode 100644 index 0000000..f60bacf --- /dev/null +++ b/games/idacio/idacio.def @@ -0,0 +1,17 @@ +LIBRARY idacio + +EXPORTS + idac_io_init + idac_io_get_opbtns + idac_io_get_gamebtns + idac_io_get_shifter + idac_io_get_analogs + idac_io_led_init + idac_io_led_set_fet_output + idac_io_led_gs_update + idac_io_led_set_leds + idac_io_ffb_init + idac_io_ffb_toggle + idac_io_ffb_constant_force + idac_io_ffb_rumble + idac_io_ffb_damper diff --git a/games/idacio/idacio.h b/games/idacio/idacio.h new file mode 100644 index 0000000..bc58a34 --- /dev/null +++ b/games/idacio/idacio.h @@ -0,0 +1,211 @@ +#pragma once + +#include + +#include +#include + +enum { + IDAC_IO_OPBTN_TEST = 0x01, + IDAC_IO_OPBTN_SERVICE = 0x02, + IDAC_IO_OPBTN_COIN = 0x04, +}; + +enum { + IDAC_IO_GAMEBTN_UP = 0x01, + IDAC_IO_GAMEBTN_DOWN = 0x02, + IDAC_IO_GAMEBTN_LEFT = 0x04, + IDAC_IO_GAMEBTN_RIGHT = 0x08, + IDAC_IO_GAMEBTN_START = 0x10, + IDAC_IO_GAMEBTN_VIEW_CHANGE = 0x20, +}; + +enum { + /* These are the bitmasks to use when checking which + lights are triggered on incoming IO4 GPIO writes. */ + IDAC_IO_LED_START = 1 << 31, + IDAC_IO_LED_VIEW_CHANGE = 1 << 30, + IDAC_IO_LED_UP = 1 << 25, + IDAC_IO_LED_DOWN = 1 << 24, + IDAC_IO_LED_LEFT = 1 << 23, + IDAC_IO_LED_RIGHT = 1 << 22, +}; + +struct idac_io_analog_state { + /* Current steering wheel position, where zero is the centered position. + + The game will accept any signed 16-bit position value, however a real + cabinet will report a value of approximately +/- 25230 when the wheel + is at full lock. Steering wheel positions of a magnitude greater than + this value are not possible on a real cabinet. */ + + int16_t wheel; + + /* Current position of the accelerator pedal, where 0 is released. */ + + uint16_t accel; + + /* Current position of the brake pedal, where 0 is released. */ + + uint16_t brake; +}; + +/* Get the version of the IDAC 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 idac_io_get_api_version(void); + +/* Initialize the IO DLL. This is the second function that will be called on + your DLL, after mu3_io_get_api_version. + + All subsequent calls to this API may originate from arbitrary threads. + + Minimum API version: 0x0100 */ + +HRESULT idac_io_init(void); + +/* Get the state of the cabinet's operator buttons as of the last poll. See + IDAC_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 idac_io_get_opbtns(uint8_t *opbtn); + +/* Get the state of the cabinet's gameplay buttons as of the last poll. See + IDAC_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 idac_io_get_gamebtns(uint8_t *gamebtn); + +/* Poll the current state of the cabinet's IO4 analog inputs. See structure + definition above for details. + + Minimum API version: 0x0100 */ + +void idac_io_get_analogs(struct idac_io_analog_state *out); + +/* Poll the current position of the six-speed shifter and return it via the + gear out parameter. Valid values are 0 for neutral and 1-6 for gears 1-6. + + idachook internally translates this gear position value into the correct + combination of Gear Left, Gear Right, Gear Up, Gear Down buttons that the + game will then interpret as the current position of the gear lever. + + Minimum API version: 0x0100 */ + +void idac_io_get_shifter(uint8_t *gear); + +/* Initialize LED emulation. This function will be called before any + other idac_io_led_*() function calls. + + 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: 0x0101 */ + +HRESULT idac_io_led_init(void); + +/* Update the FET outputs. rgb is a pointer to an array up to 3 bytes. + + The following bits are used to control the FET outputs: + [0]: LEFT SEAT LED + [1]: RIGHT SEAT LED + + The LED is truned on when the byte is 255 and turned off when the byte is 0. + + Minimum API version: 0x0101 */ + +void idac_io_led_set_fet_output(uint8_t board, const uint8_t *rgb); + +/* Update the RGB LEDs. rgb is a pointer to an array up to 32 * 4 = 128 bytes. + + The LEDs are laid out as follows: + [0]: LEFT UP LED + [1-2]: LEFT CENTER LED + [3]: LEFT DOWN LED + [5]: RIGHT UP LED + [6-7]: RIGHT CENTER LED + [8]: RIGHT DOWN LED + + Each rgb value is comprised for 4 bytes in the order of R, G, B, Speed. + Speed is a value from 0 to 255, where 0 is the fastest speed and 255 is the slowest. + + Minimum API version: 0x0101 */ + +void idac_io_led_gs_update(uint8_t board, const uint8_t *rgb); + +/* Update the cabinet button LEDs. rgb is a pointer to an array up to 6 bytes. + + The LEDs are laid out as follows: + [0]: START LED + [1]: VIEW CHANGE LED + [2]: UP LED + [3]: DOWN LED + [4]: RIGHT LED + [5]: LEFT LED + + The LED is turned on when the byte is 255 and turned off when the byte is 0. + + Minimum API version: 0x0101 */ + +void idac_io_led_set_leds(uint8_t board, const uint8_t *rgb); + +/* Initialize FFB emulation. This function will be called before any + other idac_io_ffb_*() function calls. + + This will always be called even if FFB board emulation is disabled to allow + the IO DLL to initialize any necessary resources. + + Minimum API version: 0x0102 */ + +HRESULT idac_io_ffb_init(void); + +/* Toggle FFB emulation. If active is true, FFB emulation should be enabled. + If active is false, FFB emulation should be disabled and all FFB effects + should be stopped and released. + + Minimum API version: 0x0102 */ + +void idac_io_ffb_toggle(bool active); + +/* Set a constant force FFB effect. + + Direction is 0 for right and 1 for left. + Force is the magnitude of the force, where 0 is no force and 127 is the + maximum force in a given direction. + + Minimum API version: 0x0102 */ + +void idac_io_ffb_constant_force(uint8_t direction, uint8_t force); + +/* Set a (sine) periodic force FFB effect. + + Period is the period of the effect in milliseconds (not sure). + Force is the magnitude of the force, where 0 is no force and 127 is the + maximum force. + + Minimum API version: 0x0102 */ + +void idac_io_ffb_rumble(uint8_t period, uint8_t force); + +/* Set a damper FFB effect. + + Force is the magnitude of the force, where 0 is no force and 40 is the + maximum force. Theoretically the maximum force is 127, but the game only + uses a maximum of 40. + + Minimum API version: 0x0102 */ + +void idac_io_ffb_damper(uint8_t force); diff --git a/games/idacio/meson.build b/games/idacio/meson.build new file mode 100644 index 0000000..b095fdf --- /dev/null +++ b/games/idacio/meson.build @@ -0,0 +1,31 @@ +idacio_lib = static_library( + 'idacio', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + dependencies : [ + dinput8_lib, + dxguid_lib, + xinput_lib, + ], + link_with : [ + util_lib, + ], + sources : [ + 'backend.h', + 'config.c', + 'config.h', + 'di.c', + 'di.h', + 'di-dev.c', + 'di-dev.h', + 'dllmain.c', + 'idacio.h', + 'shifter.c', + 'shifter.h', + 'wnd.c', + 'wnd.h', + 'xi.c', + 'xi.h', + ], +) diff --git a/games/idacio/shifter.c b/games/idacio/shifter.c new file mode 100644 index 0000000..55de837 --- /dev/null +++ b/games/idacio/shifter.c @@ -0,0 +1,32 @@ +#include +#include + +#include "idacio/shifter.h" + +static bool idac_shifter_shifting; +static uint8_t idac_shifter_gear; + +void idac_shifter_set(uint8_t gear) +{ + idac_shifter_gear = gear; +} + +void idac_shifter_update(bool shift_dn, bool shift_up) +{ + if (!idac_shifter_shifting) { + if (shift_dn && idac_shifter_gear > 0) { + idac_shifter_gear--; + } + + if (shift_up && idac_shifter_gear < 6) { + idac_shifter_gear++; + } + } + + idac_shifter_shifting = shift_dn || shift_up; +} + +uint8_t idac_shifter_current_gear(void) +{ + return idac_shifter_gear; +} diff --git a/games/idacio/shifter.h b/games/idacio/shifter.h new file mode 100644 index 0000000..c32dcf1 --- /dev/null +++ b/games/idacio/shifter.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include + +void idac_shifter_set(uint8_t gear); +void idac_shifter_update(bool shift_dn, bool shift_up); +uint8_t idac_shifter_current_gear(void); diff --git a/games/idacio/wnd.c b/games/idacio/wnd.c new file mode 100644 index 0000000..68720b4 --- /dev/null +++ b/games/idacio/wnd.c @@ -0,0 +1,86 @@ +#include + +#include +#include + +#include "util/dprintf.h" + +/* DirectInput requires a window for correct initialization (and also force + feedback), so this source file provides some utilities for creating a + generic message-only window. */ + +static LRESULT WINAPI idac_io_wnd_proc( + HWND hwnd, + UINT msg, + WPARAM wparam, + LPARAM lparam); + +HRESULT idac_io_wnd_create(HINSTANCE inst, HWND *out) +{ + HRESULT hr; + WNDCLASSEXW wcx; + ATOM atom; + HWND hwnd; + + assert(inst != NULL); /* We are not an EXE */ + assert(out != NULL); + + *out = NULL; + + memset(&wcx, 0, sizeof(wcx)); + wcx.cbSize = sizeof(wcx); + wcx.lpfnWndProc = idac_io_wnd_proc; + wcx.hInstance = inst; + wcx.lpszClassName = L"IDACIO"; + + atom = RegisterClassExW(&wcx); + + if (atom == 0) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("IDACIO: RegisterClassExW failed: %08x\n", (int) hr); + + goto fail; + } + + hwnd = CreateWindowExW( + 0, + (wchar_t *) (intptr_t) atom, + L"", + 0, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + HWND_MESSAGE, + NULL, + inst, + NULL); + + if (hwnd == NULL) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("IDACIO: CreateWindowExW failed: %08x\n", (int) hr); + + goto fail; + } + + *out = hwnd; + + return S_OK; + +fail: + UnregisterClassW((wchar_t *) (intptr_t) atom, inst); + + return hr; +} + +static LRESULT WINAPI idac_io_wnd_proc( + HWND hwnd, + UINT msg, + WPARAM wparam, + LPARAM lparam) +{ + switch (msg) { + default: + return DefWindowProcW(hwnd, msg, wparam, lparam); + } +} diff --git a/games/idacio/wnd.h b/games/idacio/wnd.h new file mode 100644 index 0000000..21a5a8b --- /dev/null +++ b/games/idacio/wnd.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +HRESULT idac_io_wnd_create(HINSTANCE inst, HWND *out); diff --git a/games/idacio/xi.c b/games/idacio/xi.c new file mode 100644 index 0000000..570bf12 --- /dev/null +++ b/games/idacio/xi.c @@ -0,0 +1,250 @@ +#include +#include +#include +#include +#include +#include + +#include "idacio/xi.h" +#include "idacio/backend.h" +#include "idacio/config.h" +#include "idacio/idacio.h" +#include "idacio/shifter.h" + +#include "util/dprintf.h" + +static void idac_xi_get_gamebtns(uint8_t *gamebtn_out); +static void idac_xi_get_shifter(uint8_t *gear); +static void idac_xi_get_analogs(struct idac_io_analog_state *out); + +static HRESULT idac_xi_ffb_init(void); +static void idac_xi_ffb_toggle(bool active); +static void idac_xi_ffb_constant_force(uint8_t direction, uint8_t force); +static void idac_xi_ffb_rumble(uint8_t force, uint8_t period); +static void idac_xi_ffb_damper(uint8_t force); + +static HRESULT idac_xi_config_apply(const struct idac_xi_config *cfg); + +static const struct idac_io_backend idac_xi_backend = { + .get_gamebtns = idac_xi_get_gamebtns, + .get_shifter = idac_xi_get_shifter, + .get_analogs = idac_xi_get_analogs, + .ffb_init = idac_xi_ffb_init, + .ffb_toggle = idac_xi_ffb_toggle, + .ffb_constant_force = idac_xi_ffb_constant_force, + .ffb_rumble = idac_xi_ffb_rumble, + .ffb_damper = idac_xi_ffb_damper +}; + +static bool idac_xi_single_stick_steering; +static bool idac_xi_linear_steering; +static uint16_t idac_xi_left_stick_deadzone; +static uint16_t idac_xi_right_stick_deadzone; + +const uint16_t max_stick_value = 32767; +/* Apply steering wheel restriction. Real cabs only report about 76% of + the output value when the wheel is turned to either of its maximum positions. */ +const uint16_t max_wheel_value = 24831; + +HRESULT idac_xi_init(const struct idac_xi_config *cfg, const struct idac_io_backend **backend) { + HRESULT hr; + assert(cfg != NULL); + assert(backend != NULL); + + hr = idac_xi_config_apply(cfg); + + if (FAILED(hr)) { + return hr; + } + + dprintf("IDACIO: Using XInput controller\n"); + *backend = &idac_xi_backend; + + return S_OK; +} + +static HRESULT idac_xi_config_apply(const struct idac_xi_config *cfg) { + /* Deadzones check */ + if (cfg->left_stick_deadzone > 32767 || cfg->left_stick_deadzone < 0) { + dprintf("XInput: Left stick deadzone is too large or negative\n"); + return E_INVALIDARG; + } + + if (cfg->right_stick_deadzone > 32767 || cfg->right_stick_deadzone < 0) { + dprintf("XInput: Right stick deadzone is too large or negative\n"); + return E_INVALIDARG; + } + + dprintf("XInput: --- Begin configuration ---\n"); + dprintf("XInput: Single Stick Steering : %i\n", cfg->single_stick_steering); + dprintf("XInput: Linear Steering . . . : %i\n", cfg->linear_steering); + dprintf("XInput: Left Deadzone . . . . : %i\n", cfg->left_stick_deadzone); + dprintf("XInput: Right Deadzone . . . : %i\n", cfg->right_stick_deadzone); + dprintf("XInput: --- End configuration ---\n"); + + idac_xi_single_stick_steering = cfg->single_stick_steering; + idac_xi_linear_steering = cfg->linear_steering; + idac_xi_left_stick_deadzone = cfg->left_stick_deadzone; + idac_xi_right_stick_deadzone = cfg->right_stick_deadzone; + + return S_OK; +} + +static void idac_xi_get_gamebtns(uint8_t *gamebtn_out) { + uint8_t gamebtn; + XINPUT_STATE xi; + WORD xb; + + assert(gamebtn_out != NULL); + + gamebtn = 0; + + memset(&xi, 0, sizeof(xi)); + XInputGetState(0, &xi); + xb = xi.Gamepad.wButtons; + + if (xb & XINPUT_GAMEPAD_DPAD_UP) { + gamebtn |= IDAC_IO_GAMEBTN_UP; + } + + if (xb & XINPUT_GAMEPAD_DPAD_DOWN) { + gamebtn |= IDAC_IO_GAMEBTN_DOWN; + } + + if (xb & (XINPUT_GAMEPAD_DPAD_LEFT | XINPUT_GAMEPAD_LEFT_THUMB)) { + gamebtn |= IDAC_IO_GAMEBTN_LEFT; + } + + if (xb & (XINPUT_GAMEPAD_DPAD_RIGHT | XINPUT_GAMEPAD_RIGHT_THUMB)) { + gamebtn |= IDAC_IO_GAMEBTN_RIGHT; + } + + if (xb & (XINPUT_GAMEPAD_START | XINPUT_GAMEPAD_A)) { + gamebtn |= IDAC_IO_GAMEBTN_START; + } + + if (xb & (XINPUT_GAMEPAD_BACK | XINPUT_GAMEPAD_B)) { + gamebtn |= IDAC_IO_GAMEBTN_VIEW_CHANGE; + } + + *gamebtn_out = gamebtn; +} + +static void idac_xi_get_shifter(uint8_t *gear) { + bool shift_dn; + bool shift_up; + XINPUT_STATE xi; + WORD xb; + + assert(gear != NULL); + + memset(&xi, 0, sizeof(xi)); + XInputGetState(0, &xi); + xb = xi.Gamepad.wButtons; + + if (xb & XINPUT_GAMEPAD_START) { + /* Reset to Neutral when start is pressed */ + idac_shifter_set(0); + } + + shift_dn = xb & (XINPUT_GAMEPAD_Y | XINPUT_GAMEPAD_LEFT_SHOULDER); + shift_up = xb & (XINPUT_GAMEPAD_X | XINPUT_GAMEPAD_RIGHT_SHOULDER); + + idac_shifter_update(shift_dn, shift_up); + + *gear = idac_shifter_current_gear(); +} + +static int16_t calculate_norm_steering(int16_t axis, uint16_t deadzone, bool linear_steering) { + // determine how far the controller is pushed + float magnitude = sqrt(axis*axis); + + // determine the direction the controller is pushed + float norm_axis = axis / magnitude; + + float norm_magnitude = 0.0; + + // check if the controller is outside a circular dead zone + if (magnitude > deadzone) + { + // clip the magnitude at its expected maximum value + if (magnitude > max_stick_value) magnitude = max_stick_value; + + // adjust magnitude relative to the end of the dead zone + magnitude -= deadzone; + + // optionally normalize the magnitude with respect to its expected range + // giving a magnitude value of 0.0 to 1.0 + norm_magnitude = magnitude / (max_stick_value - deadzone); + } else // if the controller is in the deadzone zero out the magnitude + { + magnitude = 0.0; + norm_magnitude = 0.0; + } + + // apply non-linear transform to the axis + if (!linear_steering) { + return (int16_t)(norm_axis * powf(norm_magnitude, 3.0f) * max_wheel_value); + } + + return (int16_t)(norm_axis * norm_magnitude * max_wheel_value); +} + +static void idac_xi_get_analogs(struct idac_io_analog_state *out) { + XINPUT_STATE xi; + int left; + int right; + + assert(out != NULL); + + memset(&xi, 0, sizeof(xi)); + XInputGetState(0, &xi); + + left = xi.Gamepad.sThumbLX; + right = xi.Gamepad.sThumbRX; + + // normalize the steering axis + left = calculate_norm_steering(left, idac_xi_left_stick_deadzone, idac_xi_linear_steering); + right = calculate_norm_steering(right, idac_xi_right_stick_deadzone, idac_xi_linear_steering); + + if (idac_xi_single_stick_steering) { + out->wheel = left; + } else { + out->wheel = (left + right) / 2; + } + + out->accel = xi.Gamepad.bRightTrigger << 8; + out->brake = xi.Gamepad.bLeftTrigger << 8; +} + +static HRESULT idac_xi_ffb_init(void) { + return S_OK; +} + +static void idac_xi_ffb_toggle(bool active) { + XINPUT_VIBRATION vibration; + + memset(&vibration, 0, sizeof(vibration)); + + XInputSetState(0, &vibration); +} + +static void idac_xi_ffb_constant_force(uint8_t direction, uint8_t force) { + return; +} + +static void idac_xi_ffb_rumble(uint8_t force, uint8_t period) { + XINPUT_VIBRATION vibration; + /* XInput max strength is 65.535, so multiply the 127.0 by 516. */ + uint16_t strength = force * 516; + + memset(&vibration, 0, sizeof(vibration)); + vibration.wLeftMotorSpeed = strength; + vibration.wRightMotorSpeed = strength; + + XInputSetState(0, &vibration); +} + +static void idac_xi_ffb_damper(uint8_t force) { + return; +} diff --git a/games/idacio/xi.h b/games/idacio/xi.h new file mode 100644 index 0000000..16cdea5 --- /dev/null +++ b/games/idacio/xi.h @@ -0,0 +1,10 @@ +#pragma once + +/* Can't call this xinput.h or it will conflict with */ + +#include + +#include "idacio/backend.h" +#include "idacio/config.h" + +HRESULT idac_xi_init(const struct idac_xi_config *cfg, const struct idac_io_backend **backend); diff --git a/idzhook/config.c b/games/idzhook/config.c similarity index 51% rename from idzhook/config.c rename to games/idzhook/config.c index db83b9f..6bbc0ed 100644 --- a/idzhook/config.c +++ b/games/idzhook/config.c @@ -1,5 +1,6 @@ #include #include +#include #include "amex/amex.h" #include "amex/config.h" @@ -18,6 +19,43 @@ #include "platform/config.h" #include "platform/platform.h" +void led15070_config_load(struct led15070_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + wchar_t tmpstr[16]; + + cfg->enable = GetPrivateProfileIntW(L"led15070", L"enable", 1, filename); + cfg->port_no[0] = GetPrivateProfileIntW(L"led15070", L"portNo1", 0, filename); + cfg->port_no[1] = GetPrivateProfileIntW(L"led15070", L"portNo2", 0, filename); + cfg->fw_ver = GetPrivateProfileIntW(L"led15070", L"fwVer", 0x90, filename); + /* TODO: Unknown, no firmware file available */ + cfg->fw_sum = GetPrivateProfileIntW(L"led15070", L"fwSum", 0x0000, filename); + + GetPrivateProfileStringW( + L"led15070", + L"boardNumber", + L"15070-02", + tmpstr, + _countof(tmpstr), + filename); + + size_t n = wcstombs(cfg->board_number, tmpstr, sizeof(cfg->board_number)); + for (int i = n; i < sizeof(cfg->board_number); i++) + { + cfg->board_number[i] = ' '; + } + + GetPrivateProfileStringW( + L"led15070", + L"eepromPath", + L"DEVICE", + cfg->eeprom_path, + _countof(cfg->eeprom_path), + filename); +} + void idz_dll_config_load( struct idz_dll_config *cfg, const wchar_t *filename) @@ -47,6 +85,8 @@ void idz_hook_config_load( dvd_config_load(&cfg->dvd, filename); gfx_config_load(&cfg->gfx, filename); idz_dll_config_load(&cfg->dll, filename); + ffb_config_load(&cfg->ffb, filename); + led15070_config_load(&cfg->led15070, filename); zinput_config_load(&cfg->zinput, filename); } diff --git a/idzhook/config.h b/games/idzhook/config.h similarity index 86% rename from idzhook/config.h rename to games/idzhook/config.h index 0684949..47174ad 100644 --- a/idzhook/config.h +++ b/games/idzhook/config.h @@ -5,7 +5,8 @@ #include "amex/amex.h" -#include "board/sg-reader.h" +#include "board/config.h" +#include "board/led15070.h" #include "gfxhook/gfx.h" @@ -23,6 +24,8 @@ struct idz_hook_config { struct dvd_config dvd; struct gfx_config gfx; struct idz_dll_config dll; + struct ffb_config ffb; + struct led15070_config led15070; struct zinput_config zinput; }; diff --git a/idzhook/dllmain.c b/games/idzhook/dllmain.c similarity index 73% rename from idzhook/dllmain.c rename to games/idzhook/dllmain.c index fe78b6f..534f4d0 100644 --- a/idzhook/dllmain.c +++ b/games/idzhook/dllmain.c @@ -1,3 +1,14 @@ +/* + "Initial D ARCADE STAGE Zero" (idz) hook + + Devices + + JVS: 837-15257 "Type 4" I/O Board + COM1: 838-15069 MOTOR DRIVE BD RS232/422 Board + COM10: 837-15286 "Gen 2" Aime Reader + COM11: 837-15070-02 IC BD LED Controller Board +*/ + #include #include @@ -22,12 +33,14 @@ #include "idzhook/config.h" #include "idzhook/idz-dll.h" #include "idzhook/jvs.h" +#include "idzhook/ffb.h" #include "idzhook/zinput.h" #include "platform/platform.h" #include "util/dprintf.h" #include "util/lib.h" +#include "util/env.h" static HMODULE idz_hook_mod; static process_entry_t idz_startup; @@ -43,8 +56,8 @@ static DWORD CALLBACK idz_pre_startup(void) /* Config load */ - idz_hook_config_load(&idz_hook_cfg, L".\\segatools.ini"); - + idz_hook_config_load(&idz_hook_cfg, get_config_path()); + module_path = module_file_name(NULL); if (module_path != NULL) { @@ -52,7 +65,7 @@ static DWORD CALLBACK idz_pre_startup(void) _wcslwr(file_name); - if (wcsstr(file_name, L"serverbox") != NULL) { + if (wcsstr(file_name, L"ServerBox") != NULL) { dprintf("Executable filename contains 'ServerBox', disabling full-screen mode\n"); idz_hook_cfg.gfx.windowed = true; @@ -91,13 +104,33 @@ static DWORD CALLBACK idz_pre_startup(void) goto fail; } + hr = idz_jvs_hook_init(); + + if (FAILED(hr)) { + goto fail; + } + hr = amex_hook_init(&idz_hook_cfg.amex, idz_jvs_init); if (FAILED(hr)) { goto fail; } - hr = sg_reader_hook_init(&idz_hook_cfg.aime, 10, idz_hook_mod); + hr = sg_reader_hook_init(&idz_hook_cfg.aime, 10, 1, idz_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = idz_ffb_hook_init(&idz_hook_cfg.ffb, 1); + + if (FAILED(hr)) { + goto fail; + } + + unsigned int led_port_no[2] = {11, 0}; + hr = led15070_hook_init(&idz_hook_cfg.led15070, idz_dll.led_init, + idz_dll.led_set_fet_output, NULL, idz_dll.led_gs_update, led_port_no); if (FAILED(hr)) { goto fail; @@ -105,7 +138,7 @@ static DWORD CALLBACK idz_pre_startup(void) /* Initialize debug helpers */ - spike_hook_init(L".\\segatools.ini"); + spike_hook_init(get_config_path()); dprintf("--- End idz_pre_startup ---\n"); diff --git a/games/idzhook/ffb.c b/games/idzhook/ffb.c new file mode 100644 index 0000000..7ac1086 --- /dev/null +++ b/games/idzhook/ffb.c @@ -0,0 +1,59 @@ +#include + +#include +#include +#include +#include + +#include "board/ffb.h" + +#include "idzhook/idz-dll.h" + +#include "util/dprintf.h" + +static void idz_ffb_toggle(bool active); +static void idz_ffb_constant_force(uint8_t direction, uint8_t force); +static void idz_ffb_rumble(uint8_t force, uint8_t period); +static void idz_ffb_damper(uint8_t force); + +static const struct ffb_ops idz_ffb_ops = { + .toggle = idz_ffb_toggle, + .constant_force = idz_ffb_constant_force, + .rumble = idz_ffb_rumble, + .damper = idz_ffb_damper +}; + +HRESULT idz_ffb_hook_init(const struct ffb_config *cfg, unsigned int port_no) +{ + HRESULT hr; + + assert(idz_dll.jvs_init != NULL); + + hr = ffb_hook_init(cfg, &idz_ffb_ops, port_no); + + if (FAILED(hr)) { + return hr; + } + + return idz_dll.ffb_init(); +} + +static void idz_ffb_toggle(bool active) +{ + idz_dll.ffb_toggle(active); +} + +static void idz_ffb_constant_force(uint8_t direction, uint8_t force) +{ + idz_dll.ffb_constant_force(direction, force); +} + +static void idz_ffb_rumble(uint8_t force, uint8_t period) +{ + idz_dll.ffb_rumble(force, period); +} + +static void idz_ffb_damper(uint8_t force) +{ + idz_dll.ffb_damper(force); +} diff --git a/games/idzhook/ffb.h b/games/idzhook/ffb.h new file mode 100644 index 0000000..eaee98c --- /dev/null +++ b/games/idzhook/ffb.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "board/ffb.h" + +HRESULT idz_ffb_hook_init(const struct ffb_config *cfg, unsigned int port_no); diff --git a/idzhook/idz-dll.c b/games/idzhook/idz-dll.c similarity index 77% rename from idzhook/idz-dll.c rename to games/idzhook/idz-dll.c index eb1b6e1..a497319 100644 --- a/idzhook/idz-dll.c +++ b/games/idzhook/idz-dll.c @@ -24,6 +24,33 @@ const struct dll_bind_sym idz_dll_syms[] = { }, { .sym = "idz_io_jvs_read_coin_counter", .off = offsetof(struct idz_dll, jvs_read_coin_counter), + }, { + .sym = "idz_io_led_init", + .off = offsetof(struct idz_dll, led_init), + }, { + .sym = "idz_io_led_set_fet_output", + .off = offsetof(struct idz_dll, led_set_fet_output), + }, { + .sym = "idz_io_led_gs_update", + .off = offsetof(struct idz_dll, led_gs_update), + }, { + .sym = "idz_io_led_set_leds", + .off = offsetof(struct idz_dll, led_set_leds), + }, { + .sym = "idz_io_ffb_init", + .off = offsetof(struct idz_dll, ffb_init), + }, { + .sym = "idz_io_ffb_toggle", + .off = offsetof(struct idz_dll, ffb_toggle), + }, { + .sym = "idz_io_ffb_constant_force", + .off = offsetof(struct idz_dll, ffb_constant_force), + }, { + .sym = "idz_io_ffb_rumble", + .off = offsetof(struct idz_dll, ffb_rumble), + }, { + .sym = "idz_io_ffb_damper", + .off = offsetof(struct idz_dll, ffb_damper), } }; diff --git a/idzhook/idz-dll.h b/games/idzhook/idz-dll.h similarity index 53% rename from idzhook/idz-dll.h rename to games/idzhook/idz-dll.h index e67cb45..4084d84 100644 --- a/idzhook/idz-dll.h +++ b/games/idzhook/idz-dll.h @@ -11,6 +11,15 @@ struct idz_dll { void (*jvs_read_buttons)(uint8_t *opbtn, uint8_t *gamebtn); void (*jvs_read_shifter)(uint8_t *gear); void (*jvs_read_coin_counter)(uint16_t *total); + HRESULT (*led_init)(void); + void (*led_set_fet_output)(uint8_t board, const uint8_t *rgb); + void (*led_gs_update)(uint8_t board, const uint8_t *rgb); + void (*led_set_leds)(uint8_t board, const uint8_t *rgb); + HRESULT (*ffb_init)(void); + void (*ffb_toggle)(bool active); + void (*ffb_constant_force)(uint8_t direction, uint8_t force); + void (*ffb_rumble)(uint8_t period, uint8_t force); + void (*ffb_damper)(uint8_t force); }; struct idz_dll_config { diff --git a/idzhook/idzhook.def b/games/idzhook/idzhook.def similarity index 72% rename from idzhook/idzhook.def rename to games/idzhook/idzhook.def index d8db3b0..53e01c6 100644 --- a/idzhook/idzhook.def +++ b/games/idzhook/idzhook.def @@ -22,3 +22,12 @@ EXPORTS idz_io_jvs_read_buttons idz_io_jvs_read_coin_counter idz_io_jvs_read_shifter + idz_io_led_init + idz_io_led_set_fet_output + idz_io_led_gs_update + idz_io_led_set_leds + idz_io_ffb_init + idz_io_ffb_toggle + idz_io_ffb_constant_force + idz_io_ffb_rumble + idz_io_ffb_damper diff --git a/idzhook/jvs.c b/games/idzhook/jvs.c similarity index 79% rename from idzhook/jvs.c rename to games/idzhook/jvs.c index ab2f6aa..2ded71c 100644 --- a/idzhook/jvs.c +++ b/games/idzhook/jvs.c @@ -24,11 +24,13 @@ static void idz_jvs_read_coin_counter( void *ctx, uint8_t slot_no, uint16_t *out); +static void idz_jvs_write_gpio(void *ctx, uint32_t state); static const struct io3_ops idz_jvs_io3_ops = { .read_switches = idz_jvs_read_switches, .read_analogs = idz_jvs_read_analogs, .read_coin_counter = idz_jvs_read_coin_counter, + .write_gpio = idz_jvs_write_gpio }; static const uint16_t idz_jvs_gear_signals[] = { @@ -50,21 +52,20 @@ static const uint16_t idz_jvs_gear_signals[] = { static struct io3 idz_jvs_io3; +HRESULT idz_jvs_hook_init(void) +{ + HRESULT hr; + + assert(idz_dll.jvs_init != NULL); + + return idz_dll.jvs_init(); +} + HRESULT idz_jvs_init(struct jvs_node **out) { HRESULT hr; assert(out != NULL); - assert(idz_dll.jvs_init != NULL); - - dprintf("JVS I/O: Starting Initial D Zero backend DLL\n"); - hr = idz_dll.jvs_init(); - - if (FAILED(hr)) { - dprintf("JVS I/O: Backend error, I/O disconnected; %x\n", (int) hr); - - return hr; - } io3_init(&idz_jvs_io3, NULL, &idz_jvs_io3_ops, NULL); *out = io3_to_jvs_node(&idz_jvs_io3); @@ -175,3 +176,21 @@ static void idz_jvs_read_coin_counter( idz_dll.jvs_read_coin_counter(out); } +static void idz_jvs_write_gpio(void *ctx, uint32_t state) +{ + assert(idz_dll.led_set_leds != NULL); + + // Since Sega uses an odd ordering for the first part of the bitfield, + // let's normalize the data and just send over bytes for the receiver + // to interpret as ON/OFF values. + uint8_t rgb_out[6] = { + state & IDZ_IO_LED_START ? 0xFF : 0x00, + state & IDZ_IO_LED_VIEW_CHANGE ? 0xFF : 0x00, + state & IDZ_IO_LED_UP ? 0xFF : 0x00, + state & IDZ_IO_LED_DOWN ? 0xFF : 0x00, + state & IDZ_IO_LED_RIGHT ? 0xFF : 0x00, + state & IDZ_IO_LED_LEFT ? 0xFF : 0x00, + }; + + idz_dll.led_set_leds(0, rgb_out); +} diff --git a/idzhook/jvs.h b/games/idzhook/jvs.h similarity index 76% rename from idzhook/jvs.h rename to games/idzhook/jvs.h index 6a7c72a..82de143 100644 --- a/idzhook/jvs.h +++ b/games/idzhook/jvs.h @@ -4,4 +4,6 @@ #include "jvs/jvs-bus.h" +HRESULT idz_jvs_hook_init(void); + HRESULT idz_jvs_init(struct jvs_node **root); diff --git a/idzhook/meson.build b/games/idzhook/meson.build similarity index 95% rename from idzhook/meson.build rename to games/idzhook/meson.build index ab90815..6649489 100644 --- a/idzhook/meson.build +++ b/games/idzhook/meson.build @@ -4,7 +4,6 @@ shared_library( include_directories : inc, implicit_include_directories : false, vs_module_defs : 'idzhook.def', - c_pch : '../precompiled.h', dependencies : [ capnhook.get_variable('hook_dep'), capnhook.get_variable('hooklib_dep'), @@ -32,5 +31,7 @@ shared_library( 'jvs.h', 'zinput.c', 'zinput.h', + 'ffb.c', + 'ffb.h', ], ) diff --git a/idzhook/zinput.c b/games/idzhook/zinput.c similarity index 99% rename from idzhook/zinput.c rename to games/idzhook/zinput.c index dbc3b47..23e0e30 100644 --- a/idzhook/zinput.c +++ b/games/idzhook/zinput.c @@ -1,6 +1,7 @@ #include #include +#include #include #include #include diff --git a/games/idzhook/zinput.h b/games/idzhook/zinput.h new file mode 100644 index 0000000..13a46cd --- /dev/null +++ b/games/idzhook/zinput.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#include + +struct zinput_config { + bool enable; +}; + +HRESULT zinput_hook_init(struct zinput_config *cfg); diff --git a/idzio/backend.h b/games/idzio/backend.h similarity index 51% rename from idzio/backend.h rename to games/idzio/backend.h index e0a958f..1993076 100644 --- a/idzio/backend.h +++ b/games/idzio/backend.h @@ -8,4 +8,9 @@ struct idz_io_backend { void (*jvs_read_buttons)(uint8_t *gamebtn); void (*jvs_read_shifter)(uint8_t *gear); void (*jvs_read_analogs)(struct idz_io_analog_state *state); + HRESULT (*ffb_init)(void); + void (*ffb_toggle)(bool active); + void (*ffb_constant_force)(uint8_t direction, uint8_t force); + void (*ffb_rumble)(uint8_t period, uint8_t force); + void (*ffb_damper)(uint8_t force); }; diff --git a/idzio/config.c b/games/idzio/config.c similarity index 64% rename from idzio/config.c rename to games/idzio/config.c index d06476b..369a4c5 100644 --- a/idzio/config.c +++ b/games/idzio/config.c @@ -24,6 +24,14 @@ void idz_di_config_load(struct idz_di_config *cfg, const wchar_t *filename) _countof(cfg->device_name), filename); + GetPrivateProfileStringW( + L"dinput", + L"pedalsName", + L"", + cfg->pedals_name, + _countof(cfg->pedals_name), + filename); + GetPrivateProfileStringW( L"dinput", L"shifterName", @@ -68,7 +76,31 @@ void idz_di_config_load(struct idz_di_config *cfg, const wchar_t *filename) swprintf_s(key, _countof(key), L"gear%i", i + 1); cfg->gear[i] = GetPrivateProfileIntW(L"dinput", key, i + 1, filename); } + + // FFB configuration + cfg->ffb_constant_force_strength = GetPrivateProfileIntW( + L"dinput", + L"constantForceStrength", + 100, + filename); + cfg->ffb_rumble_strength = GetPrivateProfileIntW( + L"dinput", + L"rumbleStrength", + 100, + filename); + + cfg->ffb_damper_strength = GetPrivateProfileIntW( + L"dinput", + L"damperStrength", + 100, + filename); + + cfg->ffb_rumble_duration = GetPrivateProfileIntW( + L"dinput", + L"rumbleDuration", + 1000, + filename); } void idz_xi_config_load(struct idz_xi_config *cfg, const wchar_t *filename) @@ -77,9 +109,27 @@ void idz_xi_config_load(struct idz_xi_config *cfg, const wchar_t *filename) assert(filename != NULL); cfg->single_stick_steering = GetPrivateProfileIntW( - L"io3", + L"xinput", L"singleStickSteering", - 0, + 1, + filename); + + cfg->linear_steering = GetPrivateProfileIntW( + L"xinput", + L"linearSteering", + 0, + filename); + + cfg->left_stick_deadzone = GetPrivateProfileIntW( + L"xinput", + L"leftStickDeadzone", + 7849, + filename); + + cfg->right_stick_deadzone = GetPrivateProfileIntW( + L"xinput", + L"rightStickDeadzone", + 8689, filename); } @@ -88,9 +138,10 @@ void idz_io_config_load(struct idz_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); + /* Technically it's io4 */ + cfg->vk_test = GetPrivateProfileIntW(L"io3", L"test", VK_F1, filename); + cfg->vk_service = GetPrivateProfileIntW(L"io3", L"service", VK_F2, filename); + cfg->vk_coin = GetPrivateProfileIntW(L"io3", L"coin", VK_F3, filename); cfg->restrict_ = GetPrivateProfileIntW(L"io3", L"restrict", 97, filename); GetPrivateProfileStringW( diff --git a/idzio/config.h b/games/idzio/config.h similarity index 78% rename from idzio/config.h rename to games/idzio/config.h index 19b837a..7a17f00 100644 --- a/idzio/config.h +++ b/games/idzio/config.h @@ -11,6 +11,7 @@ struct idz_shifter_config { struct idz_di_config { wchar_t device_name[64]; wchar_t shifter_name[64]; + wchar_t pedals_name[64]; wchar_t brake_axis[16]; wchar_t accel_axis[16]; uint8_t start; @@ -20,10 +21,20 @@ struct idz_di_config { uint8_t gear[6]; bool reverse_brake_axis; bool reverse_accel_axis; + + // FFB configuration + uint8_t ffb_constant_force_strength; + uint8_t ffb_rumble_strength; + uint8_t ffb_damper_strength; + + uint32_t ffb_rumble_duration; }; struct idz_xi_config { bool single_stick_steering; + bool linear_steering; + uint16_t left_stick_deadzone; + uint16_t right_stick_deadzone; }; struct idz_io_config { diff --git a/games/idzio/di-dev.c b/games/idzio/di-dev.c new file mode 100644 index 0000000..10b67d6 --- /dev/null +++ b/games/idzio/di-dev.c @@ -0,0 +1,371 @@ +#include +#include +#include +#include + +#include "idzio/di-dev.h" + +#include "util/dprintf.h" + +const struct idz_di_config *idz_di_cfg; +static HWND idz_di_wnd; +static IDirectInputDevice8W *idz_di_dev; + +/* Individual DI Effects */ +static IDirectInputEffect *idz_di_fx; +static IDirectInputEffect *idz_di_fx_rumble; +static IDirectInputEffect *idz_di_fx_damper; + +/* Max FFB Board value is 127 */ +static const double idz_di_ffb_scale = 127.0; + +HRESULT idz_di_dev_init( + const struct idz_di_config *cfg, + IDirectInputDevice8W *dev, + HWND wnd) +{ + HRESULT hr; + + assert(dev != NULL); + assert(wnd != NULL); + + idz_di_cfg = cfg; + idz_di_dev = dev; + idz_di_wnd = wnd; + + return S_OK; +} + +HRESULT idz_di_dev_poll( + IDirectInputDevice8W *dev, + HWND wnd, + union idz_di_state *out) +{ + HRESULT hr; + MSG msg; + + assert(dev != NULL); + assert(wnd != NULL); + assert(out != NULL); + + memset(out, 0, sizeof(*out)); + + /* Pump our dummy window's message queue just in case DirectInput or an + IHV DirectInput driver somehow relies on it */ + + while (PeekMessageW(&msg, wnd, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + hr = IDirectInputDevice8_GetDeviceState( + dev, + sizeof(out->st), + &out->st); + + if (FAILED(hr)) { + dprintf("DirectInput: GetDeviceState error: %08x\n", (int)hr); + } + + return hr; +} + +HRESULT idz_di_dev_start(IDirectInputDevice8W *dev, HWND wnd) { + HRESULT hr; + + assert(dev != NULL); + assert(wnd != NULL); + + hr = IDirectInputDevice8_SetCooperativeLevel( + dev, + wnd, + DISCL_BACKGROUND | DISCL_EXCLUSIVE); + + if (FAILED(hr)) { + dprintf("DirectInput: SetCooperativeLevel failed: %08x\n", (int)hr); + return hr; + } + + hr = IDirectInputDevice8_SetDataFormat(dev, &c_dfDIJoystick); + + if (FAILED(hr)) { + dprintf("DirectInput: SetDataFormat failed: %08x\n", (int)hr); + return hr; + } + + hr = IDirectInputDevice8_Acquire(dev); + + if (FAILED(hr)) { + dprintf("DirectInput: Acquire failed: %08x\n", (int)hr); + return hr; + } + + return hr; +} + +HRESULT idz_di_ffb_init(void) +{ + HRESULT hr; + + hr = idz_di_dev_start(idz_di_dev, idz_di_wnd); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} + +void idz_di_ffb_toggle(bool active) +{ + if (active) { + return; + } + + /* Stop and release all effects */ + /* I never programmed DirectInput Effects, so this might be bad practice. */ + if (idz_di_fx != NULL) { + IDirectInputEffect_Stop(idz_di_fx); + IDirectInputEffect_Release(idz_di_fx); + idz_di_fx = NULL; + } + + if (idz_di_fx_rumble != NULL) { + IDirectInputEffect_Stop(idz_di_fx_rumble); + IDirectInputEffect_Release(idz_di_fx_rumble); + idz_di_fx_rumble = NULL; + } + + if (idz_di_fx_damper != NULL) { + IDirectInputEffect_Stop(idz_di_fx_damper); + IDirectInputEffect_Release(idz_di_fx_damper); + idz_di_fx_damper = NULL; + } +} + +void idz_di_ffb_constant_force(uint8_t direction_ffb, uint8_t force) +{ + /* DI expects a magnitude in the range of -10.000 to 10.000 */ + uint16_t ffb_strength = idz_di_cfg->ffb_constant_force_strength * 100; + if (ffb_strength == 0) { + return; + } + + DWORD axis; + LONG direction; + DIEFFECT fx; + DICONSTANTFORCE cf; + HRESULT hr; + + /* Direction 0: move to the right, 1: move to the left */ + LONG magnitude = (LONG)(((double)force / idz_di_ffb_scale) * ffb_strength); + cf.lMagnitude = (direction_ffb == 0) ? -magnitude : magnitude; + + axis = DIJOFS_X; + direction = 0; + + memset(&fx, 0, sizeof(fx)); + fx.dwSize = sizeof(fx); + fx.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; + fx.dwDuration = INFINITE; + fx.dwGain = DI_FFNOMINALMAX; + fx.dwTriggerButton = DIEB_NOTRIGGER; + fx.dwTriggerRepeatInterval = INFINITE; + fx.cAxes = 1; + fx.rgdwAxes = &axis; + fx.rglDirection = &direction; + fx.cbTypeSpecificParams = sizeof(cf); + fx.lpvTypeSpecificParams = &cf; + + /* Check if the effect already exists */ + if (idz_di_fx != NULL) { + hr = IDirectInputEffect_SetParameters(idz_di_fx, &fx, DIEP_TYPESPECIFICPARAMS); + if (SUCCEEDED(hr)) { + return; // Successfully updated existing effect + } + else { + dprintf("DirectInput: Failed to update constant force feedback, recreating effect: %08x\n", (int)hr); + IDirectInputEffect_Stop(idz_di_fx); + IDirectInputEffect_Release(idz_di_fx); + idz_di_fx = NULL; // Reset the pointer + } + } + + /* Create a new constant force effect */ + IDirectInputEffect *obj; + hr = IDirectInputDevice8_CreateEffect( + idz_di_dev, + &GUID_ConstantForce, + &fx, + &obj, + NULL); + + if (FAILED(hr)) { + dprintf("DirectInput: Constant force feedback creation failed: %08x\n", (int)hr); + return; + } + + /* Start the effect */ + hr = IDirectInputEffect_Start(obj, INFINITE, 0); + if (FAILED(hr)) { + dprintf("DirectInput: Constant force feedback start failed: %08x\n", (int)hr); + IDirectInputEffect_Release(obj); + return; + } + + idz_di_fx = obj; +} + +void idz_di_ffb_rumble(uint8_t force, uint8_t period) +{ + /* DI expects a magnitude in the range of -10.000 to 10.000 */ + uint16_t ffb_strength = idz_di_cfg->ffb_rumble_strength * 100; + if (ffb_strength == 0) { + return; + } + + uint32_t ffb_duration = idz_di_cfg->ffb_rumble_duration; + + DWORD axis; + LONG direction; + DIEFFECT fx; + DIPERIODIC pe; + HRESULT hr; + + DWORD duration = (DWORD)((double)force * ffb_duration); + + memset(&pe, 0, sizeof(pe)); + pe.dwMagnitude = (DWORD)(((double)force / idz_di_ffb_scale) * ffb_strength); + pe.lOffset = 0; + pe.dwPhase = 0; + pe.dwPeriod = duration; + + axis = DIJOFS_X; + direction = 0; + + memset(&fx, 0, sizeof(fx)); + fx.dwSize = sizeof(fx); + fx.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; + fx.dwDuration = INFINITE; + fx.dwGain = DI_FFNOMINALMAX; + fx.dwTriggerButton = DIEB_NOTRIGGER; + fx.dwTriggerRepeatInterval = INFINITE; + fx.cAxes = 1; + fx.rgdwAxes = &axis; + fx.rglDirection = &direction; + fx.cbTypeSpecificParams = sizeof(pe); + fx.lpvTypeSpecificParams = &pe; + + /* Check if the effect already exists */ + if (idz_di_fx_rumble != NULL) { + hr = IDirectInputEffect_SetParameters(idz_di_fx_rumble, &fx, DIEP_TYPESPECIFICPARAMS); + if (SUCCEEDED(hr)) { + return; + } + else { + dprintf("DirectInput: Failed to update rumble feedback, recreating effect: %08x\n", (int)hr); + IDirectInputEffect_Stop(idz_di_fx_rumble); + IDirectInputEffect_Release(idz_di_fx_rumble); + idz_di_fx_rumble = NULL; + } + } + + /* Create a new rumble effect */ + IDirectInputEffect *obj; + hr = IDirectInputDevice8_CreateEffect( + idz_di_dev, + &GUID_Sine, + &fx, + &obj, + NULL); + + if (FAILED(hr)) { + dprintf("DirectInput: Rumble effect creation failed: %08x\n", (int)hr); + return; + } + + /* Start the effect */ + hr = IDirectInputEffect_Start(obj, INFINITE, 0); + if (FAILED(hr)) { + dprintf("DirectInput: Rumble effect start failed: %08x\n", (int)hr); + IDirectInputEffect_Release(obj); + return; + } + + idz_di_fx_rumble = obj; +} + +void idz_di_ffb_damper(uint8_t force) +{ + /* DI expects a coefficient in the range of -10.000 to 10.000 */ + uint16_t ffb_strength = idz_di_cfg->ffb_damper_strength * 100; + if (ffb_strength == 0) { + return; + } + + DWORD axis; + LONG direction; + DIEFFECT fx; + DICONDITION cond; + HRESULT hr; + + memset(&cond, 0, sizeof(cond)); + cond.lOffset = 0; + cond.lPositiveCoefficient = (LONG)(((double)force / idz_di_ffb_scale) * ffb_strength); + cond.lNegativeCoefficient = (LONG)(((double)force / idz_di_ffb_scale) * ffb_strength); + /* Not sure on this one */ + cond.dwPositiveSaturation = DI_FFNOMINALMAX; + cond.dwNegativeSaturation = DI_FFNOMINALMAX; + cond.lDeadBand = 0; + + axis = DIJOFS_X; + direction = 0; + + memset(&fx, 0, sizeof(fx)); + fx.dwSize = sizeof(fx); + fx.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; + fx.dwDuration = INFINITE; + fx.dwGain = DI_FFNOMINALMAX; + fx.dwTriggerButton = DIEB_NOTRIGGER; + fx.dwTriggerRepeatInterval = INFINITE; + fx.cAxes = 1; + fx.rgdwAxes = &axis; + fx.rglDirection = &direction; + fx.cbTypeSpecificParams = sizeof(cond); + fx.lpvTypeSpecificParams = &cond; + + /* Check if the damper effect already exists */ + if (idz_di_fx_damper != NULL) { + hr = IDirectInputEffect_SetParameters(idz_di_fx_damper, &fx, DIEP_TYPESPECIFICPARAMS); + if (SUCCEEDED(hr)) { + return; + } + else { + IDirectInputEffect_Stop(idz_di_fx_damper); + IDirectInputEffect_Release(idz_di_fx_damper); + idz_di_fx_damper = NULL; + } + } + + /* Create a new damper effect */ + IDirectInputEffect *obj; + hr = IDirectInputDevice8_CreateEffect( + idz_di_dev, + &GUID_Damper, + &fx, + &obj, + NULL); + + if (FAILED(hr)) { + return; + } + + /* Start the effect */ + hr = IDirectInputEffect_Start(obj, fx.dwDuration, 0); + if (FAILED(hr)) { + IDirectInputEffect_Release(obj); + return; + } + + idz_di_fx_damper = obj; +} diff --git a/games/idzio/di-dev.h b/games/idzio/di-dev.h new file mode 100644 index 0000000..783ec66 --- /dev/null +++ b/games/idzio/di-dev.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +#include + +#include "idzio/config.h" + +union idz_di_state { + DIJOYSTATE st; + uint8_t bytes[sizeof(DIJOYSTATE)]; +}; + +HRESULT idz_di_dev_init( + const struct idz_di_config *cfg, + IDirectInputDevice8W *dev, + HWND wnd); + +HRESULT idz_di_dev_start(IDirectInputDevice8W *dev, HWND wnd); +HRESULT idz_di_dev_poll( + IDirectInputDevice8W *dev, + HWND wnd, + union idz_di_state *out); + +HRESULT idz_di_ffb_init(void); +void idz_di_ffb_toggle(bool active); +void idz_di_ffb_constant_force(uint8_t direction, uint8_t force); +void idz_di_ffb_rumble(uint8_t force, uint8_t period); +void idz_di_ffb_damper(uint8_t force); diff --git a/idzio/di.c b/games/idzio/di.c similarity index 77% rename from idzio/di.c rename to games/idzio/di.c index 3ee27ff..00968fa 100644 --- a/idzio/di.c +++ b/games/idzio/di.c @@ -1,8 +1,10 @@ #include #include +#include #include #include +#include #include #include "idzio/backend.h" @@ -26,6 +28,9 @@ static const struct idz_di_axis *idz_di_get_axis(const wchar_t *name); static BOOL CALLBACK idz_di_enum_callback( const DIDEVICEINSTANCEW *dev, void *ctx); +static BOOL CALLBACK idz_di_enum_callback_pedals( + const DIDEVICEINSTANCEW *dev, + void *ctx); static BOOL CALLBACK idz_di_enum_callback_shifter( const DIDEVICEINSTANCEW *dev, void *ctx); @@ -52,11 +57,17 @@ static const struct idz_io_backend idz_di_backend = { .jvs_read_buttons = idz_di_jvs_read_buttons, .jvs_read_shifter = idz_di_jvs_read_shifter, .jvs_read_analogs = idz_di_jvs_read_analogs, + .ffb_init = idz_di_ffb_init, + .ffb_toggle = idz_di_ffb_toggle, + .ffb_constant_force = idz_di_ffb_constant_force, + .ffb_rumble = idz_di_ffb_rumble, + .ffb_damper = idz_di_ffb_damper }; static HWND idz_di_wnd; static IDirectInput8W *idz_di_api; static IDirectInputDevice8W *idz_di_dev; +static IDirectInputDevice8W *idz_di_pedals; static IDirectInputDevice8W *idz_di_shifter; static IDirectInputEffect *idz_di_fx; static size_t idz_di_off_brake; @@ -66,6 +77,7 @@ static uint8_t idz_di_shift_up; static uint8_t idz_di_view_chg; static uint8_t idz_di_start; static uint8_t idz_di_gear[6]; +static bool idz_di_use_pedals; static bool idz_di_reverse_brake_axis; static bool idz_di_reverse_accel_axis; @@ -160,13 +172,42 @@ HRESULT idz_di_init( return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); } - hr = idz_di_dev_start(idz_di_dev, idz_di_wnd); + hr = idz_di_dev_init(cfg, idz_di_dev, idz_di_wnd); if (FAILED(hr)) { return hr; } - idz_di_dev_start_fx(idz_di_dev, &idz_di_fx); + if (cfg->pedals_name[0] != L'\0') { + hr = IDirectInput8_EnumDevices( + idz_di_api, + DI8DEVCLASS_GAMECTRL, + idz_di_enum_callback_pedals, + (void *) cfg, + DIEDFL_ATTACHEDONLY); + + if (FAILED(hr)) { + dprintf("DirectInput: EnumDevices failed: %08x\n", (int) hr); + + return hr; + } + + if (idz_di_dev == NULL) { + dprintf("Pedals: Controller not found\n"); + + return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + } + + hr = idz_di_dev_start(idz_di_pedals, idz_di_wnd); + + if (FAILED(hr)) { + return hr; + } + + idz_di_use_pedals = true; + } else { + idz_di_use_pedals = false; + } if (cfg->shifter_name[0] != L'\0') { hr = IDirectInput8_EnumDevices( @@ -262,8 +303,10 @@ static HRESULT idz_di_config_apply(const struct idz_di_config *cfg) dprintf("Wheel: --- Begin configuration ---\n"); dprintf("Wheel: Device name . . . . : Contains \"%S\"\n", cfg->device_name); - dprintf("Wheel: Brake axis . . . . : %S\n", accel_axis->name); - dprintf("Wheel: Accelerator axis . : %S\n", brake_axis->name); + if (cfg->pedals_name[0] == L'\0') { + dprintf("Wheel: Brake axis . . . . : %S\n", brake_axis->name); + dprintf("Wheel: Accel axis . . . . : %S\n", accel_axis->name); + } dprintf("Wheel: Start button . . . : %i\n", cfg->start); dprintf("Wheel: View Change button : %i\n", cfg->view_chg); dprintf("Wheel: Shift Down button . : %i\n", cfg->shift_dn); @@ -272,6 +315,15 @@ static HRESULT idz_di_config_apply(const struct idz_di_config *cfg) dprintf("Wheel: Reverse Accel Axis : %i\n", cfg->reverse_accel_axis); dprintf("Wheel: --- End configuration ---\n"); + if (cfg->pedals_name[0] != L'\0') { + dprintf("Pedals: --- Begin configuration ---\n"); + dprintf("Pedals: Device name . . . : Contains \"%S\"\n", + cfg->pedals_name); + dprintf("Pedals: Brake axis . . . . : %S\n", brake_axis->name); + dprintf("Pedals: Accel axis . . . . : %S\n", accel_axis->name); + dprintf("Pedals: --- End configuration ---\n"); + } + if (cfg->shifter_name[0] != L'\0') { dprintf("Shifter: --- Begin configuration ---\n"); dprintf("Shifter: Device name . . . : Contains \"%S\"\n", @@ -286,8 +338,8 @@ static HRESULT idz_di_config_apply(const struct idz_di_config *cfg) dprintf("Shifter: --- End configuration ---\n"); } - idz_di_off_brake = accel_axis->off; - idz_di_off_accel = brake_axis->off; + idz_di_off_brake = brake_axis->off; + idz_di_off_accel = accel_axis->off; idz_di_start = cfg->start; idz_di_view_chg = cfg->view_chg; idz_di_shift_dn = cfg->shift_dn; @@ -299,6 +351,25 @@ static HRESULT idz_di_config_apply(const struct idz_di_config *cfg) idz_di_gear[i] = cfg->gear[i]; } + /* FFB configuration */ + if (cfg->ffb_constant_force_strength < 0 || cfg->ffb_constant_force_strength > 100) { + dprintf("Wheel: Invalid constant force strength: %i\n", cfg->ffb_constant_force_strength); + + return E_INVALIDARG; + } + + if (cfg->ffb_rumble_strength < 0 || cfg->ffb_rumble_strength > 100) { + dprintf("Wheel: Invalid rumble strength: %i\n", cfg->ffb_rumble_strength); + + return E_INVALIDARG; + } + + if (cfg->ffb_damper_strength < 0 || cfg->ffb_damper_strength > 100) { + dprintf("Wheel: Invalid damper strength: %i\n", cfg->ffb_damper_strength); + + return E_INVALIDARG; + } + return S_OK; } @@ -346,6 +417,34 @@ static BOOL CALLBACK idz_di_enum_callback( return DIENUM_STOP; } +static BOOL CALLBACK idz_di_enum_callback_pedals( + const DIDEVICEINSTANCEW *dev, + void *ctx) +{ + const struct idz_di_config *cfg; + HRESULT hr; + + cfg = ctx; + + if (wcsstr(dev->tszProductName, cfg->pedals_name) == NULL) { + return DIENUM_CONTINUE; + } + + dprintf("Pedals: Using DirectInput device \"%S\"\n", dev->tszProductName); + + hr = IDirectInput8_CreateDevice( + idz_di_api, + &dev->guidInstance, + &idz_di_pedals, + NULL); + + if (FAILED(hr)) { + dprintf("Pedals: CreateDevice failed: %08x\n", (int) hr); + } + + return DIENUM_STOP; +} + static BOOL CALLBACK idz_di_enum_callback_shifter( const DIDEVICEINSTANCEW *dev, void *ctx) @@ -409,7 +508,7 @@ static uint8_t idz_di_decode_pov(DWORD pov) case 9000: return IDZ_IO_GAMEBTN_RIGHT; case 13500: return IDZ_IO_GAMEBTN_RIGHT | IDZ_IO_GAMEBTN_DOWN; case 18000: return IDZ_IO_GAMEBTN_DOWN; - case 22500: return IDZ_IO_GAMEBTN_DOWN | IDZ_IO_GAMEBTN_RIGHT; + case 22500: return IDZ_IO_GAMEBTN_DOWN | IDZ_IO_GAMEBTN_LEFT; case 27000: return IDZ_IO_GAMEBTN_LEFT; case 31500: return IDZ_IO_GAMEBTN_LEFT | IDZ_IO_GAMEBTN_UP; default: return 0; @@ -492,6 +591,7 @@ static void idz_di_jvs_read_shifter_virt(uint8_t *gear) static void idz_di_jvs_read_analogs(struct idz_io_analog_state *out) { union idz_di_state state; + union idz_di_state pedals_state; const LONG *brake; const LONG *accel; HRESULT hr; @@ -504,8 +604,19 @@ static void idz_di_jvs_read_analogs(struct idz_io_analog_state *out) return; } - brake = (LONG *) &state.bytes[idz_di_off_brake]; - accel = (LONG *) &state.bytes[idz_di_off_accel]; + if (idz_di_use_pedals) { + hr = idz_di_dev_poll(idz_di_pedals, idz_di_wnd, &pedals_state); + + if (FAILED(hr)) { + return; + } + + brake = (LONG *) &pedals_state.bytes[idz_di_off_brake]; + accel = (LONG *) &pedals_state.bytes[idz_di_off_accel]; + } else { + brake = (LONG *) &state.bytes[idz_di_off_brake]; + accel = (LONG *) &state.bytes[idz_di_off_accel]; + } out->wheel = state.st.lX - 32768; diff --git a/idzio/di.h b/games/idzio/di.h similarity index 100% rename from idzio/di.h rename to games/idzio/di.h diff --git a/idzio/dllmain.c b/games/idzio/dllmain.c similarity index 62% rename from idzio/dllmain.c rename to games/idzio/dllmain.c index 278c8c2..322d194 100644 --- a/idzio/dllmain.c +++ b/games/idzio/dllmain.c @@ -12,6 +12,7 @@ #include "util/dprintf.h" #include "util/str.h" +#include "util/env.h" static struct idz_io_config idz_io_cfg; static const struct idz_io_backend *idz_io_backend; @@ -20,7 +21,7 @@ static uint16_t idz_io_coins; uint16_t idz_io_get_api_version(void) { - return 0x0100; + return 0x0102; } HRESULT idz_io_jvs_init(void) @@ -39,7 +40,7 @@ HRESULT idz_io_jvs_init(void) return hr; } - idz_io_config_load(&idz_io_cfg, L".\\segatools.ini"); + idz_io_config_load(&idz_io_cfg, get_config_path()); if (wstr_ieq(idz_io_cfg.mode, L"dinput")) { hr = idz_di_init(&idz_io_cfg.di, inst, &idz_io_backend); @@ -111,7 +112,7 @@ void idz_io_jvs_read_coin_counter(uint16_t *out) /* Coin counter is not backend-specific */ - if ( idz_io_cfg.vk_coin && + if (idz_io_cfg.vk_coin && (GetAsyncKeyState(idz_io_cfg.vk_coin) & 0x8000)) { if (!idz_io_coin) { idz_io_coin = true; @@ -123,3 +124,79 @@ void idz_io_jvs_read_coin_counter(uint16_t *out) *out = idz_io_coins; } + +HRESULT idz_io_led_init(void) +{ + return S_OK; +} + +void idz_io_led_set_fet_output(uint8_t board, const uint8_t *rgb) +{ +#if 0 + dprintf("IDZ LED: LEFT SEAT LED: %02X\n", rgb[0]); + dprintf("IDZ LED: RIGHT SEAT LED: %02X\n", rgb[1]); +#endif + + return; +} + +void idz_io_led_gs_update(uint8_t board, const uint8_t *rgb) +{ +#if 0 + for (int i = 0; i < 9; i++) { + dprintf("IDZ LED: LED %d: %02X %02X %02X Speed: %02X\n", + i, rgb[i * 4], rgb[i * 4 + 1], rgb[i * 4 + 2], rgb[i * 4 + 3]); + } +#endif + + return; +} + +void idz_io_led_set_leds(uint8_t board, const uint8_t *rgb) +{ +#if 0 + dprintf("IDZ LED: START: %02X\n", rgb[0]); + dprintf("IDZ LED: VIEW CHANGE: %02X\n", rgb[1]); + dprintf("IDZ LED: UP: %02X\n", rgb[2]); + dprintf("IDZ LED: DOWN: %02X\n", rgb[3]); + dprintf("IDZ LED: RIGHT: %02X\n", rgb[4]); + dprintf("IDZ LED: LEFT: %02X\n", rgb[5]); +#endif + + return; +} + +HRESULT idz_io_ffb_init(void) +{ + assert(idz_io_backend != NULL); + + return idz_io_backend->ffb_init(); +} + +void idz_io_ffb_toggle(bool active) +{ + assert(idz_io_backend != NULL); + + idz_io_backend->ffb_toggle(active); +} + +void idz_io_ffb_constant_force(uint8_t direction, uint8_t force) +{ + assert(idz_io_backend != NULL); + + idz_io_backend->ffb_constant_force(direction, force); +} + +void idz_io_ffb_rumble(uint8_t period, uint8_t force) +{ + assert(idz_io_backend != NULL); + + idz_io_backend->ffb_rumble(period, force); +} + +void idz_io_ffb_damper(uint8_t force) +{ + assert(idz_io_backend != NULL); + + idz_io_backend->ffb_damper(force); +} diff --git a/games/idzio/idzio.def b/games/idzio/idzio.def new file mode 100644 index 0000000..4d66148 --- /dev/null +++ b/games/idzio/idzio.def @@ -0,0 +1,17 @@ +LIBRARY idzio + +EXPORTS + idz_io_jvs_init + idz_io_jvs_read_analogs + idz_io_jvs_read_buttons + idz_io_jvs_read_coin_counter + idz_io_jvs_read_shifter + idz_io_led_init + idz_io_led_set_fet_output + idz_io_led_gs_update + idz_io_led_set_leds + idz_io_ffb_init + idz_io_ffb_toggle + idz_io_ffb_constant_force + idz_io_ffb_rumble + idz_io_ffb_damper diff --git a/idzio/idzio.h b/games/idzio/idzio.h similarity index 51% rename from idzio/idzio.h rename to games/idzio/idzio.h index 395b012..db64c44 100644 --- a/idzio/idzio.h +++ b/games/idzio/idzio.h @@ -14,6 +14,7 @@ #include +#include #include enum { @@ -30,6 +31,17 @@ enum { IDZ_IO_GAMEBTN_VIEW_CHANGE = 0x20, }; +enum { + /* These are the bitmasks to use when checking which + lights are triggered on incoming IO4 GPIO writes. */ + IDZ_IO_LED_START = 1 << 7, + IDZ_IO_LED_VIEW_CHANGE = 1 << 6, + IDZ_IO_LED_UP = 1 << 1, + IDZ_IO_LED_DOWN = 1 << 0, + IDZ_IO_LED_RIGHT = 1 << 14, + IDZ_IO_LED_LEFT = 1 << 15 +}; + struct idz_io_analog_state { /* Current steering wheel position, where zero is the centered position. @@ -104,3 +116,107 @@ void idz_io_jvs_read_shifter(uint8_t *gear); Minimum API version: 0x0100 */ void idz_io_jvs_read_coin_counter(uint16_t *total); + +/* Initialize LED emulation. This function will be called before any + other idz_io_led_*() function calls. + + 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: 0x0101 */ + +HRESULT idz_io_led_init(void); + +/* Update the FET outputs. rgb is a pointer to an array up to 3 bytes. + + The following bits are used to control the FET outputs: + [0]: LEFT SEAT LED + [1]: RIGHT SEAT LED + + The LED is truned on when the byte is 255 and turned off when the byte is 0. + + Minimum API version: 0x0101 */ + +void idz_io_led_set_fet_output(uint8_t board, const uint8_t *rgb); + +/* Update the RGB LEDs. rgb is a pointer to an array up to 32 * 4 = 128 bytes. + + The LEDs are laid out as follows: + [0]: LEFT UP LED + [1-2]: LEFT CENTER LED + [3]: LEFT DOWN LED + [5]: RIGHT UP LED + [6-7]: RIGHT CENTER LED + [8]: RIGHT DOWN LED + + Each rgb value is comprised for 4 bytes in the order of R, G, B, Speed. + Speed is a value from 0 to 255, where 0 is the fastest speed and 255 is the slowest. + + Minimum API version: 0x0101 */ + +void idz_io_led_gs_update(uint8_t board, const uint8_t *rgb); + +/* Update the cabinet button LEDs. rgb is a pointer to an array up to 6 bytes. + + The LEDs are laid out as follows: + [0]: START LED + [1]: VIEW CHANGE LED + [2]: UP LED + [3]: DOWN LED + [4]: RIGHT LED + [5]: LEFT LED + + The LED is turned on when the byte is 255 and turned off when the byte is 0. + + Minimum API version: 0x0101 */ + +void idz_io_led_set_leds(uint8_t board, const uint8_t *rgb); + +/* Initialize FFB emulation. This function will be called before any + other idz_io_ffb_*() function calls. + + This will always be called even if FFB board emulation is disabled to allow + the IO DLL to initialize any necessary resources. + + Minimum API version: 0x0102 */ + +HRESULT idz_io_ffb_init(void); + +/* Toggle FFB emulation. If active is true, FFB emulation should be enabled. + If active is false, FFB emulation should be disabled and all FFB effects + should be stopped and released. + + Minimum API version: 0x0102 */ + +void idz_io_ffb_toggle(bool active); + +/* Set a constant force FFB effect. + + Direction is 0 for right and 1 for left. + Force is the magnitude of the force, where 0 is no force and 127 is the + maximum force in a given direction. + + Minimum API version: 0x0102 */ + +void idz_io_ffb_constant_force(uint8_t direction, uint8_t force); + +/* Set a (sine) periodic force FFB effect. + + Period is the period of the effect in milliseconds (not sure). + Force is the magnitude of the force, where 0 is no force and 127 is the + maximum force. + + Minimum API version: 0x0102 */ + +void idz_io_ffb_rumble(uint8_t period, uint8_t force); + +/* Set a damper FFB effect. + + Force is the magnitude of the force, where 0 is no force and 40 is the + maximum force. Theoretically the maximum force is 127, but the game only + uses a maximum of 40. + + Minimum API version: 0x0102 */ + +void idz_io_ffb_damper(uint8_t force); diff --git a/idzio/meson.build b/games/idzio/meson.build similarity index 94% rename from idzio/meson.build rename to games/idzio/meson.build index bfab168..3a777bb 100644 --- a/idzio/meson.build +++ b/games/idzio/meson.build @@ -3,7 +3,6 @@ idzio_lib = static_library( name_prefix : '', include_directories : inc, implicit_include_directories : false, - c_pch : '../precompiled.h', dependencies : [ dinput8_lib, dxguid_lib, diff --git a/idzio/shifter.c b/games/idzio/shifter.c similarity index 100% rename from idzio/shifter.c rename to games/idzio/shifter.c diff --git a/idzio/shifter.h b/games/idzio/shifter.h similarity index 100% rename from idzio/shifter.h rename to games/idzio/shifter.h diff --git a/idzio/wnd.c b/games/idzio/wnd.c similarity index 100% rename from idzio/wnd.c rename to games/idzio/wnd.c diff --git a/idzio/wnd.h b/games/idzio/wnd.h similarity index 100% rename from idzio/wnd.h rename to games/idzio/wnd.h diff --git a/games/idzio/xi.c b/games/idzio/xi.c new file mode 100644 index 0000000..da566f0 --- /dev/null +++ b/games/idzio/xi.c @@ -0,0 +1,255 @@ +#include +#include + +#include +#include +#include +#include + +#include "idzio/backend.h" +#include "idzio/config.h" +#include "idzio/idzio.h" +#include "idzio/shifter.h" +#include "idzio/xi.h" + +#include "util/dprintf.h" + +static void idz_xi_jvs_read_buttons(uint8_t *gamebtn_out); +static void idz_xi_jvs_read_shifter(uint8_t *gear); +static void idz_xi_jvs_read_analogs(struct idz_io_analog_state *out); + +static HRESULT idz_xi_ffb_init(void); +static void idz_xi_ffb_toggle(bool active); +static void idz_xi_ffb_constant_force(uint8_t direction, uint8_t force); +static void idz_xi_ffb_rumble(uint8_t force, uint8_t period); +static void idz_xi_ffb_damper(uint8_t force); + +static HRESULT idz_xi_config_apply(const struct idz_xi_config *cfg); + +static const struct idz_io_backend idz_xi_backend = { + .jvs_read_buttons = idz_xi_jvs_read_buttons, + .jvs_read_shifter = idz_xi_jvs_read_shifter, + .jvs_read_analogs = idz_xi_jvs_read_analogs, + .ffb_init = idz_xi_ffb_init, + .ffb_toggle = idz_xi_ffb_toggle, + .ffb_constant_force = idz_xi_ffb_constant_force, + .ffb_rumble = idz_xi_ffb_rumble, + .ffb_damper = idz_xi_ffb_damper +}; + +static bool idz_xi_single_stick_steering; +static bool idz_xi_linear_steering; +static uint16_t idz_xi_left_stick_deadzone; +static uint16_t idz_xi_right_stick_deadzone; + +const uint16_t max_stick_value = 32767; +/* Apply steering wheel restriction. Real cabs only report about 76% of + the output value when the wheel is turned to either of its maximum positions. */ +const uint16_t max_wheel_value = 24831; + +HRESULT idz_xi_init(const struct idz_xi_config *cfg, const struct idz_io_backend **backend) +{ + HRESULT hr; + assert(cfg != NULL); + assert(backend != NULL); + + hr = idz_xi_config_apply(cfg); + + if (FAILED(hr)) { + return hr; + } + + dprintf("XInput: Using XInput controller\n"); + *backend = &idz_xi_backend; + + return S_OK; +} + +static HRESULT idz_xi_config_apply(const struct idz_xi_config *cfg) { + /* Deadzones check */ + if (cfg->left_stick_deadzone > 32767 || cfg->left_stick_deadzone < 0) { + dprintf("XInput: Left stick deadzone is too large or negative\n"); + return E_INVALIDARG; + } + + if (cfg->right_stick_deadzone > 32767 || cfg->right_stick_deadzone < 0) { + dprintf("XInput: Right stick deadzone is too large or negative\n"); + return E_INVALIDARG; + } + + dprintf("XInput: --- Begin configuration ---\n"); + dprintf("XInput: Single Stick Steering : %i\n", cfg->single_stick_steering); + dprintf("XInput: Linear Steering . . . : %i\n", cfg->linear_steering); + dprintf("XInput: Left Deadzone . . . . : %i\n", cfg->left_stick_deadzone); + dprintf("XInput: Right Deadzone . . . : %i\n", cfg->right_stick_deadzone); + dprintf("XInput: --- End configuration ---\n"); + + idz_xi_single_stick_steering = cfg->single_stick_steering; + idz_xi_linear_steering = cfg->linear_steering; + idz_xi_left_stick_deadzone = cfg->left_stick_deadzone; + idz_xi_right_stick_deadzone = cfg->right_stick_deadzone; + + return S_OK; +} + +static void idz_xi_jvs_read_buttons(uint8_t *gamebtn_out) +{ + uint8_t gamebtn; + XINPUT_STATE xi; + WORD xb; + + assert(gamebtn_out != NULL); + + gamebtn = 0; + + memset(&xi, 0, sizeof(xi)); + XInputGetState(0, &xi); + xb = xi.Gamepad.wButtons; + + if (xb & XINPUT_GAMEPAD_DPAD_UP) { + gamebtn |= IDZ_IO_GAMEBTN_UP; + } + + if (xb & XINPUT_GAMEPAD_DPAD_DOWN) { + gamebtn |= IDZ_IO_GAMEBTN_DOWN; + } + + if (xb & XINPUT_GAMEPAD_DPAD_LEFT) { + gamebtn |= IDZ_IO_GAMEBTN_LEFT; + } + + if (xb & XINPUT_GAMEPAD_DPAD_RIGHT) { + gamebtn |= IDZ_IO_GAMEBTN_RIGHT; + } + + if (xb & (XINPUT_GAMEPAD_START | XINPUT_GAMEPAD_A)) { + gamebtn |= IDZ_IO_GAMEBTN_START; + } + + if (xb & (XINPUT_GAMEPAD_BACK | XINPUT_GAMEPAD_B)) { + gamebtn |= IDZ_IO_GAMEBTN_VIEW_CHANGE; + } + + *gamebtn_out = gamebtn; +} + +static void idz_xi_jvs_read_shifter(uint8_t *gear) +{ + bool shift_dn; + bool shift_up; + XINPUT_STATE xi; + WORD xb; + + assert(gear != NULL); + + memset(&xi, 0, sizeof(xi)); + XInputGetState(0, &xi); + xb = xi.Gamepad.wButtons; + + if (xb & XINPUT_GAMEPAD_START) { + /* Reset to Neutral when start is pressed */ + idz_shifter_reset(); + } + + shift_dn = xb & (XINPUT_GAMEPAD_Y | XINPUT_GAMEPAD_LEFT_SHOULDER); + shift_up = xb & (XINPUT_GAMEPAD_X | XINPUT_GAMEPAD_RIGHT_SHOULDER); + + idz_shifter_update(shift_dn, shift_up); + + *gear = idz_shifter_current_gear(); +} + +static int16_t calculate_norm_steering(int16_t axis, uint16_t deadzone, bool linear_steering) { + // determine how far the controller is pushed + float magnitude = sqrt(axis*axis); + + // determine the direction the controller is pushed + float norm_axis = axis / magnitude; + + float norm_magnitude = 0.0; + + // check if the controller is outside a circular dead zone + if (magnitude > deadzone) + { + // clip the magnitude at its expected maximum value + if (magnitude > max_stick_value) magnitude = max_stick_value; + + // adjust magnitude relative to the end of the dead zone + magnitude -= deadzone; + + // optionally normalize the magnitude with respect to its expected range + // giving a magnitude value of 0.0 to 1.0 + norm_magnitude = magnitude / (max_stick_value - deadzone); + } else // if the controller is in the deadzone zero out the magnitude + { + magnitude = 0.0; + norm_magnitude = 0.0; + } + + // apply non-linear transform to the axis + if (!linear_steering) { + return (int16_t)(norm_axis * powf(norm_magnitude, 3.0f) * max_wheel_value); + } + + return (int16_t)(norm_axis * norm_magnitude * max_wheel_value); +} + +static void idz_xi_jvs_read_analogs(struct idz_io_analog_state *out) +{ + XINPUT_STATE xi; + int left; + int right; + + assert(out != NULL); + + memset(&xi, 0, sizeof(xi)); + XInputGetState(0, &xi); + + left = xi.Gamepad.sThumbLX; + right = xi.Gamepad.sThumbRX; + + // normalize the steering axis + left = calculate_norm_steering(left, idz_xi_left_stick_deadzone, idz_xi_linear_steering); + right = calculate_norm_steering(right, idz_xi_right_stick_deadzone, idz_xi_linear_steering); + + if (idz_xi_single_stick_steering) { + out->wheel = left; + } else { + out->wheel = (left + right) / 2; + } + + out->accel = xi.Gamepad.bRightTrigger << 8; + out->brake = xi.Gamepad.bLeftTrigger << 8; +} + +static HRESULT idz_xi_ffb_init(void) { + return S_OK; +} + +static void idz_xi_ffb_toggle(bool active) { + XINPUT_VIBRATION vibration; + + memset(&vibration, 0, sizeof(vibration)); + + XInputSetState(0, &vibration); +} + +static void idz_xi_ffb_constant_force(uint8_t direction, uint8_t force) { + return; +} + +static void idz_xi_ffb_rumble(uint8_t force, uint8_t period) { + XINPUT_VIBRATION vibration; + /* XInput max strength is 65.535, so multiply the 127.0 by 516. */ + uint16_t strength = force * 516; + + memset(&vibration, 0, sizeof(vibration)); + vibration.wLeftMotorSpeed = strength; + vibration.wRightMotorSpeed = strength; + + XInputSetState(0, &vibration); +} + +static void idz_xi_ffb_damper(uint8_t force) { + return; +} diff --git a/idzio/xi.h b/games/idzio/xi.h similarity index 100% rename from idzio/xi.h rename to games/idzio/xi.h diff --git a/games/kemonohook/config.c b/games/kemonohook/config.c new file mode 100644 index 0000000..c008d88 --- /dev/null +++ b/games/kemonohook/config.c @@ -0,0 +1,132 @@ +#include +#include +#include + +#include "amex/config.h" + +#include "board/config.h" + +#include "hooklib/config.h" +#include "hooklib/dvd.h" + +#include "kemonohook/config.h" + +#include "platform/config.h" + +// Check windows +#if _WIN32 || _WIN64 + #if _WIN64 + #define ENV64BIT + #else + #define ENV32BIT + #endif +#endif + +// Check GCC +#if __GNUC__ + #if __x86_64__ || __ppc64__ + #define ENV64BIT + #else + #define ENV32BIT + #endif +#endif + +void kemono_dll_config_load( + struct kemono_dll_config *cfg, + const wchar_t *filename) { + assert(cfg != NULL); + assert(filename != NULL); + + #if defined(ENV32BIT) + // Always empty, due to amdaemon being 64 bit in 32 bit mode + memset(cfg->path, 0, sizeof(cfg->path)); + #elif defined(ENV64BIT) + GetPrivateProfileStringW( + L"kemonoio", + L"path", + L"", + cfg->path, + _countof(cfg->path), + filename); + #else + #error "Unknown environment" + #endif +} + +void led15093_config_load(struct led15093_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + wchar_t tmpstr[16]; + + memset(cfg->board_number, ' ', sizeof(cfg->board_number)); + memset(cfg->chip_number, ' ', sizeof(cfg->chip_number)); + memset(cfg->boot_chip_number, ' ', sizeof(cfg->boot_chip_number)); + + cfg->enable = GetPrivateProfileIntW(L"led15093", L"enable", 1, filename); + cfg->port_no[0] = GetPrivateProfileIntW(L"led15093", L"portNo1", 0, filename); + cfg->port_no[1] = GetPrivateProfileIntW(L"led15093", L"portNo2", 1, filename); + cfg->high_baudrate = GetPrivateProfileIntW(L"led15093", L"highBaud", 0, filename); + cfg->fw_ver = GetPrivateProfileIntW(L"led15093", L"fwVer", 0xA0, filename); + cfg->fw_sum = GetPrivateProfileIntW(L"led15093", L"fwSum", 0xAA53, filename); + + GetPrivateProfileStringW( + L"led15093", + L"boardNumber", + L"15093-04", + tmpstr, + _countof(tmpstr), + filename); + + size_t n = wcstombs(cfg->board_number, tmpstr, sizeof(cfg->board_number)); + for (int i = n; i < sizeof(cfg->board_number); i++) + { + cfg->board_number[i] = ' '; + } + + GetPrivateProfileStringW( + L"led15093", + L"chipNumber", + L"6704 ", + tmpstr, + _countof(tmpstr), + filename); + + n = wcstombs(cfg->chip_number, tmpstr, sizeof(cfg->chip_number)); + for (int i = n; i < sizeof(cfg->chip_number); i++) + { + cfg->chip_number[i] = ' '; + } + + GetPrivateProfileStringW( + L"led15093", + L"bootChipNumber", + L"6704 ", + tmpstr, + _countof(tmpstr), + filename); + + n = wcstombs(cfg->boot_chip_number, tmpstr, sizeof(cfg->boot_chip_number)); + for (int i = n; i < sizeof(cfg->boot_chip_number); i++) + { + cfg->boot_chip_number[i] = ' '; + } +} + +void kemono_hook_config_load( + struct kemono_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); + vfd_config_load(&cfg->vfd, filename); + kemono_dll_config_load(&cfg->dll, filename); + unity_config_load(&cfg->unity, filename); + printer_config_load(&cfg->printer, filename); + amex_config_load(&cfg->amex, filename); + led15093_config_load(&cfg->led15093, filename); +} diff --git a/games/kemonohook/config.h b/games/kemonohook/config.h new file mode 100644 index 0000000..bf76cff --- /dev/null +++ b/games/kemonohook/config.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +#include "amex/amex.h" + +#include "board/config.h" +#include "board/led15093.h" + +#include "hooklib/dvd.h" + +#include "kemonohook/kemono-dll.h" + +#include "platform/config.h" + +#include "unityhook/config.h" + +struct kemono_hook_config { + struct platform_config platform; + struct aime_config aime; + struct dvd_config dvd; + struct vfd_config vfd; + struct kemono_dll_config dll; + struct unity_config unity; + struct printer_config printer; + struct amex_config amex; + struct led15093_config led15093; +}; + +void kemono_dll_config_load( + struct kemono_dll_config *cfg, + const wchar_t *filename); + +void kemono_hook_config_load( + struct kemono_hook_config *cfg, + const wchar_t *filename); diff --git a/games/kemonohook/dllmain.c b/games/kemonohook/dllmain.c new file mode 100644 index 0000000..3373489 --- /dev/null +++ b/games/kemonohook/dllmain.c @@ -0,0 +1,146 @@ +#include +#include + +#include "board/io4.h" +#include "board/sg-reader.h" +#include "board/vfd.h" +#include "hook/iohook.h" +#include "hook/process.h" +#include "hook/table.h" +#include "hooklib/printer.h" +#include "hooklib/serial.h" +#include "hooklib/spike.h" +#include "kemonohook/config.h" +#include "kemonohook/hooks.h" +#include "kemonohook/jvs.h" +#include "kemonohook/kemono-dll.h" +#include "platform/platform.h" +#include "unityhook/hook.h" +#include "util/dprintf.h" +#include "util/env.h" + +static HMODULE kemono_hook_mod; +static process_entry_t kemono_startup; +static struct kemono_hook_config kemono_hook_cfg; + +static DWORD CALLBACK kemono_pre_startup(void) { + HRESULT hr; + + dprintf("--- Begin kemono_pre_startup ---\n"); + + /* Load config */ + + kemono_hook_cfg.aime.dll.path64 = true; + kemono_hook_config_load(&kemono_hook_cfg, get_config_path()); + + /* Hook Win32 APIs */ + + dvd_hook_init(&kemono_hook_cfg.dvd, kemono_hook_mod); + serial_hook_init(); + + // 2.02 does not call printer update functions + uint16_t ret; + fwdlusb_updateFirmware_main( + 1, + "UnityApp\\Parade_Data\\StreamingAssets\\Printer\\E0223100-014E-C300-" + "MAINAPP.BIN", + &ret); + if (ret != 0) { + goto fail; + } + fwdlusb_updateFirmware_dsp( + 2, + "UnityApp\\Parade_Data\\StreamingAssets\\Printer\\E0223200-0101-C300-" + "DSPAPP.BIN", + &ret); + if (ret != 0) { + goto fail; + } + fwdlusb_updateFirmware_param( + 3, + "UnityApp\\Parade_Data\\StreamingAssets\\Printer\\D0460700-0101-C300-" + "PARAM.BIN", + &ret); + if (ret != 0) { + goto fail; + } + + printer_hook_init(&kemono_hook_cfg.printer, 0, kemono_hook_mod); + printer_set_dimensions(720, 1028); // printer doesn't call setimageformat + + /* Initialize emulation hooks */ + + hr = platform_hook_init(&kemono_hook_cfg.platform, "SDFL", "AAW1", + kemono_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = sg_reader_hook_init(&kemono_hook_cfg.aime, 1, 1, kemono_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = kemono_dll_init(&kemono_hook_cfg.dll, kemono_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = amex_hook_init(&kemono_hook_cfg.amex, kemono_jvs_init); + + if (FAILED(hr)) { + goto fail; + } + + unsigned int led_port_no[2] = {10, 0}; + hr = led15093_hook_init(&kemono_hook_cfg.led15093, kemono_dll.led_init, + kemono_dll.led_set_leds, led_port_no); + + if (FAILED(hr)) { + goto fail; + } + + kemono_extra_hooks_init(); + + /* Initialize Unity native plugin DLL hooks + + There seems to be an issue with other DLL hooks if `LoadLibraryW` is + hooked earlier in the `kemonohook` initialization. */ + + unity_hook_init(&kemono_hook_cfg.unity, kemono_hook_mod, + kemono_extra_hooks_load); + + /* Initialize debug helpers */ + + spike_hook_init(get_config_path()); + + dprintf("--- End kemono_pre_startup ---\n"); + + /* Jump to EXE start address */ + + return kemono_startup(); + +fail: + ExitProcess(EXIT_FAILURE); +} + +BOOL WINAPI DllMain(HMODULE mod, DWORD cause, void *ctx) { + HRESULT hr; + + if (cause != DLL_PROCESS_ATTACH) { + return TRUE; + } + + kemono_hook_mod = mod; + + hr = process_hijack_startup(kemono_pre_startup, &kemono_startup); + + if (!SUCCEEDED(hr)) { + dprintf("Failed to hijack process startup: %x\n", (int)hr); + } + + return SUCCEEDED(hr); +} diff --git a/games/kemonohook/hooks.c b/games/kemonohook/hooks.c new file mode 100644 index 0000000..c34302f --- /dev/null +++ b/games/kemonohook/hooks.c @@ -0,0 +1,67 @@ +#include + +#include "hook/iohook.h" +#include "hook/procaddr.h" +#include "hook/table.h" + +#include "hooklib/serial.h" + +#include "kemonohook/hooks.h" + +#include "util/dprintf.h" + +static BOOL WINAPI hook_GetVersionExW(LPOSVERSIONINFOW lpVersionInformation); + +static int (WINAPI *next_GetVersionExW)(LPOSVERSIONINFOW lpVersionInformation); + +static const struct hook_symbol kemono_kernel32_syms[] = { + { + .name = "GetVersionExW", + .patch = hook_GetVersionExW, + .link = (void **) &next_GetVersionExW, + } +}; + +void kemono_extra_hooks_init(){ + HMODULE serialportapi = LoadLibraryA("Parade_Data/Plugins/SerialPortAPI.dll"); // HACK?? + if (serialportapi != NULL){ + iohook_apply_hooks(serialportapi); + serial_hook_apply_hooks(serialportapi); + dprintf("Kemono: Successfully pre-loaded SerialPortAPI\n"); + } +} + +void kemono_extra_hooks_load(HMODULE mod, const wchar_t* target_module) { + + // Workaround for AmManager.checkTarget:Environment.GetEnvironmentVariable("USERNAME") + SetEnvironmentVariableA("USERNAME", "AppUser"); + + // Workaround for AmManager.checkTarget, expects OS version to be 6.2 or 6.3 + hook_table_apply( + mod, + "kernel32.dll", + kemono_kernel32_syms, + _countof(kemono_kernel32_syms)); + + // needed for LED COM port + // FIXME: SerialPortAPI.dll seems to be loaded twice? this causes a crash + /*if (_wcsicmp(L"SerialPortAPI.dll", target_module) == 0) { + iohook_apply_hooks(mod); + serial_hook_apply_hooks(mod); + dprintf("Kemono: Loaded I/O hooks for serial port\n"); + }*/ + +} + +static BOOL WINAPI hook_GetVersionExW(LPOSVERSIONINFOW lpVersionInformation) { + int result = next_GetVersionExW(lpVersionInformation); + + if (result) { + lpVersionInformation->dwMajorVersion = 6; + lpVersionInformation->dwMinorVersion = 2; + lpVersionInformation->dwBuildNumber = 0; + dprintf("Kemono: GetVersionExW hook hit\n"); + } + + return result; +} \ No newline at end of file diff --git a/games/kemonohook/hooks.h b/games/kemonohook/hooks.h new file mode 100644 index 0000000..a778e5b --- /dev/null +++ b/games/kemonohook/hooks.h @@ -0,0 +1,5 @@ +#pragma once + +void kemono_extra_hooks_init(); + +void kemono_extra_hooks_load(HMODULE mod, const wchar_t* target_module); \ No newline at end of file diff --git a/games/kemonohook/jvs.c b/games/kemonohook/jvs.c new file mode 100644 index 0000000..0b10d3c --- /dev/null +++ b/games/kemonohook/jvs.c @@ -0,0 +1,139 @@ +#include + +#include +#include +#include +#include + +#include "amex/jvs.h" + +#include "board/io3.h" + +#include "kemonohook/kemono-dll.h" + +#include "jvs/jvs-bus.h" + +#include "util/dprintf.h" + +struct kemono_jvs_ir_mask { + uint16_t p1; + uint16_t p2; +}; + +static void kemono_jvs_read_switches(void *ctx, struct io3_switch_state *out); +static void kemono_jvs_read_coin_counter( + void *ctx, + uint8_t slot_no, + uint16_t *out); +static void kemono_jvs_write_gpio(void *ctx, uint32_t state); + +static const struct io3_ops kemono_jvs_io3_ops = { + .read_switches = kemono_jvs_read_switches, + .read_coin_counter = kemono_jvs_read_coin_counter, + .write_gpio = kemono_jvs_write_gpio +}; + +static struct io3 kemono_jvs_io3; + +HRESULT kemono_jvs_init(struct jvs_node **out) +{ + HRESULT hr; + + assert(out != NULL); + assert(kemono_dll.init != NULL); + + dprintf("JVS I/O: Starting IO backend\n"); + hr = kemono_dll.init(); + + if (FAILED(hr)) { + dprintf("JVS I/O: Backend error, I/O disconnected: %x\n", (int) hr); + + return hr; + } + + io3_init(&kemono_jvs_io3, NULL, &kemono_jvs_io3_ops, NULL); + *out = io3_to_jvs_node(&kemono_jvs_io3); + + return S_OK; +} + +static void kemono_jvs_read_switches(void *ctx, struct io3_switch_state *out) +{ + const struct kemono_jvs_ir_mask *masks; + uint16_t opbtn; + uint16_t pbtn; + size_t i; + + assert(out != NULL); + assert(kemono_dll.poll != NULL); + + opbtn = 0; + pbtn = 0; + + kemono_dll.poll(&opbtn, &pbtn); + + out->system = 0x00; + out->p1 = 0x0000; + out->p2 = 0x0000; + + if (opbtn & KEMONO_IO_OPBTN_TEST) { + out->system |= 1 << 7; + } + + if (opbtn & KEMONO_IO_OPBTN_SERVICE) { + out->p1 |= 1 << 14; + } + + if (pbtn & KEMONO_IO_GAMEBTN_UP) { + out->p1 |= 1 << 13; + } + + if (pbtn & KEMONO_IO_GAMEBTN_DOWN) { + out->p1 |= 1 << 12; + } + + if (pbtn & KEMONO_IO_GAMEBTN_LEFT) { + out->p1 |= 1 << 11; + } + + if (pbtn & KEMONO_IO_GAMEBTN_RIGHT) { + out->p1 |= 1 << 10; + } + + if (pbtn & KEMONO_IO_GAMEBTN_R) { + out->p1 |= 1 << 9; + } + + if (pbtn & KEMONO_IO_GAMEBTN_G) { + out->p1 |= 1 << 7; + } + + if (pbtn & KEMONO_IO_GAMEBTN_B) { + out->p1 |= 1 << 8; + } + + if (pbtn & KEMONO_IO_GAMEBTN_START) { + out->p1 |= 1 << 15; + } + + +} + +static void kemono_jvs_read_coin_counter( + void *ctx, + uint8_t slot_no, + uint16_t *out) +{ + assert(out != NULL); + assert(kemono_dll.jvs_read_coin_counter != NULL); + + if (slot_no > 0) { + return; + } + + kemono_dll.jvs_read_coin_counter(out); +} + +static void kemono_jvs_write_gpio(void *ctx, uint32_t state){ + kemono_dll.jvs_write_gpio(state); +} \ No newline at end of file diff --git a/games/kemonohook/jvs.h b/games/kemonohook/jvs.h new file mode 100644 index 0000000..359138b --- /dev/null +++ b/games/kemonohook/jvs.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "jvs/jvs-bus.h" + +HRESULT kemono_jvs_init(struct jvs_node **root); diff --git a/games/kemonohook/kemono-dll.c b/games/kemonohook/kemono-dll.c new file mode 100644 index 0000000..004cf50 --- /dev/null +++ b/games/kemonohook/kemono-dll.c @@ -0,0 +1,119 @@ +#include + +#include +#include + +#include "kemonohook/kemono-dll.h" + +#include "util/dll-bind.h" +#include "util/dprintf.h" + +const struct dll_bind_sym kemono_dll_syms[] = { + { + .sym = "kemono_io_init", + .off = offsetof(struct kemono_dll, init), + }, + { + .sym = "kemono_io_poll", + .off = offsetof(struct kemono_dll, poll), + }, + { + .sym = "kemono_io_jvs_read_coin_counter", + .off = offsetof(struct kemono_dll, jvs_read_coin_counter), + }, + { + .sym = "kemono_io_led_init", + .off = offsetof(struct kemono_dll, led_init), + }, + { + .sym = "kemono_io_led_set_colors", + .off = offsetof(struct kemono_dll, led_set_leds), + }, + { + .sym = "kemono_io_jvs_write_gpio", + .off = offsetof(struct kemono_dll, jvs_write_gpio), + } +}; + +struct kemono_dll kemono_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 kemono_dll_init(const struct kemono_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("Kemono IO: Failed to load IO DLL: %lx: %S\n", + hr, + cfg->path); + + goto end; + } + + dprintf("Kemono IO: Using custom IO DLL: %S\n", cfg->path); + src = owned; + } else { + owned = NULL; + src = self; + } + + get_api_version = (void *) GetProcAddress(src, "kemono_io_get_api_version"); + + if (get_api_version != NULL) { + kemono_dll.api_version = get_api_version(); + } else { + kemono_dll.api_version = 0x0100; + dprintf("Custom IO DLL does not expose kemono_io_get_api_version, " + "assuming API version 1.0.\n" + "Please ask the developer to update their DLL.\n"); + } + + if (kemono_dll.api_version >= 0x0200) { + hr = E_NOTIMPL; + dprintf("Kemono IO: Custom IO DLL implements an unsupported " + "API version (%#04x). Please update Segatools.\n", + kemono_dll.api_version); + + goto end; + } + + sym = kemono_dll_syms; + hr = dll_bind(&kemono_dll, src, &sym, _countof(kemono_dll_syms)); + + if (FAILED(hr)) { + if (src != self) { + dprintf("Kemono 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/games/kemonohook/kemono-dll.h b/games/kemonohook/kemono-dll.h new file mode 100644 index 0000000..df676f0 --- /dev/null +++ b/games/kemonohook/kemono-dll.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "kemonoio/kemonoio.h" + +struct kemono_dll { + uint16_t api_version; + + HRESULT (*init)(void); + + HRESULT (*poll)(uint16_t *ops, uint16_t *player); + + void (*jvs_read_coin_counter)(uint16_t *coins); + + HRESULT (*led_init)(void); + + void (*led_set_leds)(uint8_t board, uint8_t *rgb); + + void (*jvs_write_gpio)(uint32_t state); +}; + +struct kemono_dll_config { + wchar_t path[MAX_PATH]; +}; + +extern struct kemono_dll kemono_dll; + +HRESULT kemono_dll_init(const struct kemono_dll_config *cfg, HINSTANCE self); diff --git a/games/kemonohook/kemonohook.def b/games/kemonohook/kemonohook.def new file mode 100644 index 0000000..601a00b --- /dev/null +++ b/games/kemonohook/kemonohook.def @@ -0,0 +1,83 @@ +LIBRARY kemonohook + +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 + kemono_io_get_api_version + kemono_io_jvs_read_coin_counter + kemono_io_init + kemono_io_poll + kemono_io_led_init + kemono_io_led_set_colors + kemono_io_jvs_write_gpio + fwdlusb_open + fwdlusb_close + fwdlusb_listupPrinter + fwdlusb_listupPrinterSN + fwdlusb_selectPrinter + fwdlusb_selectPrinterSN + fwdlusb_getPrinterInfo + fwdlusb_status + fwdlusb_statusAll + fwdlusb_resetPrinter + fwdlusb_updateFirmware + fwdlusb_getFirmwareInfo + fwdlusb_MakeThread + fwdlusb_ReleaseThread + fwdlusb_AttachThreadCount + fwdlusb_getErrorLog + chcusb_MakeThread + chcusb_open + chcusb_close + chcusb_ReleaseThread + chcusb_listupPrinter + chcusb_listupPrinterSN + chcusb_selectPrinter + chcusb_selectPrinterSN + chcusb_getPrinterInfo + chcusb_imageformat + chcusb_setmtf + chcusb_makeGamma + chcusb_setIcctable + chcusb_copies + chcusb_status + chcusb_statusAll + chcusb_startpage + chcusb_endpage + chcusb_write + chcusb_writeLaminate + chcusb_writeHolo + chcusb_setPrinterInfo + chcusb_getGamma + chcusb_getMtf + chcusb_cancelCopies + chcusb_setPrinterToneCurve + chcusb_getPrinterToneCurve + chcusb_blinkLED + chcusb_resetPrinter + chcusb_AttachThreadCount + chcusb_getPrintIDStatus + chcusb_setPrintStandby + chcusb_testCardFeed + chcusb_exitCard + chcusb_getCardRfidTID + chcusb_commCardRfidReader + chcusb_updateCardRfidReader + chcusb_getErrorLog + chcusb_getErrorStatus + chcusb_setCutList + chcusb_setLaminatePattern + chcusb_color_adjustment + chcusb_color_adjustmentEx + chcusb_getEEPROM + chcusb_setParameter + chcusb_getParameter + chcusb_universal_command diff --git a/games/kemonohook/meson.build b/games/kemonohook/meson.build new file mode 100644 index 0000000..63dc9e1 --- /dev/null +++ b/games/kemonohook/meson.build @@ -0,0 +1,33 @@ +shared_library( + 'kemonohook', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + vs_module_defs : 'kemonohook.def', + dependencies : [ + capnhook.get_variable('hook_dep'), + capnhook.get_variable('hooklib_dep'), + ], + link_with : [ + aimeio_lib, + amex_lib, + board_lib, + hooklib_lib, + jvs_lib, + kemonoio_lib, + platform_lib, + unityhook_lib, + util_lib, + ], + sources : [ + 'config.c', + 'config.h', + 'dllmain.c', + 'hooks.c', + 'hooks.h', + 'jvs.c', + 'jvs.h', + 'kemono-dll.c', + 'kemono-dll.h', + ], +) diff --git a/games/kemonoio/config.c b/games/kemonoio/config.c new file mode 100644 index 0000000..88afa5d --- /dev/null +++ b/games/kemonoio/config.c @@ -0,0 +1,28 @@ +#include + +#include +#include +#include + +#include "kemonoio/config.h" + +void kemono_io_config_load( + struct kemono_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); + + cfg->vk_left = GetPrivateProfileIntW(L"io3", L"left", VK_LEFT, filename); + cfg->vk_right = GetPrivateProfileIntW(L"io3", L"right", VK_RIGHT, filename); + cfg->vk_up = GetPrivateProfileIntW(L"io3", L"up", VK_UP, filename); + cfg->vk_down = GetPrivateProfileIntW(L"io3", L"down", VK_DOWN, filename); + cfg->vk_red = GetPrivateProfileIntW(L"io3", L"red", 'A', filename); + cfg->vk_green = GetPrivateProfileIntW(L"io3", L"green", 'S', filename); + cfg->vk_blue = GetPrivateProfileIntW(L"io3", L"blue", 'D', filename); + cfg->vk_start = GetPrivateProfileIntW(L"io3", L"start", VK_RETURN, filename); +} diff --git a/games/kemonoio/config.h b/games/kemonoio/config.h new file mode 100644 index 0000000..f2c36f3 --- /dev/null +++ b/games/kemonoio/config.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +#include + +struct kemono_io_config { + uint8_t vk_test; + uint8_t vk_service; + uint8_t vk_coin; + + uint8_t vk_left; + uint8_t vk_right; + uint8_t vk_up; + uint8_t vk_down; + uint8_t vk_red; + uint8_t vk_green; + uint8_t vk_blue; + uint8_t vk_start; +}; + +void kemono_io_config_load( + struct kemono_io_config *cfg, + const wchar_t *filename); diff --git a/games/kemonoio/kemonoio.c b/games/kemonoio/kemonoio.c new file mode 100644 index 0000000..932abb1 --- /dev/null +++ b/games/kemonoio/kemonoio.c @@ -0,0 +1,110 @@ +#include + +#include +#include +#include +#include + +#include "kemonoio/kemonoio.h" +#include "kemonoio/config.h" +#include "util/env.h" + +static uint8_t kemono_opbtn; +static uint16_t kemono_pbtn; +static uint16_t kemono_io_coins; +static struct kemono_io_config kemono_io_cfg; +static bool kemono_io_coin; + +uint16_t kemono_io_get_api_version(void) { + return 0x0100; +} + +HRESULT kemono_io_init(void) { + kemono_io_config_load(&kemono_io_cfg, get_config_path()); + + kemono_io_coins = 0; + + return S_OK; +} + +HRESULT kemono_io_poll(uint16_t *ops, uint16_t *player) { + kemono_opbtn = 0; + kemono_pbtn = 0; + + if (GetAsyncKeyState(kemono_io_cfg.vk_test) & 0x8000) { + kemono_opbtn |= KEMONO_IO_OPBTN_TEST; + } + + if (GetAsyncKeyState(kemono_io_cfg.vk_service) & 0x8000) { + kemono_opbtn |= KEMONO_IO_OPBTN_SERVICE; + } + + if (kemono_io_cfg.vk_coin && + (GetAsyncKeyState(kemono_io_cfg.vk_coin) & 0x8000)) { + if (!kemono_io_coin) { + kemono_io_coin = true; + kemono_io_coins++; + } + } else { + kemono_io_coin = false; + } + + if (GetAsyncKeyState(kemono_io_cfg.vk_up)) { + kemono_pbtn |= KEMONO_IO_GAMEBTN_UP; + } + + if (GetAsyncKeyState(kemono_io_cfg.vk_down)) { + kemono_pbtn |= KEMONO_IO_GAMEBTN_DOWN; + } + + if (GetAsyncKeyState(kemono_io_cfg.vk_left)) { + kemono_pbtn |= KEMONO_IO_GAMEBTN_LEFT; + } + + if (GetAsyncKeyState(kemono_io_cfg.vk_right)) { + kemono_pbtn |= KEMONO_IO_GAMEBTN_RIGHT; + } + + if (GetAsyncKeyState(kemono_io_cfg.vk_red)) { + kemono_pbtn |= KEMONO_IO_GAMEBTN_R; + } + + if (GetAsyncKeyState(kemono_io_cfg.vk_green)) { + kemono_pbtn |= KEMONO_IO_GAMEBTN_G; + } + + if (GetAsyncKeyState(kemono_io_cfg.vk_blue)) { + kemono_pbtn |= KEMONO_IO_GAMEBTN_B; + } + + if (GetAsyncKeyState(kemono_io_cfg.vk_start)) { + kemono_pbtn |= KEMONO_IO_GAMEBTN_START; + } + + if (ops != NULL) { + *ops = kemono_opbtn; + } + if (player != NULL) { + *player = kemono_pbtn; + } + + return S_OK; +} + +void kemono_io_jvs_read_coin_counter(uint16_t *out) { + assert(out != NULL); + + *out = kemono_io_coins; +} + +HRESULT kemono_io_led_init(void) { + return S_OK; +} + +void kemono_io_led_set_colors(uint8_t board, uint8_t *rgb) { + +} + +void kemono_io_jvs_write_gpio(uint32_t state){ + +} diff --git a/games/kemonoio/kemonoio.h b/games/kemonoio/kemonoio.h new file mode 100644 index 0000000..3b180e2 --- /dev/null +++ b/games/kemonoio/kemonoio.h @@ -0,0 +1,81 @@ +#pragma once + +#include + +#include + +enum { + KEMONO_IO_OPBTN_TEST = 0x01, + KEMONO_IO_OPBTN_SERVICE = 0x02 +}; + +enum { + KEMONO_IO_GAMEBTN_UP = 0x01, + KEMONO_IO_GAMEBTN_DOWN = 0x02, + KEMONO_IO_GAMEBTN_LEFT = 0x04, + KEMONO_IO_GAMEBTN_RIGHT = 0x08, + KEMONO_IO_GAMEBTN_R = 0x10, + KEMONO_IO_GAMEBTN_G = 0x20, + KEMONO_IO_GAMEBTN_B = 0x40, + KEMONO_IO_GAMEBTN_START = 0x80 +}; + +/* Get the version of the Kemono 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 kemono_io_get_api_version(void); + +/* Initialize the IO DLL. This is the second function that will be called on + your DLL, after kemono_io_get_api_version. + + All subsequent calls to this API may originate from arbitrary threads. + + Minimum API version: 0x0100 */ + +HRESULT kemono_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 kemono_io_poll(uint16_t* ops, uint16_t* player); + +/* 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 kemono_io_jvs_read_coin_counter(uint16_t *out); + +/* Initialize LED emulation. This function will be called before any + other fgo_io_led_*() function calls. + + 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 kemono_io_led_init(void); + +/* Update the RGB LEDs. + + The left side LED bar are indices 0 to 32. + The right side LED bar are indices 33 to 65. + + Minimum API version: 0x0100 */ +void kemono_io_led_set_colors(uint8_t board, uint8_t *rgb); + +/* Update the button LEDs. + + Button R: Bit 15 + Button G: Bit 1 + Button B: Bit 13 + Start Button: Bit 11 + + Minimum API version: 0x0100 */ +void kemono_io_jvs_write_gpio(uint32_t state); \ No newline at end of file diff --git a/games/kemonoio/meson.build b/games/kemonoio/meson.build new file mode 100644 index 0000000..5e5c1ac --- /dev/null +++ b/games/kemonoio/meson.build @@ -0,0 +1,12 @@ +kemonoio_lib = static_library( + 'kemonoio_lib', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + sources : [ + 'kemonoio.c', + 'kemonoio.h', + 'config.c', + 'config.h', + ], +) diff --git a/games/mai2hook/config.c b/games/mai2hook/config.c new file mode 100644 index 0000000..5435ada --- /dev/null +++ b/games/mai2hook/config.c @@ -0,0 +1,93 @@ +#include +#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 touch_config_load( + struct touch_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable_1p = GetPrivateProfileIntW(L"touch", L"p1Enable", 1, filename); + cfg->enable_2p = GetPrivateProfileIntW(L"touch", L"p2Enable", 1, filename); +} + +void led15070_config_load(struct led15070_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + wchar_t tmpstr[16]; + + cfg->enable = GetPrivateProfileIntW(L"led15070", L"enable", 1, filename); + cfg->port_no[0] = GetPrivateProfileIntW(L"led15070", L"portNo1", 0, filename); + cfg->port_no[1] = GetPrivateProfileIntW(L"led15070", L"portNo2", 0, filename); + cfg->fw_ver = GetPrivateProfileIntW(L"led15070", L"fwVer", 0x90, filename); + cfg->fw_sum = GetPrivateProfileIntW(L"led15070", L"fwSum", 0x00, filename); + + GetPrivateProfileStringW( + L"led15070", + L"boardNumber", + L"15070-04", + tmpstr, + _countof(tmpstr), + filename); + + size_t n = wcstombs(cfg->board_number, tmpstr, sizeof(cfg->board_number)); + for (int i = n; i < sizeof(cfg->board_number); i++) + { + cfg->board_number[i] = ' '; + } + + GetPrivateProfileStringW( + L"led15070", + L"eepromPath", + L"DEVICE", + cfg->eeprom_path, + _countof(cfg->eeprom_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); + vfd_config_load(&cfg->vfd, filename); + mai2_dll_config_load(&cfg->dll, filename); + touch_config_load(&cfg->touch, filename); + led15070_config_load(&cfg->led15070, filename); + unity_config_load(&cfg->unity, filename); +} diff --git a/games/mai2hook/config.h b/games/mai2hook/config.h new file mode 100644 index 0000000..1055b81 --- /dev/null +++ b/games/mai2hook/config.h @@ -0,0 +1,45 @@ +#pragma once + +#include + +#include "board/config.h" + +#include "hooklib/dvd.h" + +#include "mai2hook/mai2-dll.h" + +#include "platform/config.h" + +#include "mai2hook/touch.h" + +#include "board/led15070.h" + +#include "unityhook/config.h" + +struct mai2_hook_config { + struct platform_config platform; + struct aime_config aime; + struct dvd_config dvd; + struct io4_config io4; + struct vfd_config vfd; + struct mai2_dll_config dll; + struct touch_config touch; + struct led15070_config led15070; + struct unity_config unity; +}; + +void mai2_dll_config_load( + struct mai2_dll_config *cfg, + const wchar_t *filename); + +void touch_config_load( + struct touch_config *cfg, + const wchar_t *filename); + +void led15070_config_load( + struct led15070_config *cfg, + const wchar_t *filename); + +void mai2_hook_config_load( + struct mai2_hook_config *cfg, + const wchar_t *filename); diff --git a/games/mai2hook/dllmain.c b/games/mai2hook/dllmain.c new file mode 100644 index 0000000..cecc01e --- /dev/null +++ b/games/mai2hook/dllmain.c @@ -0,0 +1,172 @@ +/* + "maimai DX" (mai2) hook + + Devices + + USB: 837-15257-01 "Type 4" I/O Board + USB: 2 * 601-13216 USB "QR Code" Camera (SDEZ2, SDEZ3) + USB: 601-13249 USB "Player" Camera (SDEZ1) + USB: 837-15067-02 IC BD USB to Serial 232 + connected to + 837-15070-04 LED Board Controller (COM21) + 837-15070-04 LED Board Controller (COM23) + + COM1: 837-15396 "Gen 3" Aime Reader + COM2: 200-6275 VFD GP1232A02A FUTABA Board + COM3: 509-6483 Touch Panel Controller + COM4: 509-6483 Touch Panel Controller +*/ + +#include + +#include + +#include "board/io4.h" +#include "board/sg-reader.h" +#include "board/vfd.h" + +#include "hook/process.h" +#include "hook/table.h" +#include "hook/iohook.h" + +#include "hooklib/serial.h" +#include "hooklib/spike.h" + +#include "mai2hook/config.h" +#include "mai2hook/io4.h" +#include "mai2hook/mai2-dll.h" + +#include "platform/platform.h" + +#include "unityhook/hook.h" + +#include "util/dprintf.h" +#include "util/env.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, get_config_path()); + + /* Hook Win32 APIs */ + + dvd_hook_init(&mai2_hook_cfg.dvd, mai2_hook_mod); + serial_hook_init(); + + /* Initialize emulation hooks */ + + struct dipsw_config new_dipsw_config[8] = { + {L"Delivery Server", L"Server", L"Client"}, + }; + + // Set the system dip switch configuration + memcpy(mai2_hook_cfg.platform.system.dipsw_config, new_dipsw_config, + sizeof(new_dipsw_config)); + + hr = platform_hook_init( + &mai2_hook_cfg.platform, + "SDEZ", + "ACA1", + mai2_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + /* Initialize DLLs */ + + hr = mai2_dll_init(&mai2_hook_cfg.dll, mai2_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + // Touch Panel uses COM3 and COM4 + + hr = touch_hook_init(&mai2_hook_cfg.touch); + + if (FAILED(hr)) { + goto fail; + } + + // LED board uses COM21 and COM23 + unsigned int led_port_no[2] = {21, 23}; + hr = led15070_hook_init(&mai2_hook_cfg.led15070, + mai2_dll.led_init, + mai2_dll.led_set_fet_output, + mai2_dll.led_dc_update, + mai2_dll.led_gs_update, + led_port_no); + + if (FAILED(hr)) { + goto fail; + } + + hr = sg_reader_hook_init(&mai2_hook_cfg.aime, 1, 3, mai2_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = vfd_hook_init(&mai2_hook_cfg.vfd, 2); + + 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(&mai2_hook_cfg.unity, mai2_hook_mod, NULL); + + /* Initialize debug helpers */ + + spike_hook_init(get_config_path()); + + 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/games/mai2hook/io4.c b/games/mai2hook/io4.c new file mode 100644 index 0000000..4eb7f6b --- /dev/null +++ b/games/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/games/mai2hook/io4.h b/games/mai2hook/io4.h new file mode 100644 index 0000000..5a0a2a0 --- /dev/null +++ b/games/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/games/mai2hook/mai2-dll.c b/games/mai2hook/mai2-dll.c new file mode 100644 index 0000000..121750e --- /dev/null +++ b/games/mai2hook/mai2-dll.c @@ -0,0 +1,130 @@ +#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), + }, { + .sym = "mai2_io_touch_init", + .off = offsetof(struct mai2_dll, touch_init), + }, { + .sym = "mai2_io_touch_set_sens", + .off = offsetof(struct mai2_dll, touch_set_sens), + }, { + .sym = "mai2_io_touch_update", + .off = offsetof(struct mai2_dll, touch_update), + }, { + .sym = "mai2_io_led_init", + .off = offsetof(struct mai2_dll, led_init), + }, { + .sym = "mai2_io_led_set_fet_output", + .off = offsetof(struct mai2_dll, led_set_fet_output), + }, { + .sym = "mai2_io_led_dc_update", + .off = offsetof(struct mai2_dll, led_dc_update), + }, { + .sym = "mai2_io_led_gs_update", + .off = offsetof(struct mai2_dll, led_gs_update), + }, +}; + +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 = 0x0101; + 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/games/mai2hook/mai2-dll.h b/games/mai2hook/mai2-dll.h new file mode 100644 index 0000000..599d44a --- /dev/null +++ b/games/mai2hook/mai2-dll.h @@ -0,0 +1,28 @@ +#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); + HRESULT (*touch_init)(mai2_io_touch_callback_t callback); + void (*touch_set_sens)(uint8_t *bytes); + void (*touch_update)(bool player1, bool player2); + HRESULT (*led_init)(void); + void (*led_set_fet_output)(uint8_t board, const uint8_t *rgb); + void (*led_dc_update)(uint8_t board, const uint8_t *rgb); + void (*led_gs_update)(uint8_t board, const uint8_t *rgb); +}; + +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/games/mai2hook/mai2hook.def b/games/mai2hook/mai2hook.def new file mode 100644 index 0000000..50e4058 --- /dev/null +++ b/games/mai2hook/mai2hook.def @@ -0,0 +1,25 @@ +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 + mai2_io_touch_init + mai2_io_touch_set_sens + mai2_io_touch_update + mai2_io_led_init + mai2_io_led_set_fet_output + mai2_io_led_dc_update + mai2_io_led_gs_update diff --git a/games/mai2hook/meson.build b/games/mai2hook/meson.build new file mode 100644 index 0000000..ed47793 --- /dev/null +++ b/games/mai2hook/meson.build @@ -0,0 +1,31 @@ +shared_library( + 'mai2hook', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + vs_module_defs : 'mai2hook.def', + dependencies : [ + capnhook.get_variable('hook_dep'), + capnhook.get_variable('hooklib_dep'), + ], + link_with : [ + aimeio_lib, + board_lib, + hooklib_lib, + mai2io_lib, + platform_lib, + unityhook_lib, + util_lib, + ], + sources : [ + 'config.c', + 'config.h', + 'dllmain.c', + 'io4.c', + 'io4.h', + 'touch.c', + 'touch.h', + 'mai2-dll.c', + 'mai2-dll.h', + ], +) diff --git a/games/mai2hook/touch.c b/games/mai2hook/touch.c new file mode 100644 index 0000000..305d005 --- /dev/null +++ b/games/mai2hook/touch.c @@ -0,0 +1,259 @@ +#include +#include + +#include "hooklib/fdshark.h" +#include "hooklib/reg.h" + +#include "mai2hook/mai2-dll.h" +#include "mai2hook/touch.h" + +#include "util/dprintf.h" +#include "util/dump.h" + + +static HRESULT read_reg_touch_1p(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_wstr(bytes, nbytes, L"COM3"); +} + +static HRESULT read_reg_touch_2p(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_wstr(bytes, nbytes, L"COM4"); +} + +static const struct reg_hook_val touch_reg_key[] = { + { + .name = L"\\Device\\RealTouchBoard0", + .read = read_reg_touch_1p, + .type = REG_SZ, + }, + { + .name = L"\\Device\\RealTouchBoard1", + .read = read_reg_touch_2p, + .type = REG_SZ, + }, +}; + +const char *sensor_map[34] = { + "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", // 0x41 - 0x48 + "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", // 0x49 - 0x50 + "C1", "C2", // 0x51 - 0x52 + "D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8", // 0x53 - 0x5A + "E1", "E2", "E3", "E4", "E5", "E6", "E7", "E8" // 0x5B - 0x62 +}; + +const char *sensor_to_str(uint8_t sensor) +{ + if (sensor < 0x41 || sensor > 0x62) + { + return "Invalid"; + } + + return sensor_map[sensor - 0x41]; +} + +static CRITICAL_SECTION touch_1p_lock; +static struct uart touch_1p_uart; +static uint8_t touch_1p_written_bytes[64]; +static uint8_t touch_1p_readable_bytes[64]; +static bool touch_1p_status = false; + +static CRITICAL_SECTION touch_2p_lock; +static struct uart touch_2p_uart; +static uint8_t touch_2p_written_bytes[64]; +static uint8_t touch_2p_readable_bytes[64]; +static bool touch_2p_status = false; + +HRESULT touch_hook_init(const struct touch_config *cfg) +{ + assert(cfg != NULL); + + if (!cfg->enable_1p && !cfg->enable_2p) + { + return S_FALSE; + } + + HRESULT hr = reg_hook_push_key(HKEY_LOCAL_MACHINE, L"HARDWARE\\DEVICEMAP\\SERIALCOMM", touch_reg_key, _countof(touch_reg_key)); + + if (FAILED(hr)) + { + return hr; + } + + if (cfg->enable_1p) + { + dprintf("Mai2 touch 1P: Init.\n"); + + InitializeCriticalSection(&touch_1p_lock); + uart_init(&touch_1p_uart, 3); + touch_1p_uart.written.bytes = touch_1p_written_bytes; + touch_1p_uart.written.nbytes = sizeof(touch_1p_written_bytes); + touch_1p_uart.readable.bytes = touch_1p_readable_bytes; + touch_1p_uart.readable.nbytes = sizeof(touch_1p_readable_bytes); + } + + if (cfg->enable_2p) + { + dprintf("Mai2 touch 2P: Init.\n"); + + InitializeCriticalSection(&touch_2p_lock); + uart_init(&touch_2p_uart, 4); + touch_2p_uart.written.bytes = touch_2p_written_bytes; + touch_2p_uart.written.nbytes = sizeof(touch_2p_written_bytes); + touch_2p_uart.readable.bytes = touch_2p_readable_bytes; + touch_2p_uart.readable.nbytes = sizeof(touch_2p_readable_bytes); + } + + return iohook_push_handler(touch_handle_irp); +} + +static HRESULT touch_handle_irp(struct irp *irp) +{ + HRESULT hr; + + assert(irp != NULL); + + if (uart_match_irp(&touch_1p_uart, irp)) + { + EnterCriticalSection(&touch_1p_lock); + hr = touch_handle_irp_locked(irp, &touch_1p_uart); + LeaveCriticalSection(&touch_1p_lock); + } + else if (uart_match_irp(&touch_2p_uart, irp)) + { + EnterCriticalSection(&touch_2p_lock); + hr = touch_handle_irp_locked(irp, &touch_2p_uart); + LeaveCriticalSection(&touch_2p_lock); + } + else + { + return iohook_invoke_next(irp); + } + + return hr; +} + +static HRESULT touch_handle_irp_locked(struct irp *irp, struct uart *uart) +{ + HRESULT hr; + + if (irp->op == IRP_OP_OPEN) + { + dprintf("Mai2 touch port %d: Starting backend\n", uart->port_no); + hr = mai2_dll.touch_init(touch_auto_scan); + + if (FAILED(hr)) + { + dprintf("Mai2 touch port %d: Backend error: %x\n", uart->port_no, (int)hr); + return hr; + } + } + + hr = uart_handle_irp(uart, irp); + + if (FAILED(hr) || irp->op != IRP_OP_WRITE) + { + return hr; + } + +#if defined(LOG_MAI2_TOUCH) + dprintf("Mai2 touch port %d WRITE:\n", uart->port_no); + dump_iobuf(&uart->written); +#endif + uint8_t port_no = uart->port_no; + uint8_t *src = uart->written.bytes; + uint8_t *dest = uart->readable.bytes; + + switch (src[3]) + { + case commandRSET: + dprintf("Mai2 touch port %d: Reset\n", port_no); + break; + + case commandHALT: // Enter Conditioning mode and stop sending touch data. + dprintf("Mai2 touch port %d: Halt\n", port_no); + assert(mai2_dll.touch_update != NULL); + if (port_no == 3) + { + EnterCriticalSection(&touch_1p_lock); + touch_1p_status = false; + mai2_dll.touch_update(touch_1p_status, touch_2p_status); + LeaveCriticalSection(&touch_1p_lock); + } + else + { + EnterCriticalSection(&touch_2p_lock); + touch_2p_status = false; + mai2_dll.touch_update(touch_1p_status, touch_2p_status); + LeaveCriticalSection(&touch_2p_lock); + } + break; + + case commandSTAT: // Exit Conditioning mode and resume sending touch data. + dprintf("Mai2 touch port %d: Stat\n", port_no); + assert(mai2_dll.touch_update != NULL); + if (port_no == 3) + { + EnterCriticalSection(&touch_1p_lock); + touch_1p_status = true; + mai2_dll.touch_update(touch_1p_status, touch_2p_status); + LeaveCriticalSection(&touch_1p_lock); + } + else + { + EnterCriticalSection(&touch_2p_lock); + touch_2p_status = true; + mai2_dll.touch_update(touch_1p_status, touch_2p_status); + LeaveCriticalSection(&touch_2p_lock); + } + break; + + case commandRatio: +#if defined(LOG_MAI2_TOUCH) + dprintf("Mai2 touch side %c: set sensor %s ratio to %d\n", src[1], sensor_to_str(src[2]), src[4]); +#endif + dest[0] = res_start; + dest[1] = src[1]; // L,R + dest[2] = src[2]; // sensor + dest[3] = commandRatio; + dest[4] = src[4]; // Ratio + dest[5] = res_end; + uart->readable.pos = 6; + // The Ratio is fixed at 0x72 and does not need to be sent to mai2io for processing. + break; + + case commandSens: +#if defined(LOG_MAI2_TOUCH) + dprintf("Mai2 touch side %c: set sensor %s sensitivity to %d\n", src[1], sensor_to_str(src[2]), src[4]); +#endif + dest[0] = res_start; + dest[1] = src[1]; // L,R + dest[2] = src[2]; // sensor + dest[3] = commandSens; + dest[4] = src[4]; // Sensitivity + dest[5] = res_end; + uart->readable.pos = 6; + mai2_dll.touch_set_sens(dest); + break; + + default: + dprintf("Mai2 touch port %d: Unknow %02x\n", port_no, src[3]); + break; + } +#if defined(LOG_MAI2_TOUCH) + dprintf("Mai2 touch port %d READ:\n", uart->port_no); + dump_iobuf(&uart->readable); +#endif + uart->written.pos = 0; + + return hr; +} + +static void touch_auto_scan(const uint8_t player, const uint8_t state[7]) +{ + struct uart *touch_uart = player == 1 ? &touch_1p_uart : &touch_2p_uart; + touch_uart->readable.bytes[0] = res_start; + memcpy(&touch_uart->readable.bytes[1], state, 7); + touch_uart->readable.bytes[8] = res_end; + touch_uart->readable.pos = 9; +} diff --git a/games/mai2hook/touch.h b/games/mai2hook/touch.h new file mode 100644 index 0000000..f6fc72a --- /dev/null +++ b/games/mai2hook/touch.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include + +#include "hooklib/uart.h" + +struct touch_config +{ + bool enable_1p; + bool enable_2p; +}; + +enum +{ + commandRSET = 0x45, // E + commandHALT = 0x4C, // L + commandSTAT = 0x41, // A + commandRatio = 0x72, // r + commandSens = 0x6B, // k + req_start = 0x7b, // { + req_end = 0x7d, // } + res_start = 0x28, // ( + res_end = 0x29, // ) +}; + +extern const char *sensor_map[34]; +const char *sensor_to_str(uint8_t sensor); + +HRESULT touch_hook_init(const struct touch_config *cfg); +static HRESULT touch_handle_irp(struct irp *irp); +static HRESULT touch_handle_irp_locked(struct irp *irp, struct uart *uart); + +/* Called in mai2io to send touch data. + Similar to chuni slider_res_auto_scan, but the host does not require periodic updates. + Touch data is sent only when there is a change. */ +static void touch_auto_scan(const uint8_t player, const uint8_t state[7]); diff --git a/games/mai2io/config.c b/games/mai2io/config.c new file mode 100644 index 0000000..c6c9913 --- /dev/null +++ b/games/mai2io/config.c @@ -0,0 +1,84 @@ +#include + +#include +#include +#include +#include + +#include "mai2io/config.h" +#include "mai2hook/touch.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}; + +static const int mai2_io_1p_touch_default[] = { + 'T', 'Y', 'H', 'N', 'B', 'V', 'F', 'R', + 'T', 'Y', 'H', 'N', 'B', 'V', 'F', 'R', + 'G', 'G', + 'T', 'Y', 'H', 'N', 'B', 'V', 'F', 'R', + 'T', 'Y', 'H', 'N', 'B', 'V', 'F', 'R', +}; +static const int mai2_io_2p_touch_default[] = { + 'I', 'O', 'L', VK_OEM_PERIOD, VK_OEM_COMMA, 'M', 'J', 'U', + 'I', 'O', 'L', VK_OEM_PERIOD, VK_OEM_COMMA, 'M', 'J', 'U', + 'K', 'K', + 'I', 'O', 'L', VK_OEM_PERIOD, VK_OEM_COMMA, 'M', 'J', 'U', + 'I', 'O', 'L', VK_OEM_PERIOD, VK_OEM_COMMA, 'M', 'J', 'U', +}; + +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", VK_F1, filename); + cfg->vk_service = GetPrivateProfileIntW(L"io4", L"service", VK_F2, filename); + cfg->vk_coin = GetPrivateProfileIntW(L"io4", L"coin", VK_F3, filename); + cfg->vk_btn_enable = GetPrivateProfileIntW(L"button", L"enable", 1, filename); + + for (i = 0; i < 9; i++) + { + swprintf_s(key, _countof(key), L"p1Btn%i", i + 1); + cfg->vk_1p_btn[i] = GetPrivateProfileIntW( + L"button", + key, + mai2_io_1p_default[i], + filename); + + swprintf_s(key, _countof(key), L"p2Btn%i", i + 1); + cfg->vk_2p_btn[i] = GetPrivateProfileIntW( + L"button", + key, + mai2_io_2p_default[i], + filename); + } + + cfg->debug_input_1p = GetPrivateProfileIntW(L"touch", L"p1DebugInput", 0, filename); + cfg->debug_input_2p = GetPrivateProfileIntW(L"touch", L"p2DebugInput", 0, filename); + for (i = 0; i < 34; i++) + { + swprintf_s(key, _countof(key), L"p1Touch%S", sensor_map[i]); + cfg->vk_1p_touch[i] = GetPrivateProfileIntW( + L"touch", + key, + mai2_io_1p_touch_default[i], + filename); + + swprintf_s(key, _countof(key), L"p2Touch%S", sensor_map[i]); + cfg->vk_2p_touch[i] = GetPrivateProfileIntW( + L"touch", + key, + mai2_io_2p_touch_default[i], + filename); + } +} diff --git a/games/mai2io/config.h b/games/mai2io/config.h new file mode 100644 index 0000000..ce765ed --- /dev/null +++ b/games/mai2io/config.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +#include + +struct mai2_io_config { + uint8_t vk_test; + uint8_t vk_service; + uint8_t vk_coin; + bool vk_btn_enable; + uint8_t vk_1p_btn[9]; + uint8_t vk_2p_btn[9]; + bool debug_input_1p; + bool debug_input_2p; + uint8_t vk_1p_touch[34]; + uint8_t vk_2p_touch[34]; +}; + +void mai2_io_config_load( + struct mai2_io_config *cfg, + const wchar_t *filename); diff --git a/games/mai2io/mai2io.c b/games/mai2io/mai2io.c new file mode 100644 index 0000000..57404cb --- /dev/null +++ b/games/mai2io/mai2io.c @@ -0,0 +1,267 @@ +#include "mai2io/mai2io.h" + +#include +#include + +#include "mai2hook/touch.h" +#include "mai2io/config.h" +#include "util/dprintf.h" +#include "util/env.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; +mai2_io_touch_callback_t _callback; +static HANDLE mai2_io_touch_1p_thread; +static bool mai2_io_touch_1p_stop_flag; +static HANDLE mai2_io_touch_2p_thread; +static bool mai2_io_touch_2p_stop_flag; + +uint16_t mai2_io_get_api_version(void) { return 0x0101; } + +HRESULT mai2_io_init(void) { + mai2_io_config_load(&mai2_io_cfg, get_config_path()); + + 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; + } + // If sinmai has enabled DebugInput, there is no need to input buttons + // through hook amdaemon. + if (!mai2_io_cfg.vk_btn_enable) { + return S_OK; + } + + // 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; + } +} + +HRESULT mai2_io_touch_init(mai2_io_touch_callback_t callback) { + _callback = callback; + return S_OK; +} + +void mai2_io_touch_set_sens(uint8_t *bytes) { +#if 0 + dprintf("Mai2 touch side %c: set sensor %s sensitivity to %d\n", bytes[1], sensor_to_str(bytes[2]), bytes[4]); +#endif + return; +} + +void mai2_io_touch_update(bool player1, bool player2) { + if (mai2_io_cfg.debug_input_1p) { + if (player1 && mai2_io_touch_1p_thread == NULL) { + mai2_io_touch_1p_thread = (HANDLE)_beginthreadex( + NULL, 0, mai2_io_touch_1p_thread_proc, _callback, 0, NULL); + } else if (!player1 && mai2_io_touch_1p_thread != NULL) { + mai2_io_touch_1p_stop_flag = true; + + WaitForSingleObject(mai2_io_touch_1p_thread, INFINITE); + CloseHandle(mai2_io_touch_1p_thread); + mai2_io_touch_1p_thread = NULL; + + mai2_io_touch_1p_stop_flag = false; + } + } + + if (mai2_io_cfg.debug_input_2p) { + if (player2 && mai2_io_touch_2p_thread == NULL) { + mai2_io_touch_2p_thread = (HANDLE)_beginthreadex( + NULL, 0, mai2_io_touch_2p_thread_proc, _callback, 0, NULL); + } else if (!player2 && mai2_io_touch_2p_thread != NULL) { + mai2_io_touch_2p_stop_flag = true; + + WaitForSingleObject(mai2_io_touch_2p_thread, INFINITE); + CloseHandle(mai2_io_touch_2p_thread); + mai2_io_touch_2p_thread = NULL; + + mai2_io_touch_2p_stop_flag = false; + } + } +} + +static unsigned int __stdcall mai2_io_touch_1p_thread_proc(void *ctx) { + mai2_io_touch_callback_t callback = ctx; + + while (!mai2_io_touch_1p_stop_flag) { + uint8_t state[7] = {0, 0, 0, 0, 0, 0, 0}; + + for (int i = 0; i < 34; i++) { + if (GetAsyncKeyState(mai2_io_cfg.vk_1p_touch[i])) { + int byteIndex = i / 5; + int bitIndex = i % 5; + state[byteIndex] |= (1 << bitIndex); + } + } + callback(1, state); + Sleep(1); + } + return 0; +} + +static unsigned int __stdcall mai2_io_touch_2p_thread_proc(void *ctx) { + mai2_io_touch_callback_t callback = ctx; + + while (!mai2_io_touch_2p_stop_flag) { + uint8_t state[7] = {0, 0, 0, 0, 0, 0, 0}; + + for (int i = 0; i < 34; i++) { + if (GetAsyncKeyState(mai2_io_cfg.vk_2p_touch[i])) { + int byteIndex = i / 5; + int bitIndex = i % 5; + state[byteIndex] |= (1 << bitIndex); + } + } + callback(2, state); + Sleep(1); + } + return 0; +} + +HRESULT mai2_io_led_init(void) { return S_OK; } + +void mai2_io_led_set_fet_output(uint8_t board, const uint8_t *rgb) { +#if 0 + uint8_t player = board + 1; + dprintf("MAI2 LED %dP: BodyLed brightness: %d%%\n", player, + (rgb[0] * 100) / 255); + dprintf("MAI2 LED %dP: ExtLed brightness: %d%%\n", player, + (rgb[1] * 100) / 255); + dprintf("MAI2 LED %dP: SideLed brightness: %d%%\n", player, + (rgb[2] * 100) / 255); +#endif + return; +} + +void mai2_io_led_dc_update(uint8_t board, const uint8_t *rgb) { +#if 0 + uint8_t player = board + 1; + for (int i = 0; i < 10; i++) { + dprintf("Mai2 LED %dP: LED %d: %02X %02X %02X Speed: %02X\n", player + i, rgb[i * 4], rgb[i * 4 + 1], rgb[i * 4 + 2], rgb[i * 4 + 3]); + } +#endif + return; +} + +void mai2_io_led_gs_update(uint8_t board, const uint8_t *rgb) { +#if 0 + uint8_t player = board + 1; + for (int i = 0; i < 8; i++) { + dprintf("Mai2 LED %dP: LED %d: %02X %02X %02X Speed: %02X\n", player, i, + rgb[i * 4], rgb[i * 4 + 1], rgb[i * 4 + 2], rgb[i * 4 + 3]); + } +#endif + return; +} diff --git a/games/mai2io/mai2io.h b/games/mai2io/mai2io.h new file mode 100644 index 0000000..9c0a7f0 --- /dev/null +++ b/games/mai2io/mai2io.h @@ -0,0 +1,192 @@ +#pragma once + +#include + +#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); + +/* Callback function used by mai2_io_touch_1p/2p_thread_proc. + + The 'player'(1 or 2) parameter indicates which player the touch data is for. + + The 'state' represents a complete response packet. + The format of the state array is as follows: + uint8_t state[7] = { + bytes[0] - bit(0 , 0 , 0 , A5, A4, A3, A2, A1) + bytes[1] - bit(0 , 0 , 0 , B2, B1, A8, A7, A6) + bytes[2] - bit(0 , 0 , 0 , B7, B6, B5, B4, B3) + bytes[3] - bit(0 , 0 , 0 , D2, D1, C2, C1, B8) + bytes[4] - bit(0 , 0 , 0 , D7, D6, D5, D4, D3) + bytes[5] - bit(0 , 0 , 0 , E4, E3, E2, E1, D8) + bytes[6] - bit(0 , 0 , 0 , 0 , E8, E7, E6, E5) + } + The 7 bytes are the touch data, with each byte storing the touch state in the lower 5 bits. + A value of 1 indicates that the corresponding touch area is pressed. + The touch areas are ordered from A1 to E8, and the binary values are stored from low to high. */ + +typedef void (*mai2_io_touch_callback_t)(const uint8_t player, const uint8_t state[7]); + +/** + * @brief Initializes the touch input callback function + * + * This function accepts a callback function as a parameter and stores it in the global variable `_callback` + * for later handling of touch input events. + * + * @param callback The touch input callback function that takes two parameters: player number and the touch state array. + * @return HRESULT Returns the result of the operation, S_OK on success. + */ + +HRESULT mai2_io_touch_init(mai2_io_touch_callback_t callback); + +/* Send sensitivity setting data to the touch device. + Format: + bytes[0] - Header + bytes[1] - Target device, ASCII characters 'L' or 'R' + bytes[2] - Target touch point + bytes[3] - commandRatio identifier + bytes[4] - Ratio value to be set, within a fixed range + bytes[5] - Footer + + Example function, not actually used. The sensitivity range can be determined + based on the Ratio set within the game. */ + +void mai2_io_touch_set_sens(uint8_t *bytes); + +/** + * @brief Updates the touch input acceptance state + * + * This function determines whether the game is ready to accept touch input based on the states of player 1 and player 2. + * If the game is ready, it creates or stops the corresponding threads to handle touch data for each player. + * Whether or not threads are created for each player is controlled by `mai2_io_cfg.debug_input_1p` and `mai2_io_cfg.debug_input_2p` configuration. + * + * @param player1 If `true`, indicates the game is ready to accept touch data from player 1, `false` means the game is not ready. + * @param player2 If `true`, indicates the game is ready to accept touch data from player 2, `false` means the game is not ready. + */ + +void mai2_io_touch_update(bool player1, bool player2); + +/** + * @brief Player touch input handling thread + * + * This function runs in a separate thread, continuously monitoring player touch status and passing the state data + * to the main thread via a callback function. Each time a touch input is detected, it updates the `state` array and calls the callback. + * The thread stops when `mai2_io_touch_1p/2p_stop_flag` is `true`. + * + * @param ctx The callback function context, of type `mai2_io_touch_callback_t`, used to handle the touch input events. + * @return The thread's return value, typically `0`. + */ + +static unsigned int __stdcall mai2_io_touch_1p_thread_proc(void *ctx); + +static unsigned int __stdcall mai2_io_touch_2p_thread_proc(void *ctx); + +/* Initialize LED emulation. This function will be called before any + other mai2_io_led_*() function calls. + + 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: 0x0101 */ + +HRESULT mai2_io_led_init(void); + +/* Update the FET outputs. rgb is a pointer to an array up to 3 bytes. + + maimai DX uses two boards. Board 0 is for the player 1 side (left) and board 1 + is for the player 2 side (right). + + Set the brightness of the white light on the machine's outer shell. + The program will continuously send changed values to request the blinking effect. + + [0]: BodyLed + [1]: ExtLed + [2]: SideLed + + The LED is truned on when the byte is 255 and turned off when the byte is 0. + + Minimum API version: 0x0101 */ + +void mai2_io_led_set_fet_output(uint8_t board, const uint8_t *rgb); + +/* The effect of this command is unknown, it is triggered after LED_15070_CMD_EEPROM_READ. */ + +void mai2_io_led_dc_update(uint8_t board, const uint8_t *rgb); + +/* Update the RGB LEDs. rgb is a pointer to an array up to 8 * 4 = 32 bytes. + + maimai DX uses two boards. Board 0 is for the player 1 side (left) and board 1 + is for the player 2 side (right). + + The LEDs are laid out as follows: + [0-7]: 8 button LED + + Each rgb value is comprised for 4 bytes in the order of R, G, B, Speed. + Speed is a value from 0 to 255, where 0 is the fastest speed and 255 is the slowest. + + Minimum API version: 0x0101 */ + +void mai2_io_led_gs_update(uint8_t board, const uint8_t *rgb); diff --git a/games/mai2io/meson.build b/games/mai2io/meson.build new file mode 100644 index 0000000..3d06b9f --- /dev/null +++ b/games/mai2io/meson.build @@ -0,0 +1,15 @@ +mai2io_lib = static_library( + 'mai2io', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + dependencies : [ + capnhook.get_variable('hook_dep'), + ], + sources : [ + 'mai2io.c', + 'mai2io.h', + 'config.c', + 'config.h', + ], +) diff --git a/mercuryhook/config.c b/games/mercuryhook/config.c similarity index 96% rename from mercuryhook/config.c rename to games/mercuryhook/config.c index f087942..d15c305 100644 --- a/mercuryhook/config.c +++ b/games/mercuryhook/config.c @@ -1,5 +1,6 @@ #include #include +#include #include "board/config.h" @@ -68,6 +69,7 @@ void mercury_hook_config_load( dvd_config_load(&cfg->dvd, filename); io4_config_load(&cfg->io4, filename); gfx_config_load(&cfg->gfx, filename); + vfd_config_load(&cfg->vfd, filename); mercury_dll_config_load(&cfg->dll, filename); touch_config_load(&cfg->touch, filename); elisabeth_config_load(&cfg->elisabeth, filename); diff --git a/mercuryhook/config.h b/games/mercuryhook/config.h similarity index 96% rename from mercuryhook/config.h rename to games/mercuryhook/config.h index d5bf463..2105704 100644 --- a/mercuryhook/config.h +++ b/games/mercuryhook/config.h @@ -19,6 +19,7 @@ struct mercury_hook_config { struct dvd_config dvd; struct io4_config io4; struct gfx_config gfx; + struct vfd_config vfd; struct mercury_dll_config dll; struct touch_config touch; struct elisabeth_config elisabeth; diff --git a/mercuryhook/dllmain.c b/games/mercuryhook/dllmain.c similarity index 72% rename from mercuryhook/dllmain.c rename to games/mercuryhook/dllmain.c index f3df2a7..37030d3 100644 --- a/mercuryhook/dllmain.c +++ b/games/mercuryhook/dllmain.c @@ -1,5 +1,20 @@ +/* + "WACCA" (mercury) hook + + Devices + + USB: 837-15257-01 "Type 4" I/O Board + USB: 14-1497-R "Elisabeth" LED Board Controller + COM1: 837-15396 "Gen 3" Aime Reader + COM2: 200-6275 VFD GP1232A02A FUTABA Board + COM3: PSS-7135-L02-01 "Left Side" Touch Board + COM4: PSS-7135-L02-01 "Right Sdde" Touch Board +*/ + #include +#include + #include "board/io4.h" #include "board/sg-reader.h" #include "board/vfd.h" @@ -21,6 +36,7 @@ #include "platform/platform.h" #include "util/dprintf.h" +#include "util/env.h" static HMODULE mercury_hook_mod; static process_entry_t mercury_startup; @@ -36,7 +52,7 @@ static DWORD CALLBACK mercury_pre_startup(void) /* Load config */ - mercury_hook_config_load(&mercury_hook_cfg, L".\\segatools.ini"); + mercury_hook_config_load(&mercury_hook_cfg, get_config_path()); /* Hook Win32 APIs */ @@ -48,6 +64,14 @@ static DWORD CALLBACK mercury_pre_startup(void) /* Initialize emulation hooks */ + struct dipsw_config new_dipsw_config[8] = { + {L"Delivery Server", L"Server", L"Client"}, + }; + + // Set the system dip switch configuration + memcpy(mercury_hook_cfg.platform.system.dipsw_config, new_dipsw_config, + sizeof(new_dipsw_config)); + hr = platform_hook_init( &mercury_hook_cfg.platform, "SDFE", @@ -58,13 +82,13 @@ static DWORD CALLBACK mercury_pre_startup(void) goto fail; } - hr = sg_reader_hook_init(&mercury_hook_cfg.aime, 1, mercury_hook_mod); + hr = sg_reader_hook_init(&mercury_hook_cfg.aime, 1, 1, mercury_hook_mod); if (FAILED(hr)) { goto fail; } - hr = vfd_hook_init(2); + hr = vfd_hook_init(&mercury_hook_cfg.vfd, 2); if (FAILED(hr)) { goto fail; @@ -89,7 +113,7 @@ static DWORD CALLBACK mercury_pre_startup(void) /* Initialize debug helpers */ - spike_hook_init(L".\\segatools.ini"); + spike_hook_init(get_config_path()); dprintf("--- End mercury_pre_startup ---\n"); diff --git a/mercuryhook/elisabeth.c b/games/mercuryhook/elisabeth.c similarity index 100% rename from mercuryhook/elisabeth.c rename to games/mercuryhook/elisabeth.c diff --git a/mercuryhook/elisabeth.h b/games/mercuryhook/elisabeth.h similarity index 90% rename from mercuryhook/elisabeth.h rename to games/mercuryhook/elisabeth.h index 5806c99..32512f3 100644 --- a/mercuryhook/elisabeth.h +++ b/games/mercuryhook/elisabeth.h @@ -1,5 +1,7 @@ #pragma once + #include +#include struct led_data { DWORD unitCount; diff --git a/mercuryhook/io4.c b/games/mercuryhook/io4.c similarity index 100% rename from mercuryhook/io4.c rename to games/mercuryhook/io4.c diff --git a/mercuryhook/io4.h b/games/mercuryhook/io4.h similarity index 100% rename from mercuryhook/io4.h rename to games/mercuryhook/io4.h diff --git a/mercuryhook/mercury-dll.c b/games/mercuryhook/mercury-dll.c similarity index 100% rename from mercuryhook/mercury-dll.c rename to games/mercuryhook/mercury-dll.c diff --git a/mercuryhook/mercury-dll.h b/games/mercuryhook/mercury-dll.h similarity index 100% rename from mercuryhook/mercury-dll.h rename to games/mercuryhook/mercury-dll.h diff --git a/mercuryhook/mercuryhook.def b/games/mercuryhook/mercuryhook.def similarity index 100% rename from mercuryhook/mercuryhook.def rename to games/mercuryhook/mercuryhook.def diff --git a/mercuryhook/meson.build b/games/mercuryhook/meson.build similarity index 95% rename from mercuryhook/meson.build rename to games/mercuryhook/meson.build index 7a676b2..fd6f46a 100644 --- a/mercuryhook/meson.build +++ b/games/mercuryhook/meson.build @@ -4,7 +4,6 @@ shared_library( include_directories : inc, implicit_include_directories : false, vs_module_defs : 'mercuryhook.def', - c_pch : '../precompiled.h', dependencies : [ capnhook.get_variable('hook_dep'), capnhook.get_variable('hooklib_dep'), diff --git a/mercuryhook/touch.c b/games/mercuryhook/touch.c similarity index 98% rename from mercuryhook/touch.c rename to games/mercuryhook/touch.c index 87ba6d5..3f73a2b 100644 --- a/mercuryhook/touch.c +++ b/games/mercuryhook/touch.c @@ -130,7 +130,7 @@ static HRESULT touch0_handle_irp_locked(struct irp *irp) } for (;;) { -#if 0 +#if defined(LOG_MERCURY_SLIDER) dprintf("TX0 Buffer:\n"); dump_iobuf(&touch0_uart.written); #endif @@ -177,7 +177,7 @@ static HRESULT touch1_handle_irp_locked(struct irp *irp) } for (;;) { -#if 0 +#if defined(LOG_MERCURY_SLIDER) dprintf("TX1 Buffer:\n"); dump_iobuf(&touch1_uart.written); #endif @@ -305,11 +305,11 @@ static HRESULT touch_handle_get_unit_board_ver(const struct touch_req *req) resp.cmd = 0xa8; resp.checksum = 0; - if (req->side == 0) { + if (req->side == 0) { resp.version[6] = 'R'; resp.checksum = calc_checksum(&resp, sizeof(resp)); - #if 0 + #if defined(LOG_MERCURY_SLIDER) for (int i = 0; i < sizeof(resp.version); i++) { dprintf("0x%02x ", resp.version[i]); } @@ -322,7 +322,7 @@ static HRESULT touch_handle_get_unit_board_ver(const struct touch_req *req) resp.version[6] = 'L'; resp.checksum = calc_checksum(&resp, sizeof(resp)); - #if 0 + #if defined(LOG_MERCURY_SLIDER) for (int i = 0; i < sizeof(resp.version); i++) { dprintf("0x%02x ", resp.version[i]); } @@ -370,7 +370,7 @@ static HRESULT touch_handle_mystery2(const struct touch_req *req) if (req->side == 0) { hr = iobuf_write(&touch0_uart.readable, &resp, sizeof(resp)); - } + } else { hr = iobuf_write(&touch1_uart.readable, &resp, sizeof(resp)); } @@ -388,7 +388,7 @@ static HRESULT touch_handle_start_auto_scan(const struct touch_req *req) dprintf("Wacca Touch%d: Start Auto", req->side); - #if 0 + #if defined(LOG_MERCURY_SLIDER) for (int i = 0; i < req->data_length; i++) dprintf("0x%02x ", req->data[i]); #endif @@ -451,13 +451,13 @@ static void touch_res_auto_scan(const bool *state) counter++; } } - + memcpy(frame0.data1, dataR, sizeof(dataR)); memcpy(frame0.data2, data2, sizeof(data2)); memcpy(frame1.data1, dataL, sizeof(dataL)); memcpy(frame1.data2, data2, sizeof(data2)); - + frame0.checksum = 0; frame0.checksum = calc_checksum(&frame0, sizeof(frame0)); diff --git a/mercuryhook/touch.h b/games/mercuryhook/touch.h similarity index 100% rename from mercuryhook/touch.h rename to games/mercuryhook/touch.h diff --git a/mercuryio/config.c b/games/mercuryio/config.c similarity index 86% rename from mercuryio/config.c rename to games/mercuryio/config.c index f3c8e54..1308ac8 100644 --- a/mercuryio/config.c +++ b/games/mercuryio/config.c @@ -3,6 +3,7 @@ #include #include #include +#include #include "mercuryio/config.h" @@ -27,11 +28,11 @@ void mercury_io_config_load( assert(cfg != NULL); assert(filename != NULL); - cfg->vk_test = GetPrivateProfileIntW(L"io4", L"test", 0x2D, filename); - cfg->vk_service = GetPrivateProfileIntW(L"io4", L"service", 0x2E, filename); - cfg->vk_coin = GetPrivateProfileIntW(L"io4", L"coin", 0x24, filename); - cfg->vk_vol_up = GetPrivateProfileIntW(L"io4", L"volup", 0x26, filename); - cfg->vk_vol_down = GetPrivateProfileIntW(L"io4", L"voldown", 0x28, filename); + cfg->vk_test = GetPrivateProfileIntW(L"io4", L"test", VK_F1, filename); + cfg->vk_service = GetPrivateProfileIntW(L"io4", L"service", VK_F2, filename); + cfg->vk_coin = GetPrivateProfileIntW(L"io4", L"coin", VK_F3, filename); + cfg->vk_vol_up = GetPrivateProfileIntW(L"io4", L"volup", VK_UP, filename); + cfg->vk_vol_down = GetPrivateProfileIntW(L"io4", L"voldown", VK_DOWN, filename); for (i = 0 ; i < 240 ; i++) { swprintf_s(key, _countof(key), L"cell%i", i + 1); diff --git a/mercuryio/config.h b/games/mercuryio/config.h similarity index 100% rename from mercuryio/config.h rename to games/mercuryio/config.h diff --git a/mercuryio/mercuryio.c b/games/mercuryio/mercuryio.c similarity index 95% rename from mercuryio/mercuryio.c rename to games/mercuryio/mercuryio.c index 6d38ff1..880349c 100644 --- a/mercuryio/mercuryio.c +++ b/games/mercuryio/mercuryio.c @@ -2,11 +2,13 @@ #include #include +#include #include #include "mercuryio/mercuryio.h" #include "mercuryio/config.h" #include "mercuryhook/elisabeth.h" +#include "util/env.h" static unsigned int __stdcall mercury_io_touch_thread_proc(void *ctx); @@ -23,7 +25,7 @@ uint16_t mercury_io_get_api_version(void) HRESULT mercury_io_init(void) { - mercury_io_config_load(&mercury_io_cfg, L".\\segatools.ini"); + mercury_io_config_load(&mercury_io_cfg, get_config_path()); return S_OK; } diff --git a/mercuryio/mercuryio.def b/games/mercuryio/mercuryio.def similarity index 100% rename from mercuryio/mercuryio.def rename to games/mercuryio/mercuryio.def diff --git a/mercuryio/mercuryio.h b/games/mercuryio/mercuryio.h similarity index 100% rename from mercuryio/mercuryio.h rename to games/mercuryio/mercuryio.h diff --git a/mercuryio/meson.build b/games/mercuryio/meson.build similarity index 88% rename from mercuryio/meson.build rename to games/mercuryio/meson.build index 2970fa9..aa628fb 100644 --- a/mercuryio/meson.build +++ b/games/mercuryio/meson.build @@ -3,7 +3,6 @@ mercuryio_lib = static_library( name_prefix : '', include_directories : inc, implicit_include_directories : false, - c_pch : '../precompiled.h', sources : [ 'mercuryio.c', 'mercuryio.h', diff --git a/games/mu3hook/config.c b/games/mu3hook/config.c new file mode 100644 index 0000000..6c52446 --- /dev/null +++ b/games/mu3hook/config.c @@ -0,0 +1,109 @@ +#include +#include +#include + +#include "board/config.h" + +#include "gfxhook/config.h" + +#include "hooklib/config.h" +#include "hooklib/dvd.h" + +#include "mu3hook/config.h" + +#include "platform/config.h" + +void mu3_dll_config_load( + struct mu3_dll_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + GetPrivateProfileStringW( + L"mu3io", + L"path", + L"", + cfg->path, + _countof(cfg->path), + filename); +} + +void led15093_config_load(struct led15093_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + wchar_t tmpstr[16]; + + memset(cfg->board_number, ' ', sizeof(cfg->board_number)); + memset(cfg->chip_number, ' ', sizeof(cfg->chip_number)); + memset(cfg->boot_chip_number, ' ', sizeof(cfg->boot_chip_number)); + + cfg->enable = GetPrivateProfileIntW(L"led15093", L"enable", 1, filename); + cfg->port_no[0] = GetPrivateProfileIntW(L"led15093", L"portNo1", 0, filename); + cfg->port_no[1] = GetPrivateProfileIntW(L"led15093", L"portNo2", 0, filename); + cfg->high_baudrate = GetPrivateProfileIntW(L"led15093", L"highBaud", 0, filename); + cfg->fw_ver = GetPrivateProfileIntW(L"led15093", L"fwVer", 0xA0, filename); + cfg->fw_sum = GetPrivateProfileIntW(L"led15093", L"fwSum", 0xAA53, filename); + + GetPrivateProfileStringW( + L"led15093", + L"boardNumber", + L"15093-06", + tmpstr, + _countof(tmpstr), + filename); + + size_t n = wcstombs(cfg->board_number, tmpstr, sizeof(cfg->board_number)); + for (int i = n; i < sizeof(cfg->board_number); i++) + { + cfg->board_number[i] = ' '; + } + + GetPrivateProfileStringW( + L"led15093", + L"chipNumber", + L"6710A", + tmpstr, + _countof(tmpstr), + filename); + + n = wcstombs(cfg->chip_number, tmpstr, sizeof(cfg->chip_number)); + for (int i = n; i < sizeof(cfg->chip_number); i++) + { + cfg->chip_number[i] = ' '; + } + + GetPrivateProfileStringW( + L"led15093", + L"bootChipNumber", + L"6709 ", + tmpstr, + _countof(tmpstr), + filename); + + n = wcstombs(cfg->boot_chip_number, tmpstr, sizeof(cfg->boot_chip_number)); + for (int i = n; i < sizeof(cfg->boot_chip_number); i++) + { + cfg->boot_chip_number[i] = ' '; + } +} + +void mu3_hook_config_load( + struct mu3_hook_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + platform_config_load(&cfg->platform, filename); + aime_config_load(&cfg->aime, filename); + dvd_config_load(&cfg->dvd, filename); + io4_config_load(&cfg->io4, filename); + gfx_config_load(&cfg->gfx, filename); + led15093_config_load(&cfg->led15093, filename); + vfd_config_load(&cfg->vfd, filename); + mu3_dll_config_load(&cfg->dll, filename); + unity_config_load(&cfg->unity, filename); +} diff --git a/mu3hook/config.h b/games/mu3hook/config.h similarity index 78% rename from mu3hook/config.h rename to games/mu3hook/config.h index 58af239..b2094c2 100644 --- a/mu3hook/config.h +++ b/games/mu3hook/config.h @@ -3,6 +3,7 @@ #include #include "board/config.h" +#include "board/led15093.h" #include "gfxhook/gfx.h" @@ -12,13 +13,18 @@ #include "platform/config.h" +#include "unityhook/config.h" + struct mu3_hook_config { struct platform_config platform; struct aime_config aime; struct dvd_config dvd; struct io4_config io4; struct gfx_config gfx; + struct led15093_config led15093; + struct vfd_config vfd; struct mu3_dll_config dll; + struct unity_config unity; }; void mu3_dll_config_load( diff --git a/mu3hook/dllmain.c b/games/mu3hook/dllmain.c similarity index 66% rename from mu3hook/dllmain.c rename to games/mu3hook/dllmain.c index dd01104..1b57e5d 100644 --- a/mu3hook/dllmain.c +++ b/games/mu3hook/dllmain.c @@ -1,8 +1,19 @@ +/* + "O.N.G.E.K.I." (mu3) hook + + Devices + + USB: 837-15257-01 "Type 4" I/O Board + USB: 3 * 601-13216 USB "QR Code" Camera (SDDT1-SDDT3) + COM1: 837-15396 "Gen 3" Aime Reader + COM2: 200-6275 VFD GP1232A02A FUTABA Board + COM3: 837-15093-06 LED Controller Board +*/ + #include #include -#include "board/io4.h" #include "board/sg-reader.h" #include "board/vfd.h" @@ -20,11 +31,14 @@ #include "mu3hook/config.h" #include "mu3hook/io4.h" #include "mu3hook/mu3-dll.h" -#include "mu3hook/unity.h" #include "platform/platform.h" +#include "unityhook/config.h" +#include "unityhook/hook.h" + #include "util/dprintf.h" +#include "util/env.h" static HMODULE mu3_hook_mod; static process_entry_t mu3_startup; @@ -38,7 +52,7 @@ static DWORD CALLBACK mu3_pre_startup(void) /* Load config */ - mu3_hook_config_load(&mu3_hook_cfg, L".\\segatools.ini"); + mu3_hook_config_load(&mu3_hook_cfg, get_config_path()); /* Hook Win32 APIs */ @@ -51,6 +65,14 @@ static DWORD CALLBACK mu3_pre_startup(void) /* Initialize emulation hooks */ + struct dipsw_config new_dipsw_config[8] = { + {L"Delivery Server", L"Server", L"Client"}, + }; + + // Set the system dip switch configuration + memcpy(mu3_hook_cfg.platform.system.dipsw_config, new_dipsw_config, + sizeof(new_dipsw_config)); + hr = platform_hook_init( &mu3_hook_cfg.platform, "SDDT", @@ -61,24 +83,32 @@ static DWORD CALLBACK mu3_pre_startup(void) goto fail; } - hr = sg_reader_hook_init(&mu3_hook_cfg.aime, 1, mu3_hook_mod); - - if (FAILED(hr)) { - goto fail; - } - - hr = vfd_hook_init(2); - - if (FAILED(hr)) { - goto fail; - } - hr = mu3_dll_init(&mu3_hook_cfg.dll, mu3_hook_mod); if (FAILED(hr)) { goto fail; } + unsigned int led_port_no[2] = {3, 0}; + hr = led15093_hook_init(&mu3_hook_cfg.led15093, + mu3_dll.led_init, mu3_dll.led_set_leds, led_port_no); + + if (FAILED(hr)) { + return hr; + } + + hr = sg_reader_hook_init(&mu3_hook_cfg.aime, 1, 1, mu3_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = vfd_hook_init(&mu3_hook_cfg.vfd, 2); + + if (FAILED(hr)) { + goto fail; + } + hr = mu3_io4_hook_init(&mu3_hook_cfg.io4); if (FAILED(hr)) { @@ -90,11 +120,11 @@ static DWORD CALLBACK mu3_pre_startup(void) There seems to be an issue with other DLL hooks if `LoadLibraryW` is hooked earlier in the `mu3hook` initialization. */ - unity_hook_init(); + unity_hook_init(&mu3_hook_cfg.unity, mu3_hook_mod, NULL); /* Initialize debug helpers */ - spike_hook_init(L".\\segatools.ini"); + spike_hook_init(get_config_path()); dprintf("--- End mu3_pre_startup ---\n"); diff --git a/mu3hook/io4.c b/games/mu3hook/io4.c similarity index 53% rename from mu3hook/io4.c rename to games/mu3hook/io4.c index 7edcb0c..dfa7ad8 100644 --- a/mu3hook/io4.c +++ b/games/mu3hook/io4.c @@ -11,9 +11,13 @@ #include "util/dprintf.h" static HRESULT mu3_io4_poll(void *ctx, struct io4_state *state); +static HRESULT mu3_io4_write_gpio(uint8_t* payload, size_t len); + +static uint16_t coins; static const struct io4_ops mu3_io4_ops = { .poll = mu3_io4_poll, + .write_gpio = mu3_io4_write_gpio, }; HRESULT mu3_io4_hook_init(const struct io4_config *cfg) @@ -69,6 +73,11 @@ static HRESULT mu3_io4_poll(void *ctx, struct io4_state *state) state->buttons[0] |= IO4_BUTTON_SERVICE; } + if (opbtn & MU3_IO_OPBTN_COIN) { + coins++; + } + state->chutes[0] = coins << 8; + if (left & MU3_IO_GAMEBTN_1) { state->buttons[0] |= 1 << 0; } @@ -118,3 +127,46 @@ static HRESULT mu3_io4_poll(void *ctx, struct io4_state *state) return S_OK; } + +static HRESULT mu3_io4_write_gpio(uint8_t* payload, size_t len) +{ + // Just fast fail if there aren't enough bytes in the payload + if (len < 3) + return S_OK; + + // This command is used for lights in Ongeki, but it only contains button lights, + // and only in the first 3 bytes of the payload; everything else is padding to + // make the payload 62 bytes. The rest of the cabinet lights and the side button + // lights are handled separately, by the 15093 lights controller. + uint32_t lights_data = (uint32_t) ((uint8_t)(payload[0]) << 24 | + (uint8_t)(payload[1]) << 16 | + (uint8_t)(payload[2]) << 8); + + // Since Sega uses an odd ordering for the first part of the bitfield, + // let's normalize the data and just send over bytes for the receiver + // to interpret as RGB values. + uint8_t rgb_out[6 * 3] = { + lights_data & MU3_IO_LED_L1_R ? 0xFF : 0x00, + lights_data & MU3_IO_LED_L1_G ? 0xFF : 0x00, + lights_data & MU3_IO_LED_L1_B ? 0xFF : 0x00, + lights_data & MU3_IO_LED_L2_R ? 0xFF : 0x00, + lights_data & MU3_IO_LED_L2_G ? 0xFF : 0x00, + lights_data & MU3_IO_LED_L2_B ? 0xFF : 0x00, + lights_data & MU3_IO_LED_L3_R ? 0xFF : 0x00, + lights_data & MU3_IO_LED_L3_G ? 0xFF : 0x00, + lights_data & MU3_IO_LED_L3_B ? 0xFF : 0x00, + lights_data & MU3_IO_LED_R1_R ? 0xFF : 0x00, + lights_data & MU3_IO_LED_R1_G ? 0xFF : 0x00, + lights_data & MU3_IO_LED_R1_B ? 0xFF : 0x00, + lights_data & MU3_IO_LED_R2_R ? 0xFF : 0x00, + lights_data & MU3_IO_LED_R2_G ? 0xFF : 0x00, + lights_data & MU3_IO_LED_R2_B ? 0xFF : 0x00, + lights_data & MU3_IO_LED_R3_R ? 0xFF : 0x00, + lights_data & MU3_IO_LED_R3_G ? 0xFF : 0x00, + lights_data & MU3_IO_LED_R3_B ? 0xFF : 0x00, + }; + + mu3_dll.led_set_leds(1, rgb_out); + + return S_OK; +} diff --git a/mu3hook/io4.h b/games/mu3hook/io4.h similarity index 100% rename from mu3hook/io4.h rename to games/mu3hook/io4.h diff --git a/mu3hook/meson.build b/games/mu3hook/meson.build similarity index 89% rename from mu3hook/meson.build rename to games/mu3hook/meson.build index 27ba7f7..9f1bfcf 100644 --- a/mu3hook/meson.build +++ b/games/mu3hook/meson.build @@ -4,7 +4,6 @@ shared_library( include_directories : inc, implicit_include_directories : false, vs_module_defs : 'mu3hook.def', - c_pch : '../precompiled.h', dependencies : [ capnhook.get_variable('hook_dep'), capnhook.get_variable('hooklib_dep'), @@ -17,6 +16,7 @@ shared_library( hooklib_lib, mu3io_lib, platform_lib, + unityhook_lib, util_lib, ], sources : [ @@ -27,7 +27,5 @@ shared_library( 'io4.h', 'mu3-dll.c', 'mu3-dll.h', - 'unity.h', - 'unity.c', ], ) diff --git a/mu3hook/mu3-dll.c b/games/mu3hook/mu3-dll.c similarity index 70% rename from mu3hook/mu3-dll.c rename to games/mu3hook/mu3-dll.c index 9e8e93e..6acf929 100644 --- a/mu3hook/mu3-dll.c +++ b/games/mu3hook/mu3-dll.c @@ -24,9 +24,28 @@ const struct dll_bind_sym mu3_dll_syms[] = { }, { .sym = "mu3_io_get_lever", .off = offsetof(struct mu3_dll, get_lever), + }, { + .sym = "mu3_io_led_init", + .off = offsetof(struct mu3_dll, led_init), + }, { + .sym = "mu3_io_led_set_colors", + .off = offsetof(struct mu3_dll, led_set_leds), } }; +/* Helper function to determine upon dll_bind failure whether the required functions were found + NOTE: relies on symbols order declared above */ +static HRESULT has_enough_symbols(uint16_t version, uint8_t count) +{ + if ( version <= 0x0100 && count == 5 ) + return S_OK; + + if ( version >= 0x0101 && count == 7 ) + return S_OK; + + return E_FAIL; +} + struct mu3_dll mu3_dll; // Copypasta DLL binding and diagnostic message boilerplate. @@ -86,16 +105,25 @@ HRESULT mu3_dll_init(const struct mu3_dll_config *cfg, HINSTANCE self) } sym = mu3_dll_syms; + const struct dll_bind_sym *init_sym = &sym[0]; + hr = dll_bind(&mu3_dll, src, &sym, _countof(mu3_dll_syms)); if (FAILED(hr)) { if (src != self) { - dprintf("Ongeki IO: Custom IO DLL does not provide function " - "\"%s\". Please contact your IO DLL's developer for " - "further assistance.\n", - sym->sym); + // Might still be ok depending on external dll API version + int bind_count = sym - init_sym; + if (has_enough_symbols(mu3_dll.api_version, bind_count) == S_OK) + { + hr = S_OK; + } else { + dprintf("Ongeki IO: Custom IO DLL does not provide function " + "\"%s\". Please contact your IO DLL's developer for " + "further assistance.\n", + sym->sym); - goto end; + goto end; + } } else { dprintf("Internal error: could not reflect \"%s\"\n", sym->sym); } diff --git a/mu3hook/mu3-dll.h b/games/mu3hook/mu3-dll.h similarity index 84% rename from mu3hook/mu3-dll.h rename to games/mu3hook/mu3-dll.h index 41f280f..2a919ea 100644 --- a/mu3hook/mu3-dll.h +++ b/games/mu3hook/mu3-dll.h @@ -11,6 +11,8 @@ struct mu3_dll { void (*get_opbtns)(uint8_t *opbtn); void (*get_gamebtns)(uint8_t *left, uint8_t *right); void (*get_lever)(int16_t *pos); + HRESULT (*led_init)(void); + void (*led_set_leds)(uint8_t board, uint8_t *rgb); }; struct mu3_dll_config { diff --git a/mu3hook/mu3hook.def b/games/mu3hook/mu3hook.def similarity index 92% rename from mu3hook/mu3hook.def rename to games/mu3hook/mu3hook.def index d90abd5..0393a2e 100644 --- a/mu3hook/mu3hook.def +++ b/games/mu3hook/mu3hook.def @@ -23,3 +23,5 @@ EXPORTS mu3_io_get_opbtns mu3_io_init mu3_io_poll + mu3_io_led_init + mu3_io_led_set_colors diff --git a/games/mu3io/config.c b/games/mu3io/config.c new file mode 100644 index 0000000..9dbab48 --- /dev/null +++ b/games/mu3io/config.c @@ -0,0 +1,57 @@ +#include + +#include +#include +#include +#include + +#include "mu3io/config.h" + + +void mu3_io_config_load( + struct mu3_io_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + wchar_t output_path_input[6]; + + cfg->vk_test = GetPrivateProfileIntW(L"io4", L"test", VK_F1, filename); + cfg->vk_service = GetPrivateProfileIntW(L"io4", L"service", VK_F2, filename); + cfg->vk_coin = GetPrivateProfileIntW(L"io4", L"coin", VK_F3, filename); + + cfg->use_mouse = GetPrivateProfileIntW(L"io4", L"mouse", 0, filename); + + cfg->vk_left_1 = GetPrivateProfileIntW(L"io4", L"left1", 'A', filename); + cfg->vk_left_2 = GetPrivateProfileIntW(L"io4", L"left2", 'S', filename); + cfg->vk_left_3 = GetPrivateProfileIntW(L"io4", L"left3", 'D', filename); + cfg->vk_left_side = GetPrivateProfileIntW(L"io4", L"leftSide", 'Q', filename); + cfg->vk_right_side = GetPrivateProfileIntW(L"io4", L"rightSide", 'E', filename); + cfg->vk_right_1 = GetPrivateProfileIntW(L"io4", L"right1", 'J', filename); + cfg->vk_right_2 = GetPrivateProfileIntW(L"io4", L"right2", 'K', filename); + cfg->vk_right_3 = GetPrivateProfileIntW(L"io4", L"right3", 'L', filename); + cfg->vk_left_menu = GetPrivateProfileIntW(L"io4", L"leftMenu", 'U', filename); + cfg->vk_right_menu = GetPrivateProfileIntW(L"io4", L"rightMenu", 'O', filename); + + cfg->cab_led_output_pipe = GetPrivateProfileIntW(L"led", L"cabLedOutputPipe", 1, filename); + cfg->cab_led_output_serial = GetPrivateProfileIntW(L"led", L"cabLedOutputSerial", 0, filename); + + cfg->controller_led_output_pipe = GetPrivateProfileIntW(L"led", L"controllerLedOutputPipe", 1, filename); + cfg->controller_led_output_serial = GetPrivateProfileIntW(L"led", L"controllerLedOutputSerial", 0, filename); + + cfg->led_serial_baud = GetPrivateProfileIntW(L"led", L"serialBaud", 921600, filename); + + GetPrivateProfileStringW( + L"led", + L"serialPort", + L"COM5", + output_path_input, + _countof(output_path_input), + filename); + + // Sanitize the output path. If it's a serial COM port, it needs to be prefixed + // with `\\.\`. + wcsncpy(cfg->led_serial_port, L"\\\\.\\", 4); + wcsncat_s(cfg->led_serial_port, MAX_PATH, output_path_input, MAX_PATH); +} diff --git a/games/mu3io/config.h b/games/mu3io/config.h new file mode 100644 index 0000000..328a460 --- /dev/null +++ b/games/mu3io/config.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +#include + +struct mu3_io_config { + uint8_t vk_test; + uint8_t vk_service; + uint8_t vk_coin; + + bool use_mouse; + + uint8_t vk_left_1; + uint8_t vk_left_2; + uint8_t vk_left_3; + uint8_t vk_left_side; + uint8_t vk_right_side; + uint8_t vk_right_1; + uint8_t vk_right_2; + uint8_t vk_right_3; + uint8_t vk_left_menu; + uint8_t vk_right_menu; + + // Which ways to output LED information are enabled + bool cab_led_output_pipe; + bool cab_led_output_serial; + + bool controller_led_output_pipe; + bool controller_led_output_serial; + + // The name of a COM port to output LED data on, in serial mode + wchar_t led_serial_port[12]; + int32_t led_serial_baud; +}; + +void mu3_io_config_load( + struct mu3_io_config *cfg, + const wchar_t *filename); diff --git a/games/mu3io/leddata.h b/games/mu3io/leddata.h new file mode 100644 index 0000000..84bc8b7 --- /dev/null +++ b/games/mu3io/leddata.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +#include + +#define LED_PACKET_FRAMING 0xE0 +#define LED_PACKET_ESCAPE 0xD0 +#define LED_NUM_MAX 66 +#define LED_BOARDS_TOTAL 2 +#define LED_OUTPUT_HEADER_SIZE 2 +#define LED_OUTPUT_DATA_SIZE_MAX LED_NUM_MAX * 3 * 2 // max if every byte's escaped +#define LED_OUTPUT_TOTAL_SIZE_MAX LED_OUTPUT_HEADER_SIZE + LED_OUTPUT_DATA_SIZE_MAX + +// This struct is used to send data related to the button and cab LEDs +struct _ongeki_led_data_buf_t { + uint8_t framing; // Sync byte + uint8_t board; // LED output the data is for (0: cab, 1: control deck) + uint8_t data[LED_OUTPUT_DATA_SIZE_MAX]; // Buffer for LEDs + uint8_t data_len; // How many bytes to output from the buffer +}; + +static uint8_t ongeki_led_board_data_lens[LED_BOARDS_TOTAL] = {9*3, 6*3}; diff --git a/games/mu3io/ledoutput.c b/games/mu3io/ledoutput.c new file mode 100644 index 0000000..83cdc26 --- /dev/null +++ b/games/mu3io/ledoutput.c @@ -0,0 +1,130 @@ +#include + +#include +#include +#include + +#include "mu3io/config.h" +#include "mu3io/leddata.h" +#include "mu3io/ledoutput.h" +#include "mu3io/pipeimpl.h" +#include "mu3io/serialimpl.h" + +static struct _ongeki_led_data_buf_t mu3_led_unescaped_buf[LED_BOARDS_TOTAL]; +static struct _ongeki_led_data_buf_t mu3_led_escaped_buf[LED_BOARDS_TOTAL]; + +static bool mu3_led_output_is_init = false; +static struct mu3_io_config* mu3_io_config; +static bool mu3_led_any_outputs_enabled; + +HANDLE mu3_led_init_mutex; + +HRESULT mu3_led_output_init(struct mu3_io_config* const cfg) +{ + DWORD dwWaitResult = WaitForSingleObject(mu3_led_init_mutex, INFINITE); + if (dwWaitResult == WAIT_FAILED) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + else if (dwWaitResult != WAIT_OBJECT_0) + { + return E_FAIL; + } + + if (!mu3_led_output_is_init) + { + mu3_io_config = cfg; + + // Setup the framing bytes for the packets + for (int i = 0; i < LED_BOARDS_TOTAL; i++) { + mu3_led_unescaped_buf[i].framing = LED_PACKET_FRAMING; + mu3_led_unescaped_buf[i].board = i; + mu3_led_unescaped_buf[i].data_len = ongeki_led_board_data_lens[i]; + + mu3_led_escaped_buf[i].framing = LED_PACKET_FRAMING; + mu3_led_escaped_buf[i].board = i; + mu3_led_escaped_buf[i].data_len = ongeki_led_board_data_lens[i]; + } + + mu3_led_any_outputs_enabled = mu3_io_config->cab_led_output_pipe || mu3_io_config->controller_led_output_pipe + || mu3_io_config->cab_led_output_serial || mu3_io_config->controller_led_output_serial; + + if (mu3_io_config->cab_led_output_pipe || mu3_io_config->controller_led_output_pipe) + { + mu3_led_pipe_init(); // don't really care about errors here tbh + } + + if (mu3_io_config->cab_led_output_serial || mu3_io_config->controller_led_output_serial) + { + mu3_led_serial_init(mu3_io_config->led_serial_port, mu3_io_config->led_serial_baud); + } + } + + mu3_led_output_is_init = true; + + ReleaseMutex(mu3_led_init_mutex); + return S_OK; +} + +struct _ongeki_led_data_buf_t* escape_led_data(struct _ongeki_led_data_buf_t* unescaped) +{ + struct _ongeki_led_data_buf_t* out_struct = &mu3_led_escaped_buf[unescaped->board]; + + uint8_t* in_buf = unescaped->data; + uint8_t* out_buf = out_struct->data; + int i = 0; + int o = 0; + + while (i < unescaped->data_len) + { + uint8_t b = in_buf[i++]; + if (b == LED_PACKET_FRAMING || b == LED_PACKET_ESCAPE) + { + out_buf[o++] = LED_PACKET_ESCAPE; + b--; + } + out_buf[o++] = b; + } + + out_struct->data_len = o; + + return out_struct; +} + +void mu3_led_output_update(int board, const uint8_t* rgb) +{ + if (board < 0 || board > 1 || !mu3_led_any_outputs_enabled) + { + return; + } + + memcpy(mu3_led_unescaped_buf[board].data, rgb, mu3_led_unescaped_buf[board].data_len); + struct _ongeki_led_data_buf_t* escaped_data = escape_led_data(&mu3_led_unescaped_buf[board]); + + if (board == 0) + { + // cab + if (mu3_io_config->cab_led_output_pipe) + { + mu3_led_pipe_update(escaped_data); + } + + if (mu3_io_config->cab_led_output_serial) + { + mu3_led_serial_update(escaped_data); + } + } + else + { + // slider + if (mu3_io_config->controller_led_output_pipe) + { + mu3_led_pipe_update(escaped_data); + } + + if (mu3_io_config->controller_led_output_serial) + { + mu3_led_serial_update(escaped_data); + } + } +} diff --git a/games/mu3io/ledoutput.h b/games/mu3io/ledoutput.h new file mode 100644 index 0000000..ab30814 --- /dev/null +++ b/games/mu3io/ledoutput.h @@ -0,0 +1,20 @@ +/* + LED output functions + + Credits: + somewhatlurker, skogaby +*/ + +#pragma once + +#include + +#include +#include + +#include "mu3io/config.h" + +extern HANDLE mu3_led_init_mutex; + +HRESULT mu3_led_output_init(struct mu3_io_config* const cfg); +void mu3_led_output_update(int board, const uint8_t* rgb); diff --git a/mu3io/meson.build b/games/mu3io/meson.build similarity index 55% rename from mu3io/meson.build rename to games/mu3io/meson.build index 3d6e60e..67df7ca 100644 --- a/mu3io/meson.build +++ b/games/mu3io/meson.build @@ -3,12 +3,20 @@ mu3io_lib = static_library( name_prefix : '', include_directories : inc, implicit_include_directories : false, - c_pch : '../precompiled.h', dependencies : [ xinput_lib, ], sources : [ + 'config.c', + 'config.h', + 'leddata.h', + 'ledoutput.c', + 'ledoutput.h', 'mu3io.c', 'mu3io.h', + 'pipeimpl.c', + 'pipeimpl.h', + 'serialimpl.c', + 'serialimpl.h' ], ) diff --git a/games/mu3io/mu3io.c b/games/mu3io/mu3io.c new file mode 100644 index 0000000..e5618d9 --- /dev/null +++ b/games/mu3io/mu3io.c @@ -0,0 +1,221 @@ +#include +#include + +#include +#include +#include + +#include "mu3io/mu3io.h" +#include "mu3io/config.h" +#include "mu3io/ledoutput.h" + +#include "util/dprintf.h" +#include "util/env.h" + +static uint8_t mu3_opbtn; +static uint8_t mu3_left_btn; +static uint8_t mu3_right_btn; +static int16_t mu3_lever_pos; +static int16_t mu3_lever_xpos; +static struct mu3_io_config mu3_io_cfg; +static bool mu3_io_coin; + +// Mouse control factor to adjust the speed of mouse movement +const double MOUSE_SENSITIVITY = 0.5; + +uint16_t mu3_io_get_api_version(void) +{ + return 0x0101; +} + +HRESULT mu3_io_init(void) +{ + mu3_io_config_load(&mu3_io_cfg, get_config_path()); + + dprintf("XInput: --- Begin configuration ---\n"); + dprintf("XInput: Mouse lever emulation : %i\n", mu3_io_cfg.use_mouse); + dprintf("XInput: --- End configuration ---\n"); + + mu3_led_init_mutex = CreateMutex( + NULL, // default security attributes + FALSE, // initially not owned + NULL); // unnamed mutex + + if (mu3_led_init_mutex == NULL) + { + return E_FAIL; + } + + return mu3_led_output_init(&mu3_io_cfg); +} + +HRESULT mu3_io_poll(void) +{ + int lever; + int xlever; + XINPUT_STATE xi; + WORD xb; + + mu3_opbtn = 0; + mu3_left_btn = 0; + mu3_right_btn = 0; + + if (GetAsyncKeyState(mu3_io_cfg.vk_test) & 0x8000) { + mu3_opbtn |= MU3_IO_OPBTN_TEST; + } + + if (GetAsyncKeyState(mu3_io_cfg.vk_service) & 0x8000) { + mu3_opbtn |= MU3_IO_OPBTN_SERVICE; + } + + if (GetAsyncKeyState(mu3_io_cfg.vk_coin) & 0x8000) { + if (!mu3_io_coin) { + mu3_io_coin = true; + mu3_opbtn |= MU3_IO_OPBTN_COIN; + } + } else { + mu3_io_coin = false; + } + + memset(&xi, 0, sizeof(xi)); + XInputGetState(0, &xi); + xb = xi.Gamepad.wButtons; + + if (GetAsyncKeyState(mu3_io_cfg.vk_left_1) || (xb & XINPUT_GAMEPAD_DPAD_LEFT)) { + mu3_left_btn |= MU3_IO_GAMEBTN_1; + } + + if (GetAsyncKeyState(mu3_io_cfg.vk_left_2) || (xb & XINPUT_GAMEPAD_DPAD_UP)) { + mu3_left_btn |= MU3_IO_GAMEBTN_2; + } + + if (GetAsyncKeyState(mu3_io_cfg.vk_left_3) || (xb & XINPUT_GAMEPAD_DPAD_RIGHT)) { + mu3_left_btn |= MU3_IO_GAMEBTN_3; + } + + if (GetAsyncKeyState(mu3_io_cfg.vk_right_1) || (xb & XINPUT_GAMEPAD_X)) { + mu3_right_btn |= MU3_IO_GAMEBTN_1; + } + + if (GetAsyncKeyState(mu3_io_cfg.vk_right_2) || (xb & XINPUT_GAMEPAD_Y)) { + mu3_right_btn |= MU3_IO_GAMEBTN_2; + } + + if (GetAsyncKeyState(mu3_io_cfg.vk_right_3) || (xb & XINPUT_GAMEPAD_B)) { + mu3_right_btn |= MU3_IO_GAMEBTN_3; + } + + if (GetAsyncKeyState(mu3_io_cfg.vk_left_menu) || (xb & XINPUT_GAMEPAD_BACK)) { + mu3_left_btn |= MU3_IO_GAMEBTN_MENU; + } + + if (GetAsyncKeyState(mu3_io_cfg.vk_right_menu) || (xb & XINPUT_GAMEPAD_START)) { + mu3_right_btn |= MU3_IO_GAMEBTN_MENU; + } + + if (GetAsyncKeyState(mu3_io_cfg.vk_left_side) || (xb & XINPUT_GAMEPAD_LEFT_SHOULDER)) { + mu3_left_btn |= MU3_IO_GAMEBTN_SIDE; + } + + if (GetAsyncKeyState(mu3_io_cfg.vk_right_side) || (xb & XINPUT_GAMEPAD_RIGHT_SHOULDER)) { + mu3_right_btn |= MU3_IO_GAMEBTN_SIDE; + } + + lever = mu3_lever_pos; + + if (mu3_io_cfg.use_mouse) { + // mouse movement + POINT mousePos; + GetCursorPos(&mousePos); + + // int mouseMovement = (int)(xi.Gamepad.sThumbLX * MOUSE_SENSITIVITY); + // int newXPos = mousePos.x + mouseMovement; + int mouse_x = mousePos.x; + + // clamp the mouse_x position to the screen width + int screenWidth = GetSystemMetrics(SM_CXSCREEN); + if (mouse_x < 0) { + mouse_x = 0; + } + else if (mouse_x > screenWidth) { + mouse_x = screenWidth; + } + + // normalize the mouse_x position from 0 to 1 + double mouse_x_norm = (double)mouse_x / screenWidth; + + // scale the mouse_x_norm to the range of INT16_MIN to INT16_MAX + mouse_x = (int)((mouse_x_norm * (INT16_MAX - INT16_MIN)) + INT16_MIN); + + lever = mouse_x; + } else { + if (abs(xi.Gamepad.sThumbLX) > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) { + lever += xi.Gamepad.sThumbLX / 24; + } + + if (abs(xi.Gamepad.sThumbRX) > XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) { + lever += xi.Gamepad.sThumbRX / 24; + } + } + + if (lever < INT16_MIN) { + lever = INT16_MIN; + } + + if (lever > INT16_MAX) { + lever = INT16_MAX; + } + + mu3_lever_pos = lever; + + xlever = mu3_lever_pos + - xi.Gamepad.bLeftTrigger * 64 + + xi.Gamepad.bRightTrigger * 64; + + if (xlever < INT16_MIN) { + xlever = INT16_MIN; + } + + if (xlever > INT16_MAX) { + xlever = INT16_MAX; + } + + mu3_lever_xpos = xlever; + + return S_OK; +} + +void mu3_io_get_opbtns(uint8_t *opbtn) +{ + if (opbtn != NULL) { + *opbtn = mu3_opbtn; + } +} + +void mu3_io_get_gamebtns(uint8_t *left, uint8_t *right) +{ + if (left != NULL) { + *left = mu3_left_btn; + } + + if (right != NULL ){ + *right = mu3_right_btn; + } +} + +void mu3_io_get_lever(int16_t *pos) +{ + if (pos != NULL) { + *pos = mu3_lever_xpos; + } +} + +HRESULT mu3_io_led_init(void) +{ + return S_OK; +} + +void mu3_io_led_set_colors(uint8_t board, uint8_t *rgb) +{ + mu3_led_output_update(board, rgb); +} diff --git a/mu3io/mu3io.h b/games/mu3io/mu3io.h similarity index 53% rename from mu3io/mu3io.h rename to games/mu3io/mu3io.h index d46a475..1fddac0 100644 --- a/mu3io/mu3io.h +++ b/games/mu3io/mu3io.h @@ -1,5 +1,15 @@ #pragma once +/* + MU3 CUSTOM IO API + + Changelog: + + - 0x0100: Initial API version (assumed if chuni_io_get_api_version is not + exported) + - 0x0101: Added mu3_io_led_init and mu3_io_set_leds +*/ + #include #include @@ -7,6 +17,7 @@ enum { MU3_IO_OPBTN_TEST = 0x01, MU3_IO_OPBTN_SERVICE = 0x02, + MU3_IO_OPBTN_COIN = 0x04, }; enum { @@ -17,6 +28,29 @@ enum { MU3_IO_GAMEBTN_MENU = 0x10, }; +enum { + /* These are the bitmasks to use when checking which + lights are triggered on incoming IO4 GPIO writes. */ + MU3_IO_LED_L1_R = 1 << 31, + MU3_IO_LED_L1_G = 1 << 28, + MU3_IO_LED_L1_B = 1 << 30, + MU3_IO_LED_L2_R = 1 << 27, + MU3_IO_LED_L2_G = 1 << 29, + MU3_IO_LED_L2_B = 1 << 26, + MU3_IO_LED_L3_R = 1 << 25, + MU3_IO_LED_L3_G = 1 << 24, + MU3_IO_LED_L3_B = 1 << 23, + MU3_IO_LED_R1_R = 1 << 22, + MU3_IO_LED_R1_G = 1 << 21, + MU3_IO_LED_R1_B = 1 << 20, + MU3_IO_LED_R2_R = 1 << 19, + MU3_IO_LED_R2_G = 1 << 18, + MU3_IO_LED_R2_B = 1 << 17, + MU3_IO_LED_R3_R = 1 << 16, + MU3_IO_LED_R3_G = 1 << 15, + MU3_IO_LED_R3_B = 1 << 14, +}; + /* Get the version of the Ongeki 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 @@ -82,3 +116,43 @@ void mu3_io_get_gamebtns(uint8_t *left, uint8_t *right); Minimum API version: 0x0100 */ void mu3_io_get_lever(int16_t *pos); + +/* Initialize LED emulation. This function will be called before any + other mu3_io_led_*() function calls. + + 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: 0x0101 */ + +HRESULT mu3_io_led_init(void); + +/* Update the RGB LEDs. rgb is a pointer to an array of up to 61 * 3 = 183 bytes. + + ONGEKI uses one board with WS2811 protocol (each logical led corresponds to 3 + physical leds). Board 0 is used for all cab lights and both WAD button lights. + + Board 0 has 61 LEDs: + [0]-[1]: left side button + [2]-[8]: left pillar lower LEDs + [9]-[17]: left pillar center LEDs + [18]-[24]: left pillar upper LEDs + [25]-[35]: billboard LEDs + [36]-[42]: right pillar upper LEDs + [43]-[51]: right pillar center LEDs + [52]-[58]: right pillar lower LEDs + [59]-[60]: right side button + + Board 1 has 6 LEDs: + [0]-[5]: 3 left and 3 right controller buttons + + Each rgb value is comprised of 3 bytes in R,G,B order. The tricky part is + that the board 0 is called from mu3 and the board 1 is called from amdaemon. + So the library must be able to handle both calls, using shared memory f.e. + This is up to the developer to decide how to handle this, recommended way is + to use the amdaemon process as the main one and the mu3 process as a sub one. + + Minimum API version: 0x0101 */ + +void mu3_io_led_set_colors(uint8_t board, uint8_t *rgb); diff --git a/games/mu3io/pipeimpl.c b/games/mu3io/pipeimpl.c new file mode 100644 index 0000000..7010601 --- /dev/null +++ b/games/mu3io/pipeimpl.c @@ -0,0 +1,160 @@ +#include + +#include +#include +#include + +#include "mu3io/leddata.h" +#include "mu3io/pipeimpl.h" + +static bool mu3_pipe_update[LED_BOARDS_TOTAL]; + +// incoming data is copied into these to ensure it isn't written during output +static struct _ongeki_led_data_buf_t mu3_pipe_write_buf[LED_BOARDS_TOTAL]; +static HANDLE mu3_pipe_write_mutex; + +static HRESULT mu3_pipe_create(LPHANDLE hPipe, LPCWSTR lpszPipename, DWORD dwBufSize) +{ + *hPipe = INVALID_HANDLE_VALUE; + + *hPipe = CreateNamedPipeW( + lpszPipename, // pipe name + PIPE_ACCESS_OUTBOUND, // read/write access + PIPE_TYPE_BYTE | // byte type pipe + PIPE_WAIT, // blocking mode + PIPE_UNLIMITED_INSTANCES, // max. instances + dwBufSize, // output buffer size + 0, // input buffer size + 0, // client time-out + NULL); // default security attribute + + if (*hPipe == INVALID_HANDLE_VALUE) + { + return E_FAIL; + } + + return S_OK; +} + +static HRESULT mu3_pipe_write(HANDLE hPipe, LPCVOID lpBuffer, DWORD dwSize) +{ + DWORD cbWritten = 0; + + bool fSuccess = WriteFile( + hPipe, + lpBuffer, + dwSize, + &cbWritten, + NULL); + + if (!fSuccess || cbWritten != dwSize) + { + DWORD last_err = GetLastError(); + return (last_err == ERROR_BROKEN_PIPE) ? E_ABORT : E_FAIL; + } + + return S_OK; +} + +static unsigned int __stdcall mu3_io_led_pipe_thread_proc(void *ctx) +{ + HANDLE hPipe; + LPCWSTR lpszPipename = L"\\\\.\\pipe\\ongeki_led"; + + while (true) + { + hPipe = INVALID_HANDLE_VALUE; + + if (mu3_pipe_create(&hPipe, lpszPipename, LED_OUTPUT_TOTAL_SIZE_MAX) != S_OK) + { + continue; + } + + // wait for a connection to the pipe + bool fConnected = ConnectNamedPipe(hPipe, NULL) ? + true : (GetLastError() == ERROR_PIPE_CONNECTED); + + while (fConnected) + { + if (WaitForSingleObject(mu3_pipe_write_mutex, INFINITE) != WAIT_OBJECT_0) + { + continue; + } + + for (int i = 0; i < LED_BOARDS_TOTAL; i++) { + if (mu3_pipe_update[i]) + { + HRESULT result = mu3_pipe_write( + hPipe, + &mu3_pipe_write_buf[i], + LED_OUTPUT_HEADER_SIZE + mu3_pipe_write_buf[i].data_len); + + if (result != S_OK) + { + //if (result == E_ABORT) + //{ + fConnected = false; + //} + break; + } + + mu3_pipe_update[i] = false; + } + } + + ReleaseMutex(mu3_pipe_write_mutex); + } + + FlushFileBuffers(hPipe); + DisconnectNamedPipe(hPipe); + CloseHandle(hPipe); + } + + return 0; +} + +HRESULT mu3_led_pipe_init() +{ + mu3_pipe_write_mutex = CreateMutex( + NULL, // default security attributes + FALSE, // initially not owned + NULL); // unnamed mutex + + if (mu3_pipe_write_mutex == NULL) + { + return E_FAIL; + } + + // clear out update bools + for (int i = 0; i < LED_BOARDS_TOTAL; i++) { + mu3_pipe_update[i] = false; + } + + _beginthreadex( + NULL, + 0, + mu3_io_led_pipe_thread_proc, + 0, + 0, + NULL); + + return S_OK; +} + +void mu3_led_pipe_update(struct _ongeki_led_data_buf_t* data) +{ + if (data->board > 1) + { + return; + } + + if (WaitForSingleObject(mu3_pipe_write_mutex, INFINITE) != WAIT_OBJECT_0) + { + return; + } + + memcpy(&mu3_pipe_write_buf[data->board], data, sizeof(struct _ongeki_led_data_buf_t)); + mu3_pipe_update[data->board] = true; + + ReleaseMutex(mu3_pipe_write_mutex); +} diff --git a/games/mu3io/pipeimpl.h b/games/mu3io/pipeimpl.h new file mode 100644 index 0000000..cb5162a --- /dev/null +++ b/games/mu3io/pipeimpl.h @@ -0,0 +1,15 @@ +/* + Pipe implementation for chuniio + + Credits: + somewhatlurker, skogaby +*/ + +#pragma once + +#include + +#include "mu3io/leddata.h" + +HRESULT mu3_led_pipe_init(); +void mu3_led_pipe_update(struct _ongeki_led_data_buf_t* data); diff --git a/games/mu3io/serialimpl.c b/games/mu3io/serialimpl.c new file mode 100644 index 0000000..6ad07a6 --- /dev/null +++ b/games/mu3io/serialimpl.c @@ -0,0 +1,88 @@ +#include + +#include +#include +#include + +#include "mu3io/leddata.h" +#include "mu3io/serialimpl.h" + +#include "util/dprintf.h" + +static HANDLE mu3_serial_port; + +HRESULT mu3_led_serial_init(wchar_t led_com[12], DWORD baud) +{ + // Setup the serial communications + BOOL status; + + mu3_serial_port = CreateFileW(led_com, + GENERIC_READ | GENERIC_WRITE, + 0, + NULL, + OPEN_EXISTING, + 0, + NULL); + + if (mu3_serial_port == INVALID_HANDLE_VALUE) + { + dprintf("Ongeki Serial LEDs: Failed to open COM port (attempted on %S)\n", led_com); + return E_FAIL; + } + + DCB dcb_serial_params = { 0 }; + dcb_serial_params.DCBlength = sizeof(dcb_serial_params); + status = GetCommState(mu3_serial_port, &dcb_serial_params); + + dcb_serial_params.BaudRate = baud; + dcb_serial_params.ByteSize = 8; + dcb_serial_params.StopBits = ONESTOPBIT; + dcb_serial_params.Parity = NOPARITY; + SetCommState(mu3_serial_port, &dcb_serial_params); + + COMMTIMEOUTS timeouts = { 0 }; + timeouts.ReadIntervalTimeout = 50; + timeouts.ReadTotalTimeoutConstant = 50; + timeouts.ReadTotalTimeoutMultiplier = 10; + timeouts.WriteTotalTimeoutConstant = 50; + timeouts.WriteTotalTimeoutMultiplier = 10; + + SetCommTimeouts(mu3_serial_port, &timeouts); + + if (!status) + { + return E_FAIL; + } + + return S_OK; +} + +void mu3_led_serial_update(struct _ongeki_led_data_buf_t* data) +{ + if (data->board > 1) + { + return; + } + + if (mu3_serial_port != INVALID_HANDLE_VALUE) + { + DWORD bytes_written = 0; + + BOOL status = WriteFile( + mu3_serial_port, + data, + LED_OUTPUT_HEADER_SIZE + data->data_len, + &bytes_written, + NULL); + + if (!status) + { + DWORD last_err = GetLastError(); + dprintf("Ongeki Serial LEDs: Serial port write failed -- %d\n", (int) last_err); + } + } + else + { + dprintf("Ongeki Serial LEDs: Invalid serial port handle\n"); + } +} diff --git a/games/mu3io/serialimpl.h b/games/mu3io/serialimpl.h new file mode 100644 index 0000000..151d042 --- /dev/null +++ b/games/mu3io/serialimpl.h @@ -0,0 +1,15 @@ +/* + Serial LED implementation for chuniio + + Credits: + somewhatlurker, skogaby +*/ + +#pragma once + +#include + +#include "mu3io/leddata.h" + +HRESULT mu3_led_serial_init(wchar_t led_com[12], DWORD baud); +void mu3_led_serial_update(struct _ongeki_led_data_buf_t* data); diff --git a/games/swdchook/config.c b/games/swdchook/config.c new file mode 100644 index 0000000..e64654e --- /dev/null +++ b/games/swdchook/config.c @@ -0,0 +1,95 @@ +#include +#include +#include + +#include "board/config.h" +#include "board/sg-reader.h" + +#include "hooklib/config.h" +#include "hooklib/dvd.h" + +#include "swdchook/config.h" +#include "swdchook/swdc-dll.h" + +#include "platform/config.h" +#include "platform/platform.h" + + +void led15070_config_load(struct led15070_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + wchar_t tmpstr[16]; + + cfg->enable = GetPrivateProfileIntW(L"led15070", L"enable", 1, filename); + cfg->port_no[0] = GetPrivateProfileIntW(L"led15070", L"portNo1", 0, filename); + cfg->port_no[1] = GetPrivateProfileIntW(L"led15070", L"portNo2", 0, filename); + cfg->fw_ver = GetPrivateProfileIntW(L"led15070", L"fwVer", 0x90, filename); + /* TODO: Unknown, no firmware file available */ + cfg->fw_sum = GetPrivateProfileIntW(L"led15070", L"fwSum", 0xdead, filename); + + GetPrivateProfileStringW( + L"led15070", + L"boardNumber", + L"15070-04", + tmpstr, + _countof(tmpstr), + filename); + + size_t n = wcstombs(cfg->board_number, tmpstr, sizeof(cfg->board_number)); + for (int i = n; i < sizeof(cfg->board_number); i++) + { + cfg->board_number[i] = ' '; + } + + GetPrivateProfileStringW( + L"led15070", + L"eepromPath", + L"DEVICE", + cfg->eeprom_path, + _countof(cfg->eeprom_path), + filename); +} + +void swdc_dll_config_load( + struct swdc_dll_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + GetPrivateProfileStringW( + L"swdcio", + L"path", + L"", + cfg->path, + _countof(cfg->path), + filename); +} + +void zinput_config_load(struct zinput_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"zinput", L"enable", 1, filename); +} + +void swdc_hook_config_load( + struct swdc_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); + swdc_dll_config_load(&cfg->dll, filename); + zinput_config_load(&cfg->zinput, filename); + dvd_config_load(&cfg->dvd, filename); + io4_config_load(&cfg->io4, filename); + ffb_config_load(&cfg->ffb, filename); + led15070_config_load(&cfg->led15070, filename); + vfd_config_load(&cfg->vfd, filename); +} diff --git a/games/swdchook/config.h b/games/swdchook/config.h new file mode 100644 index 0000000..976285a --- /dev/null +++ b/games/swdchook/config.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +#include "board/config.h" +#include "board/led15070.h" + +#include "hooklib/dvd.h" + +#include "swdchook/swdc-dll.h" +#include "swdchook/zinput.h" + +#include "platform/platform.h" + +struct swdc_hook_config { + struct platform_config platform; + struct aime_config aime; + struct dvd_config dvd; + struct io4_config io4; + struct ffb_config ffb; + struct vfd_config vfd; + struct led15070_config led15070; + struct swdc_dll_config dll; + struct zinput_config zinput; +}; + +void swdc_dll_config_load( + struct swdc_dll_config *cfg, + const wchar_t *filename); + +void swdc_hook_config_load( + struct swdc_hook_config *cfg, + const wchar_t *filename); + +void zinput_config_load(struct zinput_config *cfg, const wchar_t *filename); diff --git a/games/swdchook/dllmain.c b/games/swdchook/dllmain.c new file mode 100644 index 0000000..5932a69 --- /dev/null +++ b/games/swdchook/dllmain.c @@ -0,0 +1,146 @@ +/* + "SEGA World Drivers Championship" (swdc) hook + + Devices + + USB: 837-15257 "Type 4" I/O Board + USB: 838-15415 Indicator BD Main Board (COM21) + WITH + 838-15416 Indicator BD LED Board + COM1: 838-15069 MOTOR DRIVE BD RS232/422 board + COM2: 837-15070-04 IC BD LED controller board + COM3: 837-15396 "Gen 3" Aime reader + COM4: 200-6275 VFD GP1232A02A FUTABA board +*/ + +#include +#include + +#include + +#include "board/sg-reader.h" +#include "board/io4.h" +#include "board/vfd.h" + +#include "hook/process.h" + +#include "hooklib/dvd.h" +#include "hooklib/serial.h" +#include "hooklib/spike.h" + +#include "swdchook/config.h" +#include "swdchook/swdc-dll.h" +#include "swdchook/io4.h" +#include "swdchook/ffb.h" + +#include "platform/platform.h" + +#include "util/dprintf.h" +#include "util/env.h" + +static HMODULE swdc_hook_mod; +static process_entry_t swdc_startup; +static struct swdc_hook_config swdc_hook_cfg; + +static DWORD CALLBACK swdc_pre_startup(void) +{ + HRESULT hr; + + dprintf("--- Begin swdc_pre_startup ---\n"); + + /* Config load */ + + swdc_hook_config_load(&swdc_hook_cfg, get_config_path()); + + /* Hook Win32 APIs */ + + serial_hook_init(); + dvd_hook_init(&swdc_hook_cfg.dvd, swdc_hook_mod); + + /* Initialize emulation hooks */ + + hr = platform_hook_init( + &swdc_hook_cfg.platform, + "SDDS", + "ACA4", + swdc_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = swdc_dll_init(&swdc_hook_cfg.dll, swdc_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = sg_reader_hook_init(&swdc_hook_cfg.aime, 3, 3, swdc_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = vfd_hook_init(&swdc_hook_cfg.vfd, 4); + + if (FAILED(hr)) { + return hr; + } + + hr = swdc_io4_hook_init(&swdc_hook_cfg.io4); + + if (FAILED(hr)) { + goto fail; + } + + hr = swdc_ffb_hook_init(&swdc_hook_cfg.ffb, 1); + + if (FAILED(hr)) { + goto fail; + } + + /* Not working, different board -04 instead of -02? */ + unsigned int led_port_no[2] = {2, 0}; + hr = led15070_hook_init(&swdc_hook_cfg.led15070, swdc_dll.led_init, + swdc_dll.led_set_fet_output, NULL, swdc_dll.led_gs_update, led_port_no); + + if (FAILED(hr)) { + goto fail; + } + + /* Hook external DLL APIs */ + + zinput_hook_init(&swdc_hook_cfg.zinput); + + /* Initialize debug helpers */ + + spike_hook_init(get_config_path()); + + dprintf("--- End swdc_pre_startup ---\n"); + + /* Jump to EXE start address */ + + return swdc_startup(); + +fail: + ExitProcess(EXIT_FAILURE); +} + +BOOL WINAPI DllMain(HMODULE mod, DWORD cause, void *ctx) +{ + HRESULT hr; + + if (cause != DLL_PROCESS_ATTACH) { + return TRUE; + } + + swdc_hook_mod = mod; + + hr = process_hijack_startup(swdc_pre_startup, &swdc_startup); + + if (!SUCCEEDED(hr)) { + dprintf("Failed to hijack process startup: %x\n", (int) hr); + } + + return SUCCEEDED(hr); +} diff --git a/games/swdchook/ffb.c b/games/swdchook/ffb.c new file mode 100644 index 0000000..07c6dea --- /dev/null +++ b/games/swdchook/ffb.c @@ -0,0 +1,59 @@ +#include + +#include +#include +#include +#include + +#include "board/ffb.h" + +#include "swdchook/swdc-dll.h" + +#include "util/dprintf.h" + +static void swdc_ffb_toggle(bool active); +static void swdc_ffb_constant_force(uint8_t direction, uint8_t force); +static void swdc_ffb_rumble(uint8_t force, uint8_t period); +static void swdc_ffb_damper(uint8_t force); + +static const struct ffb_ops swdc_ffb_ops = { + .toggle = swdc_ffb_toggle, + .constant_force = swdc_ffb_constant_force, + .rumble = swdc_ffb_rumble, + .damper = swdc_ffb_damper +}; + +HRESULT swdc_ffb_hook_init(const struct ffb_config *cfg, unsigned int port_no) +{ + HRESULT hr; + + assert(swdc_dll.init != NULL); + + hr = ffb_hook_init(cfg, &swdc_ffb_ops, port_no); + + if (FAILED(hr)) { + return hr; + } + + return swdc_dll.ffb_init(); +} + +static void swdc_ffb_toggle(bool active) +{ + swdc_dll.ffb_toggle(active); +} + +static void swdc_ffb_constant_force(uint8_t direction, uint8_t force) +{ + swdc_dll.ffb_constant_force(direction, force); +} + +static void swdc_ffb_rumble(uint8_t force, uint8_t period) +{ + swdc_dll.ffb_rumble(force, period); +} + +static void swdc_ffb_damper(uint8_t force) +{ + swdc_dll.ffb_damper(force); +} diff --git a/games/swdchook/ffb.h b/games/swdchook/ffb.h new file mode 100644 index 0000000..53a47a4 --- /dev/null +++ b/games/swdchook/ffb.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "board/ffb.h" + +HRESULT swdc_ffb_hook_init(const struct ffb_config *cfg, unsigned int port_no); diff --git a/games/swdchook/io4.c b/games/swdchook/io4.c new file mode 100644 index 0000000..679a646 --- /dev/null +++ b/games/swdchook/io4.c @@ -0,0 +1,209 @@ +#include + +#include +#include +#include + +#include "board/io4.h" + +#include "util/dprintf.h" + +#include "swdchook/swdc-dll.h" + +static HANDLE mmf; +static HRESULT init_mmf(void); +static void swdc_set_gamebtns(uint16_t value); + +static HRESULT swdc_io4_poll(void *ctx, struct io4_state *state); +static HRESULT swdc_io4_write_gpio(uint8_t* payload, size_t len); +static uint16_t coins; + +static const struct io4_ops swdc_io4_ops = { + .poll = swdc_io4_poll, + .write_gpio = swdc_io4_write_gpio +}; + +HRESULT swdc_io4_hook_init(const struct io4_config *cfg) { + HRESULT hr; + + assert(swdc_dll.init != NULL); + + hr = io4_hook_init(cfg, &swdc_io4_ops, NULL); + + if (FAILED(hr)) { + return hr; + } + + hr = init_mmf(); + + if (FAILED(hr)) { + return hr; + } + + return swdc_dll.init(); +} + +// Function to initialize the memory-mapped file +static HRESULT init_mmf(void) { + mmf = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 2, "SWDCButton"); + if (mmf == NULL) { + return S_FALSE; + } + + swdc_set_gamebtns(0); + + return S_OK; +} + +void swdc_set_gamebtns(uint16_t value) { + // WaitForSingleObject(mutex, INFINITE); + + // Update the memory-mapped file + LPVOID mmf_view = MapViewOfFile(mmf, FILE_MAP_ALL_ACCESS, 0, 0, 2); + if (mmf_view != NULL) { + uint16_t* ptr = (uint16_t*)mmf_view; + *ptr = value; + + UnmapViewOfFile(mmf_view); + } + + // ReleaseMutex(mutex); +} + +static HRESULT swdc_io4_poll(void *ctx, struct io4_state *state) { + uint8_t opbtn; + uint16_t gamebtn; + struct swdc_io_analog_state analog_state; + HRESULT hr; + + assert(swdc_dll.get_opbtns != NULL); + assert(swdc_dll.get_gamebtns != NULL); + assert(swdc_dll.get_analogs != NULL); + + memset(state, 0, sizeof(*state)); + memset(&analog_state, 0, sizeof(analog_state)); + opbtn = 0; + gamebtn = 0; + + swdc_dll.get_opbtns(&opbtn); + swdc_dll.get_gamebtns(&gamebtn); + swdc_dll.get_analogs(&analog_state); + + if (opbtn & SWDC_IO_OPBTN_TEST) { + state->buttons[0] |= IO4_BUTTON_TEST; + } + + if (opbtn & SWDC_IO_OPBTN_SERVICE) { + state->buttons[0] |= IO4_BUTTON_SERVICE; + } + + if (opbtn & SWDC_IO_OPBTN_COIN) { + coins++; + } + state->chutes[0] = coins << 8; + + /* Update Cabinet buttons */ + + if (gamebtn & SWDC_IO_GAMEBTN_START) { + state->buttons[0] |= 1 << 7; + } + + if (gamebtn & SWDC_IO_GAMEBTN_VIEW_CHANGE) { + state->buttons[0] |= 1 << 1; + } + + if (gamebtn & SWDC_IO_GAMEBTN_UP) { + state->buttons[0] |= 1 << 5; + } + + if (gamebtn & SWDC_IO_GAMEBTN_DOWN) { + state->buttons[0] |= 1 << 4; + } + + if (gamebtn & SWDC_IO_GAMEBTN_LEFT) { + state->buttons[0] |= 1 << 3; + } + + if (gamebtn & SWDC_IO_GAMEBTN_RIGHT) { + state->buttons[0] |= 1 << 2; + } + +/* + Update steering wheel buttons + + Those are connected to the SEGA 838-15415 INDICATOR BD MAIN + USB board which is not emulated for now. So those buttons + are hooked to the built-in XInput support. +*/ + + /* Instead update gamebtns for the file mapping */ + + swdc_set_gamebtns(gamebtn); + + if (gamebtn & SWDC_IO_GAMEBTN_STEERING_BLUE) { + state->buttons[1] |= 1 << 15; + } + + if (gamebtn & SWDC_IO_GAMEBTN_STEERING_GREEN) { + state->buttons[1] |= 1 << 14; + } + + if (gamebtn & SWDC_IO_GAMEBTN_STEERING_RED) { + state->buttons[1] |= 1 << 13; + } + + if (gamebtn & SWDC_IO_GAMEBTN_STEERING_YELLOW) { + state->buttons[1] |= 1 << 12; + } + + if (gamebtn & SWDC_IO_GAMEBTN_STEERING_PADDLE_LEFT) { + state->buttons[1] |= 1 << 1; + } + + if (gamebtn & SWDC_IO_GAMEBTN_STEERING_PADDLE_RIGHT) { + state->buttons[1] |= 1 << 0; + } + + /* Steering wheel increases left-to-right. + + Use 0x8000 as the center point. */ + + state->adcs[0] = 0x8000 + analog_state.wheel; + state->adcs[1] = analog_state.accel; + state->adcs[2] = analog_state.brake; + + return S_OK; +} + +static HRESULT swdc_io4_write_gpio(uint8_t* payload, size_t len) +{ + assert(swdc_dll.led_set_leds != NULL); + + // Just fast fail if there aren't enough bytes in the payload + if (len < 3) + return S_OK; + + // This command is used for lights in SWDC, but it only contains button lights, + // and only in the first 3 bytes of the payload; everything else is padding to + // make the payload 62 bytes. The rest of the cabinet lights and the side button + // lights are handled separately, by the 15070 lights controller. + uint32_t lights_data = (uint32_t) ((uint8_t)(payload[0]) << 24 | + (uint8_t)(payload[1]) << 16 | + (uint8_t)(payload[2]) << 8); + + // Since Sega uses an odd ordering for the first part of the bitfield, + // let's normalize the data and just send over bytes for the receiver + // to interpret as ON/OFF values. + uint8_t rgb_out[6] = { + lights_data & SWDC_IO_LED_START ? 0xFF : 0x00, + lights_data & SWDC_IO_LED_VIEW_CHANGE ? 0xFF : 0x00, + lights_data & SWDC_IO_LED_UP ? 0xFF : 0x00, + lights_data & SWDC_IO_LED_DOWN ? 0xFF : 0x00, + lights_data & SWDC_IO_LED_RIGHT ? 0xFF : 0x00, + lights_data & SWDC_IO_LED_LEFT ? 0xFF : 0x00, + }; + + swdc_dll.led_set_leds(0, rgb_out); + + return S_OK; +} diff --git a/games/swdchook/io4.h b/games/swdchook/io4.h new file mode 100644 index 0000000..69580d1 --- /dev/null +++ b/games/swdchook/io4.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "board/io4.h" + +HRESULT swdc_io4_hook_init(const struct io4_config *cfg); diff --git a/games/swdchook/meson.build b/games/swdchook/meson.build new file mode 100644 index 0000000..882647a --- /dev/null +++ b/games/swdchook/meson.build @@ -0,0 +1,33 @@ +shared_library( + 'swdchook', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + vs_module_defs : 'swdchook.def', + dependencies : [ + capnhook.get_variable('hook_dep'), + capnhook.get_variable('hooklib_dep'), + xinput_lib, + ], + link_with : [ + aimeio_lib, + board_lib, + hooklib_lib, + swdcio_lib, + platform_lib, + util_lib, + ], + sources : [ + 'config.c', + 'config.h', + 'dllmain.c', + 'swdc-dll.c', + 'swdc-dll.h', + 'io4.c', + 'io4.h', + 'zinput.c', + 'zinput.h', + 'ffb.c', + 'ffb.h' + ], +) diff --git a/games/swdchook/swdc-dll.c b/games/swdchook/swdc-dll.c new file mode 100644 index 0000000..0897b73 --- /dev/null +++ b/games/swdchook/swdc-dll.c @@ -0,0 +1,136 @@ +#include + +#include +#include + +#include "swdchook/swdc-dll.h" + +#include "util/dll-bind.h" +#include "util/dprintf.h" + +const struct dll_bind_sym swdc_dll_syms[] = { + { + .sym = "swdc_io_init", + .off = offsetof(struct swdc_dll, init), + }, { + .sym = "swdc_io_get_opbtns", + .off = offsetof(struct swdc_dll, get_opbtns), + }, { + .sym = "swdc_io_get_gamebtns", + .off = offsetof(struct swdc_dll, get_gamebtns), + }, { + .sym = "swdc_io_get_analogs", + .off = offsetof(struct swdc_dll, get_analogs), + }, { + .sym = "swdc_io_led_init", + .off = offsetof(struct swdc_dll, led_init), + }, { + .sym = "swdc_io_led_set_fet_output", + .off = offsetof(struct swdc_dll, led_set_fet_output), + }, { + .sym = "swdc_io_led_gs_update", + .off = offsetof(struct swdc_dll, led_gs_update), + }, { + .sym = "swdc_io_led_set_leds", + .off = offsetof(struct swdc_dll, led_set_leds), + }, { + .sym = "swdc_io_ffb_init", + .off = offsetof(struct swdc_dll, ffb_init), + }, { + .sym = "swdc_io_ffb_toggle", + .off = offsetof(struct swdc_dll, ffb_toggle), + }, { + .sym = "swdc_io_ffb_constant_force", + .off = offsetof(struct swdc_dll, ffb_constant_force), + }, { + .sym = "swdc_io_ffb_rumble", + .off = offsetof(struct swdc_dll, ffb_rumble), + }, { + .sym = "swdc_io_ffb_damper", + .off = offsetof(struct swdc_dll, ffb_damper), + } +}; + +struct swdc_dll swdc_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 swdc_dll_init(const struct swdc_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("SWDC IO: Failed to load IO DLL: %lx: %S\n", + hr, + cfg->path); + + goto end; + } + + dprintf("SWDC IO: Using custom IO DLL: %S\n", cfg->path); + src = owned; + } else { + owned = NULL; + src = self; + } + + get_api_version = (void *) GetProcAddress(src, "swdc_io_get_api_version"); + + if (get_api_version != NULL) { + swdc_dll.api_version = get_api_version(); + } else { + swdc_dll.api_version = 0x0100; + dprintf("Custom IO DLL does not expose swdc_io_get_api_version, " + "assuming API version 1.0.\n" + "Please ask the developer to update their DLL.\n"); + } + + if (swdc_dll.api_version >= 0x0200) { + hr = E_NOTIMPL; + dprintf("SWDC IO: Custom IO DLL implements an unsupported " + "API version (%#04x). Please update Segatools.\n", + swdc_dll.api_version); + + goto end; + } + + sym = swdc_dll_syms; + hr = dll_bind(&swdc_dll, src, &sym, _countof(swdc_dll_syms)); + + if (FAILED(hr)) { + if (src != self) { + dprintf("SWDC 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/games/swdchook/swdc-dll.h b/games/swdchook/swdc-dll.h new file mode 100644 index 0000000..7539d1c --- /dev/null +++ b/games/swdchook/swdc-dll.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include "swdcio/swdcio.h" + +struct swdc_dll { + uint16_t api_version; + HRESULT (*init)(void); + void (*get_opbtns)(uint8_t *opbtn); + void (*get_gamebtns)(uint16_t *gamebtn); + void (*get_analogs)(struct swdc_io_analog_state *out); + HRESULT (*led_init)(void); + void (*led_set_fet_output)(uint8_t board, const uint8_t *rgb); + void (*led_gs_update)(uint8_t board, const uint8_t *rgb); + void (*led_set_leds)(uint8_t board, const uint8_t *rgb); + HRESULT (*ffb_init)(void); + void (*ffb_toggle)(bool active); + void (*ffb_constant_force)(uint8_t direction, uint8_t force); + void (*ffb_rumble)(uint8_t period, uint8_t force); + void (*ffb_damper)(uint8_t force); +}; + +struct swdc_dll_config { + wchar_t path[MAX_PATH]; +}; + +extern struct swdc_dll swdc_dll; + +HRESULT swdc_dll_init(const struct swdc_dll_config *cfg, HINSTANCE self); diff --git a/games/swdchook/swdchook.def b/games/swdchook/swdchook.def new file mode 100644 index 0000000..ad4f93e --- /dev/null +++ b/games/swdchook/swdchook.def @@ -0,0 +1,27 @@ +LIBRARY swdchook + +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 + swdc_io_get_api_version + swdc_io_init + swdc_io_get_opbtns + swdc_io_get_gamebtns + swdc_io_get_analogs + swdc_io_led_init + swdc_io_led_set_fet_output + swdc_io_led_gs_update + swdc_io_led_set_leds + swdc_io_ffb_init + swdc_io_ffb_toggle + swdc_io_ffb_constant_force + swdc_io_ffb_rumble + swdc_io_ffb_damper diff --git a/games/swdchook/zinput.c b/games/swdchook/zinput.c new file mode 100644 index 0000000..5a14800 --- /dev/null +++ b/games/swdchook/zinput.c @@ -0,0 +1,214 @@ +#include +#include + +#include +#include +#include +#include +#include + +#include "board/io4.h" + +#include "hook/table.h" +#include "util/dprintf.h" +#include "util/lib.h" + +#include "swdchook/config.h" +#include "swdchook/zinput.h" + +static struct zinput_config zinput_config; +static bool zinput_hook_initted; +static bool zinput_controller_init = false; + +static HRESULT init_mmf(void); + +static HANDLE mmf; +static uint16_t* swdc_gamebtn; + +/* Hooked functions */ +DWORD WINAPI hook_XInputGetState(DWORD dwUserIndex, XINPUT_STATE *pState); +DWORD WINAPI hook_XInputSetState(DWORD dwUserIndex, XINPUT_VIBRATION *pVibration); +// Not needed for now? +DWORD WINAPI hook_XInputGetCapabilities(DWORD dwUserIndex, DWORD dwFlags, XINPUT_CAPABILITIES *pCapabilities); + +// Yup SEGA imports XInput functions via ordinal. FUN! +static struct hook_symbol zinput_hook_syms[] = { + { + .name = "XInputGetState", + .ordinal = 0x0002, + .patch = hook_XInputGetState, + .link = NULL + }, { + .name = "XInputSetState", + .ordinal = 0x0003, + .patch = hook_XInputSetState, + .link = NULL + }, { + // Not needed for now? + .name = "XInputGetCapabilities", + .patch = hook_XInputGetCapabilities, + .link = NULL + }, +}; + +void zinput_hook_init(struct zinput_config *cfg) +{ + wchar_t *module_path; + wchar_t *file_name; + + assert(cfg != NULL); + + if (!cfg->enable) { + return; + } + + if (zinput_hook_initted) { + return; + } + + module_path = module_file_name(NULL); + + if (module_path != NULL) { + file_name = PathFindFileNameW(module_path); + + free(module_path); + module_path = NULL; + + _wcslwr(file_name); + + if (wcsstr(file_name, L"amdaemon") != NULL) { + // dprintf("Executable filename contains 'amdaemon', disabling zinput\n"); + return; + } + } + + hook_table_apply( + NULL, + "XINPUT1_3.dll", + zinput_hook_syms, + _countof(zinput_hook_syms)); + + if (FAILED(init_mmf())) { + return; + } + + zinput_hook_initted = true; + + dprintf("ZInput: Hooking built-in XInput support\n"); +} + +bool zinput_connect_controller(bool enable) { + zinput_controller_init = enable; + dprintf("zinput_connect_controller\n"); + return true; +} + +static HRESULT init_mmf(void) { + // Create or open memory-mapped file + mmf = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 2, "SWDCButton"); + if (mmf == NULL) { + return S_FALSE; + } + + // Map the memory-mapped file + swdc_gamebtn = (uint16_t*)MapViewOfFile(mmf, FILE_MAP_ALL_ACCESS, 0, 0, 2); + + return S_OK; +} + +DWORD WINAPI hook_XInputGetCapabilities(DWORD dwUserIndex, DWORD dwFlags, XINPUT_CAPABILITIES *pCapabilities) { + // dprintf("ZInput: XInputGetCapabilities hook hit\n"); + + if (!zinput_controller_init) { + zinput_connect_controller(true); + } + + if (dwFlags > XINPUT_FLAG_GAMEPAD) { + return ERROR_BAD_ARGUMENTS; + } + + if (zinput_controller_init && dwUserIndex == 0) { + 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; + } +} + +DWORD WINAPI hook_XInputGetState(DWORD dwUserIndex, XINPUT_STATE *pState) { + // dprintf("ZInput: XInputGetState hook hit\n"); + + if (!zinput_controller_init) { + zinput_connect_controller(true); + } + + if (zinput_controller_init && dwUserIndex == 0) { + XINPUT_GAMEPAD gamepad_state = {0}; + gamepad_state.wButtons = 0; + + /* Read filemapping for for the gamebtns */ + + if (*swdc_gamebtn & SWDC_IO_GAMEBTN_STEERING_PADDLE_LEFT) { + gamepad_state.wButtons |= XINPUT_GAMEPAD_LEFT_SHOULDER; + } + + if (*swdc_gamebtn & SWDC_IO_GAMEBTN_STEERING_PADDLE_RIGHT) { + gamepad_state.wButtons |= XINPUT_GAMEPAD_RIGHT_SHOULDER; + } + + if (*swdc_gamebtn & SWDC_IO_GAMEBTN_STEERING_BLUE) { + gamepad_state.wButtons |= XINPUT_GAMEPAD_X; + } + + if (*swdc_gamebtn & SWDC_IO_GAMEBTN_STEERING_RED) { + gamepad_state.wButtons |= XINPUT_GAMEPAD_B; + } + + if (*swdc_gamebtn & SWDC_IO_GAMEBTN_STEERING_GREEN) { + gamepad_state.wButtons |= XINPUT_GAMEPAD_A; + } + + if (*swdc_gamebtn & SWDC_IO_GAMEBTN_STEERING_YELLOW) { + gamepad_state.wButtons |= XINPUT_GAMEPAD_Y; + } + if (pState->dwPacketNumber == UINT_MAX) + pState->dwPacketNumber = 0; + else + pState->dwPacketNumber++; + + pState->Gamepad = gamepad_state; + return ERROR_SUCCESS; + } else { + return ERROR_DEVICE_NOT_CONNECTED; + } +} + +DWORD WINAPI hook_XInputSetState(DWORD dwUserIndex, XINPUT_VIBRATION *pVibration) { + // dprintf("ZInput: XInputSetState hook hit\n"); + + if (!zinput_controller_init) { + zinput_connect_controller(true); + } + + if (zinput_controller_init && dwUserIndex == 0) { + return ERROR_SUCCESS; + } else { + return ERROR_DEVICE_NOT_CONNECTED; + } +} diff --git a/games/swdchook/zinput.h b/games/swdchook/zinput.h new file mode 100644 index 0000000..dc883dc --- /dev/null +++ b/games/swdchook/zinput.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#include + +struct zinput_config { + bool enable; +}; + +void zinput_hook_init(struct zinput_config *cfg); diff --git a/games/swdcio/backend.h b/games/swdcio/backend.h new file mode 100644 index 0000000..85517a4 --- /dev/null +++ b/games/swdcio/backend.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +#include "swdcio/swdcio.h" + +struct swdc_io_backend { + void (*get_opbtns)(uint8_t *opbtn); + void (*get_gamebtns)(uint16_t *gamebtn); + void (*get_analogs)(struct swdc_io_analog_state *state); + HRESULT (*ffb_init)(void); + void (*ffb_toggle)(bool active); + void (*ffb_constant_force)(uint8_t direction, uint8_t force); + void (*ffb_rumble)(uint8_t period, uint8_t force); + void (*ffb_damper)(uint8_t force); +}; diff --git a/games/swdcio/config.c b/games/swdcio/config.c new file mode 100644 index 0000000..4f095ce --- /dev/null +++ b/games/swdcio/config.c @@ -0,0 +1,147 @@ +#include + +#include +#include +#include +#include +#include + +#include "swdcio/config.h" + +void swdc_di_config_load(struct swdc_di_config *cfg, const wchar_t *filename) +{ + wchar_t key[8]; + int i; + + assert(cfg != NULL); + assert(filename != NULL); + + GetPrivateProfileStringW( + L"dinput", + L"deviceName", + L"", + cfg->device_name, + _countof(cfg->device_name), + filename); + + GetPrivateProfileStringW( + L"dinput", + L"pedalsName", + L"", + cfg->pedals_name, + _countof(cfg->pedals_name), + filename); + + GetPrivateProfileStringW( + L"dinput", + L"brakeAxis", + L"RZ", + cfg->brake_axis, + _countof(cfg->brake_axis), + filename); + + GetPrivateProfileStringW( + L"dinput", + L"accelAxis", + L"Y", + cfg->accel_axis, + _countof(cfg->accel_axis), + filename); + + cfg->start = GetPrivateProfileIntW(L"dinput", L"start", 0, filename); + cfg->view_chg = GetPrivateProfileIntW(L"dinput", L"viewChg", 0, filename); + cfg->paddle_left = GetPrivateProfileIntW(L"dinput", L"paddleLeft", 0, filename); + cfg->paddle_right = GetPrivateProfileIntW(L"dinput", L"paddleRight", 0, filename); + cfg->wheel_green = GetPrivateProfileIntW(L"dinput", L"wheelGreen", 0, filename); + cfg->wheel_red = GetPrivateProfileIntW(L"dinput", L"wheelRed", 0, filename); + cfg->wheel_blue = GetPrivateProfileIntW(L"dinput", L"wheelBlue", 0, filename); + cfg->wheel_yellow = GetPrivateProfileIntW(L"dinput", L"wheelYellow", 0, filename); + + cfg->reverse_brake_axis = GetPrivateProfileIntW( + L"dinput", + L"reverseBrakeAxis", + 0, + filename); + cfg->reverse_accel_axis = GetPrivateProfileIntW( + L"dinput", + L"reverseAccelAxis", + 0, + filename); + + // FFB configuration + cfg->ffb_constant_force_strength = GetPrivateProfileIntW( + L"dinput", + L"constantForceStrength", + 100, + filename); + + cfg->ffb_rumble_strength = GetPrivateProfileIntW( + L"dinput", + L"rumbleStrength", + 100, + filename); + + cfg->ffb_damper_strength = GetPrivateProfileIntW( + L"dinput", + L"damperStrength", + 100, + filename); + + cfg->ffb_rumble_duration = GetPrivateProfileIntW( + L"dinput", + L"rumbleDuration", + 1000, + filename); +} + +void swdc_xi_config_load(struct swdc_xi_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->single_stick_steering = GetPrivateProfileIntW( + L"xinput", + L"singleStickSteering", + 0, + filename); + + cfg->linear_steering = GetPrivateProfileIntW( + L"xinput", + L"linearSteering", + 0, + filename); + + cfg->left_stick_deadzone = GetPrivateProfileIntW( + L"xinput", + L"leftStickDeadzone", + 7849, + filename); + + cfg->right_stick_deadzone = GetPrivateProfileIntW( + L"xinput", + L"rightStickDeadzone", + 8689, + filename); +} + +void swdc_io_config_load(struct swdc_io_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->vk_test = GetPrivateProfileIntW(L"io4", L"test", VK_F1, filename); + cfg->vk_service = GetPrivateProfileIntW(L"io4", L"service", VK_F2, filename); + cfg->vk_coin = GetPrivateProfileIntW(L"io4", L"coin", VK_F3, filename); + cfg->restrict_ = GetPrivateProfileIntW(L"io4", L"restrict", 128, filename); + + GetPrivateProfileStringW( + L"io4", + L"mode", + L"xinput", + cfg->mode, + _countof(cfg->mode), + filename); + + swdc_di_config_load(&cfg->di, filename); + swdc_xi_config_load(&cfg->xi, filename); +} diff --git a/games/swdcio/config.h b/games/swdcio/config.h new file mode 100644 index 0000000..9a630a2 --- /dev/null +++ b/games/swdcio/config.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include + +struct swdc_di_config { + wchar_t device_name[64]; + wchar_t pedals_name[64]; + wchar_t brake_axis[16]; + wchar_t accel_axis[16]; + uint8_t start; + uint8_t view_chg; + uint8_t paddle_left; + uint8_t paddle_right; + uint8_t wheel_green; + uint8_t wheel_red; + uint8_t wheel_blue; + uint8_t wheel_yellow; + bool reverse_brake_axis; + bool reverse_accel_axis; + + // FFB configuration + uint8_t ffb_constant_force_strength; + uint8_t ffb_rumble_strength; + uint8_t ffb_damper_strength; + + uint32_t ffb_rumble_duration; +}; + +struct swdc_xi_config { + bool single_stick_steering; + bool linear_steering; + uint16_t left_stick_deadzone; + uint16_t right_stick_deadzone; +}; + +struct swdc_io_config { + uint8_t vk_test; + uint8_t vk_service; + uint8_t vk_coin; + wchar_t mode[8]; + int restrict_; + struct swdc_di_config di; + struct swdc_xi_config xi; +}; + +void swdc_di_config_load(struct swdc_di_config *cfg, const wchar_t *filename); +void swdc_xi_config_load(struct swdc_xi_config *cfg, const wchar_t *filename); +void swdc_io_config_load(struct swdc_io_config *cfg, const wchar_t *filename); diff --git a/games/swdcio/di-dev.c b/games/swdcio/di-dev.c new file mode 100644 index 0000000..4f099cc --- /dev/null +++ b/games/swdcio/di-dev.c @@ -0,0 +1,371 @@ +#include +#include +#include +#include + +#include "swdcio/di-dev.h" + +#include "util/dprintf.h" + +const struct swdc_di_config *swdc_di_cfg; +static HWND swdc_di_wnd; +static IDirectInputDevice8W *swdc_di_dev; + +/* Individual DI Effects */ +static IDirectInputEffect *swdc_di_fx; +static IDirectInputEffect *swdc_di_fx_rumble; +static IDirectInputEffect *swdc_di_fx_damper; + +/* Max FFB Board value is 127 */ +static const double swdc_di_ffb_scale = 127.0; + +HRESULT swdc_di_dev_init( + const struct swdc_di_config *cfg, + IDirectInputDevice8W *dev, + HWND wnd) +{ + HRESULT hr; + + assert(dev != NULL); + assert(wnd != NULL); + + swdc_di_cfg = cfg; + swdc_di_dev = dev; + swdc_di_wnd = wnd; + + return S_OK; +} + +HRESULT swdc_di_dev_poll( + IDirectInputDevice8W *dev, + HWND wnd, + union swdc_di_state *out) +{ + HRESULT hr; + MSG msg; + + assert(dev != NULL); + assert(wnd != NULL); + assert(out != NULL); + + memset(out, 0, sizeof(*out)); + + /* Pump our dummy window's message queue just in case DirectInput or an + IHV DirectInput driver somehow relies on it */ + + while (PeekMessageW(&msg, wnd, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + hr = IDirectInputDevice8_GetDeviceState( + dev, + sizeof(out->st), + &out->st); + + if (FAILED(hr)) { + dprintf("DirectInput: GetDeviceState error: %08x\n", (int)hr); + } + + return hr; +} + +HRESULT swdc_di_dev_start(IDirectInputDevice8W *dev, HWND wnd) { + HRESULT hr; + + assert(dev != NULL); + assert(wnd != NULL); + + hr = IDirectInputDevice8_SetCooperativeLevel( + dev, + wnd, + DISCL_BACKGROUND | DISCL_EXCLUSIVE); + + if (FAILED(hr)) { + dprintf("DirectInput: SetCooperativeLevel failed: %08x\n", (int)hr); + return hr; + } + + hr = IDirectInputDevice8_SetDataFormat(dev, &c_dfDIJoystick); + + if (FAILED(hr)) { + dprintf("DirectInput: SetDataFormat failed: %08x\n", (int)hr); + return hr; + } + + hr = IDirectInputDevice8_Acquire(dev); + + if (FAILED(hr)) { + dprintf("DirectInput: Acquire failed: %08x\n", (int)hr); + return hr; + } + + return hr; +} + +HRESULT swdc_di_ffb_init(void) +{ + HRESULT hr; + + hr = swdc_di_dev_start(swdc_di_dev, swdc_di_wnd); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} + +void swdc_di_ffb_toggle(bool active) +{ + if (active) { + return; + } + + /* Stop and release all effects */ + /* I never programmed DirectInput Effects, so this might be bad practice. */ + if (swdc_di_fx != NULL) { + IDirectInputEffect_Stop(swdc_di_fx); + IDirectInputEffect_Release(swdc_di_fx); + swdc_di_fx = NULL; + } + + if (swdc_di_fx_rumble != NULL) { + IDirectInputEffect_Stop(swdc_di_fx_rumble); + IDirectInputEffect_Release(swdc_di_fx_rumble); + swdc_di_fx_rumble = NULL; + } + + if (swdc_di_fx_damper != NULL) { + IDirectInputEffect_Stop(swdc_di_fx_damper); + IDirectInputEffect_Release(swdc_di_fx_damper); + swdc_di_fx_damper = NULL; + } +} + +void swdc_di_ffb_constant_force(uint8_t direction_ffb, uint8_t force) +{ + /* DI expects a magnitude in the range of -10.000 to 10.000 */ + uint16_t ffb_strength = swdc_di_cfg->ffb_constant_force_strength * 100; + if (ffb_strength == 0) { + return; + } + + DWORD axis; + LONG direction; + DIEFFECT fx; + DICONSTANTFORCE cf; + HRESULT hr; + + /* Direction 0: move to the right, 1: move to the left */ + LONG magnitude = (LONG)(((double)force / swdc_di_ffb_scale) * ffb_strength); + cf.lMagnitude = (direction_ffb == 0) ? -magnitude : magnitude; + + axis = DIJOFS_X; + direction = 0; + + memset(&fx, 0, sizeof(fx)); + fx.dwSize = sizeof(fx); + fx.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; + fx.dwDuration = INFINITE; + fx.dwGain = DI_FFNOMINALMAX; + fx.dwTriggerButton = DIEB_NOTRIGGER; + fx.dwTriggerRepeatInterval = INFINITE; + fx.cAxes = 1; + fx.rgdwAxes = &axis; + fx.rglDirection = &direction; + fx.cbTypeSpecificParams = sizeof(cf); + fx.lpvTypeSpecificParams = &cf; + + /* Check if the effect already exists */ + if (swdc_di_fx != NULL) { + hr = IDirectInputEffect_SetParameters(swdc_di_fx, &fx, DIEP_TYPESPECIFICPARAMS); + if (SUCCEEDED(hr)) { + return; // Successfully updated existing effect + } + else { + dprintf("DirectInput: Failed to update constant force feedback, recreating effect: %08x\n", (int)hr); + IDirectInputEffect_Stop(swdc_di_fx); + IDirectInputEffect_Release(swdc_di_fx); + swdc_di_fx = NULL; // Reset the pointer + } + } + + /* Create a new constant force effect */ + IDirectInputEffect *obj; + hr = IDirectInputDevice8_CreateEffect( + swdc_di_dev, + &GUID_ConstantForce, + &fx, + &obj, + NULL); + + if (FAILED(hr)) { + dprintf("DirectInput: Constant force feedback creation failed: %08x\n", (int)hr); + return; + } + + /* Start the effect */ + hr = IDirectInputEffect_Start(obj, INFINITE, 0); + if (FAILED(hr)) { + dprintf("DirectInput: Constant force feedback start failed: %08x\n", (int)hr); + IDirectInputEffect_Release(obj); + return; + } + + swdc_di_fx = obj; +} + +void swdc_di_ffb_rumble(uint8_t force, uint8_t period) +{ + /* DI expects a magnitude in the range of -10.000 to 10.000 */ + uint16_t ffb_strength = swdc_di_cfg->ffb_rumble_strength * 100; + if (ffb_strength == 0) { + return; + } + + uint32_t ffb_duration = swdc_di_cfg->ffb_rumble_duration; + + DWORD axis; + LONG direction; + DIEFFECT fx; + DIPERIODIC pe; + HRESULT hr; + + DWORD duration = (DWORD)((double)force * ffb_duration); + + memset(&pe, 0, sizeof(pe)); + pe.dwMagnitude = (DWORD)(((double)force / swdc_di_ffb_scale) * ffb_strength); + pe.lOffset = 0; + pe.dwPhase = 0; + pe.dwPeriod = duration; + + axis = DIJOFS_X; + direction = 0; + + memset(&fx, 0, sizeof(fx)); + fx.dwSize = sizeof(fx); + fx.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; + fx.dwDuration = INFINITE; + fx.dwGain = DI_FFNOMINALMAX; + fx.dwTriggerButton = DIEB_NOTRIGGER; + fx.dwTriggerRepeatInterval = INFINITE; + fx.cAxes = 1; + fx.rgdwAxes = &axis; + fx.rglDirection = &direction; + fx.cbTypeSpecificParams = sizeof(pe); + fx.lpvTypeSpecificParams = &pe; + + /* Check if the effect already exists */ + if (swdc_di_fx_rumble != NULL) { + hr = IDirectInputEffect_SetParameters(swdc_di_fx_rumble, &fx, DIEP_TYPESPECIFICPARAMS); + if (SUCCEEDED(hr)) { + return; + } + else { + dprintf("DirectInput: Failed to update rumble feedback, recreating effect: %08x\n", (int)hr); + IDirectInputEffect_Stop(swdc_di_fx_rumble); + IDirectInputEffect_Release(swdc_di_fx_rumble); + swdc_di_fx_rumble = NULL; + } + } + + /* Create a new rumble effect */ + IDirectInputEffect *obj; + hr = IDirectInputDevice8_CreateEffect( + swdc_di_dev, + &GUID_Sine, + &fx, + &obj, + NULL); + + if (FAILED(hr)) { + dprintf("DirectInput: Rumble effect creation failed: %08x\n", (int)hr); + return; + } + + /* Start the effect */ + hr = IDirectInputEffect_Start(obj, INFINITE, 0); + if (FAILED(hr)) { + dprintf("DirectInput: Rumble effect start failed: %08x\n", (int)hr); + IDirectInputEffect_Release(obj); + return; + } + + swdc_di_fx_rumble = obj; +} + +void swdc_di_ffb_damper(uint8_t force) +{ + /* DI expects a coefficient in the range of -10.000 to 10.000 */ + uint16_t ffb_strength = swdc_di_cfg->ffb_damper_strength * 100; + if (ffb_strength == 0) { + return; + } + + DWORD axis; + LONG direction; + DIEFFECT fx; + DICONDITION cond; + HRESULT hr; + + memset(&cond, 0, sizeof(cond)); + cond.lOffset = 0; + cond.lPositiveCoefficient = (LONG)(((double)force / swdc_di_ffb_scale) * ffb_strength); + cond.lNegativeCoefficient = (LONG)(((double)force / swdc_di_ffb_scale) * ffb_strength); + /* Not sure on this one */ + cond.dwPositiveSaturation = DI_FFNOMINALMAX; + cond.dwNegativeSaturation = DI_FFNOMINALMAX; + cond.lDeadBand = 0; + + axis = DIJOFS_X; + direction = 0; + + memset(&fx, 0, sizeof(fx)); + fx.dwSize = sizeof(fx); + fx.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; + fx.dwDuration = INFINITE; + fx.dwGain = DI_FFNOMINALMAX; + fx.dwTriggerButton = DIEB_NOTRIGGER; + fx.dwTriggerRepeatInterval = INFINITE; + fx.cAxes = 1; + fx.rgdwAxes = &axis; + fx.rglDirection = &direction; + fx.cbTypeSpecificParams = sizeof(cond); + fx.lpvTypeSpecificParams = &cond; + + /* Check if the damper effect already exists */ + if (swdc_di_fx_damper != NULL) { + hr = IDirectInputEffect_SetParameters(swdc_di_fx_damper, &fx, DIEP_TYPESPECIFICPARAMS); + if (SUCCEEDED(hr)) { + return; + } + else { + IDirectInputEffect_Stop(swdc_di_fx_damper); + IDirectInputEffect_Release(swdc_di_fx_damper); + swdc_di_fx_damper = NULL; + } + } + + /* Create a new damper effect */ + IDirectInputEffect *obj; + hr = IDirectInputDevice8_CreateEffect( + swdc_di_dev, + &GUID_Damper, + &fx, + &obj, + NULL); + + if (FAILED(hr)) { + return; + } + + /* Start the effect */ + hr = IDirectInputEffect_Start(obj, fx.dwDuration, 0); + if (FAILED(hr)) { + IDirectInputEffect_Release(obj); + return; + } + + swdc_di_fx_damper = obj; +} diff --git a/games/swdcio/di-dev.h b/games/swdcio/di-dev.h new file mode 100644 index 0000000..d28afdb --- /dev/null +++ b/games/swdcio/di-dev.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +#include + +#include "swdcio/config.h" + +union swdc_di_state { + DIJOYSTATE st; + uint8_t bytes[sizeof(DIJOYSTATE)]; +}; + +HRESULT swdc_di_dev_init( + const struct swdc_di_config *cfg, + IDirectInputDevice8W *dev, + HWND wnd); + +HRESULT swdc_di_dev_start(IDirectInputDevice8W *dev, HWND wnd); +HRESULT swdc_di_dev_poll( + IDirectInputDevice8W *dev, + HWND wnd, + union swdc_di_state *out); + +HRESULT swdc_di_ffb_init(void); +void swdc_di_ffb_toggle(bool active); +void swdc_di_ffb_constant_force(uint8_t direction, uint8_t force); +void swdc_di_ffb_rumble(uint8_t force, uint8_t period); +void swdc_di_ffb_damper(uint8_t force); diff --git a/games/swdcio/di.c b/games/swdcio/di.c new file mode 100644 index 0000000..8012454 --- /dev/null +++ b/games/swdcio/di.c @@ -0,0 +1,528 @@ +#include +#include + +#include +#include +#include +#include +#include + +#include "swdcio/backend.h" +#include "swdcio/config.h" +#include "swdcio/di.h" +#include "swdcio/di-dev.h" +#include "swdcio/swdcio.h" +#include "swdcio/wnd.h" + +#include "util/dprintf.h" +#include "util/str.h" + +struct swdc_di_axis { + wchar_t name[4]; + size_t off; +}; + +static HRESULT swdc_di_config_apply(const struct swdc_di_config *cfg); +static const struct swdc_di_axis *swdc_di_get_axis(const wchar_t *name); +static BOOL CALLBACK swdc_di_enum_callback( + const DIDEVICEINSTANCEW *dev, + void *ctx); +static BOOL CALLBACK swdc_di_enum_callback_pedals( + const DIDEVICEINSTANCEW *dev, + void *ctx); +static void swdc_di_get_buttons(uint16_t *gamebtn_out); +static uint8_t swdc_di_decode_pov(DWORD pov); +static void swdc_di_get_analogs(struct swdc_io_analog_state *out); + +static const struct swdc_di_axis swdc_di_axes[] = { + /* Just map DIJOYSTATE for now, we can map DIJOYSTATE2 later if needed */ + { .name = L"X", .off = DIJOFS_X }, + { .name = L"Y", .off = DIJOFS_Y }, + { .name = L"Z", .off = DIJOFS_Z }, + { .name = L"RX", .off = DIJOFS_RX }, + { .name = L"RY", .off = DIJOFS_RY }, + { .name = L"RZ", .off = DIJOFS_RZ }, + { .name = L"U", .off = DIJOFS_SLIDER(0) }, + { .name = L"V", .off = DIJOFS_SLIDER(1) }, +}; + +static const struct swdc_io_backend swdc_di_backend = { + .get_gamebtns = swdc_di_get_buttons, + .get_analogs = swdc_di_get_analogs, + .ffb_init = swdc_di_ffb_init, + .ffb_toggle = swdc_di_ffb_toggle, + .ffb_constant_force = swdc_di_ffb_constant_force, + .ffb_rumble = swdc_di_ffb_rumble, + .ffb_damper = swdc_di_ffb_damper +}; + +static HWND swdc_di_wnd; +static IDirectInput8W *swdc_di_api; +static IDirectInputDevice8W *swdc_di_dev; +static IDirectInputDevice8W *swdc_di_pedals; +static IDirectInputEffect *swdc_di_fx; +static size_t swdc_di_off_brake; +static size_t swdc_di_off_accel; +static uint8_t swdc_di_paddle_left; +static uint8_t swdc_di_paddle_right; +static uint8_t swdc_di_view_chg; +static uint8_t swdc_di_start; +static uint8_t swdc_di_wheel_green; +static uint8_t swdc_di_wheel_red; +static uint8_t swdc_di_wheel_blue; +static uint8_t swdc_di_wheel_yellow; +static bool swdc_di_use_pedals; +static bool swdc_di_reverse_brake_axis; +static bool swdc_di_reverse_accel_axis; + +HRESULT swdc_di_init( + const struct swdc_di_config *cfg, + HINSTANCE inst, + const struct swdc_io_backend **backend) +{ + HRESULT hr; + HMODULE dinput8; + HRESULT (WINAPI *api_entry)(HINSTANCE,DWORD,REFIID,LPVOID *,LPUNKNOWN); + wchar_t dll_path[MAX_PATH]; + UINT path_pos; + + assert(cfg != NULL); + assert(backend != NULL); + + *backend = NULL; + + hr = swdc_di_config_apply(cfg); + + if (FAILED(hr)) { + return hr; + } + + hr = swdc_io_wnd_create(inst, &swdc_di_wnd); + + if (FAILED(hr)) { + return hr; + } + + /* SWDC has some built-in DirectInput support that is not + particularly useful. swdchook shorts this out by redirecting dinput8.dll + to a no-op implementation of DirectInput. However, swdcio does need to + talk to the real operating system implementation of DirectInput without + the stub DLL interfering, so build a path to + C:\Windows\System32\dinput.dll here. */ + + dll_path[0] = L'\0'; + path_pos = GetSystemDirectoryW(dll_path, _countof(dll_path)); + wcscat_s( + dll_path + path_pos, + _countof(dll_path) - path_pos, + L"\\dinput8.dll"); + + dinput8 = LoadLibraryW(dll_path); + + if (dinput8 == NULL) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("DirectInput: LoadLibrary failed: %08x\n", (int) hr); + + return hr; + } + + api_entry = (void *) GetProcAddress(dinput8, "DirectInput8Create"); + + if (api_entry == NULL) { + dprintf("DirectInput: GetProcAddress failed\n"); + + return E_FAIL; + } + + hr = api_entry( + inst, + DIRECTINPUT_VERSION, + &IID_IDirectInput8W, + (void **) &swdc_di_api, + NULL); + + if (FAILED(hr)) { + dprintf("DirectInput: API create failed: %08x\n", (int) hr); + + return hr; + } + + hr = IDirectInput8_EnumDevices( + swdc_di_api, + DI8DEVCLASS_GAMECTRL, + swdc_di_enum_callback, + (void *) cfg, + DIEDFL_ATTACHEDONLY); + + if (FAILED(hr)) { + dprintf("DirectInput: EnumDevices failed: %08x\n", (int) hr); + + return hr; + } + + if (swdc_di_dev == NULL) { + dprintf("Wheel: Controller not found\n"); + + return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + } + + hr = swdc_di_dev_init(cfg, swdc_di_dev, swdc_di_wnd); + + if (FAILED(hr)) { + return hr; + } + + if (cfg->pedals_name[0] != L'\0') { + hr = IDirectInput8_EnumDevices( + swdc_di_api, + DI8DEVCLASS_GAMECTRL, + swdc_di_enum_callback_pedals, + (void *) cfg, + DIEDFL_ATTACHEDONLY); + + if (FAILED(hr)) { + dprintf("DirectInput: EnumDevices failed: %08x\n", (int) hr); + + return hr; + } + + if (swdc_di_dev == NULL) { + dprintf("Pedals: Controller not found\n"); + + return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + } + + hr = swdc_di_dev_start(swdc_di_pedals, swdc_di_wnd); + + if (FAILED(hr)) { + return hr; + } + + swdc_di_use_pedals = true; + } else { + swdc_di_use_pedals = false; + } + + dprintf("DirectInput: Controller initialized\n"); + + *backend = &swdc_di_backend; + + return S_OK; +} + +static HRESULT swdc_di_config_apply(const struct swdc_di_config *cfg) +{ + const struct swdc_di_axis *brake_axis; + const struct swdc_di_axis *accel_axis; + int i; + + brake_axis = swdc_di_get_axis(cfg->brake_axis); + accel_axis = swdc_di_get_axis(cfg->accel_axis); + + if (brake_axis == NULL) { + dprintf("Wheel: Invalid brake axis: %S\n", cfg->brake_axis); + + return E_INVALIDARG; + } + + if (accel_axis == NULL) { + dprintf("Wheel: Invalid accel axis: %S\n", cfg->accel_axis); + + return E_INVALIDARG; + } + + if (cfg->start > 32) { + dprintf("Wheel: Invalid start button: %i\n", cfg->start); + + return E_INVALIDARG; + } + + if (cfg->view_chg > 32) { + dprintf("Wheel: Invalid view change button: %i\n", cfg->view_chg); + + return E_INVALIDARG; + } + + if (cfg->paddle_left > 32) { + dprintf("Wheel: Invalid left paddle button: %i\n", cfg->paddle_left); + + return E_INVALIDARG; + } + + if (cfg->paddle_right > 32) { + dprintf("Wheel: Invalid right paddle button: %i\n", cfg->paddle_right); + + return E_INVALIDARG; + } + + if (cfg->wheel_green > 32) { + dprintf("Wheel: Invalid steering wheel green button: %i\n", cfg->wheel_green); + + return E_INVALIDARG; + } + + if (cfg->wheel_red > 32) { + dprintf("Wheel: Invalid steering wheel red button: %i\n", cfg->wheel_red); + + return E_INVALIDARG; + } + + if (cfg->wheel_blue > 32) { + dprintf("Wheel: Invalid steering wheel blue button: %i\n", cfg->wheel_blue); + + return E_INVALIDARG; + } + + if (cfg->wheel_yellow > 32) { + dprintf("Wheel: Invalid steering wheel yellow button: %i\n", cfg->wheel_yellow); + + return E_INVALIDARG; + } + + /* Print some debug output to make sure config works... */ + + dprintf("Wheel: --- Begin configuration ---\n"); + dprintf("Wheel: Device name . . . . : Contains \"%S\"\n", + cfg->device_name); + if (cfg->pedals_name[0] == L'\0') { + dprintf("Wheel: Brake axis . . . . : %S\n", brake_axis->name); + dprintf("Wheel: Accel axis . . . . : %S\n", accel_axis->name); + } + dprintf("Wheel: Start button . . . . . : %i\n", cfg->start); + dprintf("Wheel: View Change button . . : %i\n", cfg->view_chg); + dprintf("Wheel: Paddle Left button . . : %i\n", cfg->paddle_left); + dprintf("Wheel: Paddle Right button . : %i\n", cfg->paddle_right); + dprintf("Wheel: Steering Green button : %i\n", cfg->wheel_green); + dprintf("Wheel: Steering Red button . : %i\n", cfg->wheel_red); + dprintf("Wheel: Steering Blue button . : %i\n", cfg->wheel_blue); + dprintf("Wheel: Steering Yellow button : %i\n", cfg->wheel_yellow); + dprintf("Wheel: Reverse Brake Axis . . : %i\n", cfg->reverse_brake_axis); + dprintf("Wheel: Reverse Accel Axis . . : %i\n", cfg->reverse_accel_axis); + dprintf("Wheel: --- End configuration ---\n"); + + if (cfg->pedals_name[0] != L'\0') { + dprintf("Pedals: --- Begin configuration ---\n"); + dprintf("Pedals: Device name . . . : Contains \"%S\"\n", + cfg->pedals_name); + dprintf("Pedals: Brake axis . . . . : %S\n", brake_axis->name); + dprintf("Pedals: Accel axis . . . . : %S\n", accel_axis->name); + dprintf("Pedals: --- End configuration ---\n"); + } + + swdc_di_off_brake = brake_axis->off; + swdc_di_off_accel = accel_axis->off; + swdc_di_start = cfg->start; + swdc_di_view_chg = cfg->view_chg; + swdc_di_paddle_left = cfg->paddle_left; + swdc_di_paddle_right = cfg->paddle_right; + swdc_di_wheel_green = cfg->wheel_green; + swdc_di_wheel_red = cfg->wheel_red; + swdc_di_wheel_blue = cfg->wheel_blue; + swdc_di_wheel_yellow = cfg->wheel_yellow; + swdc_di_reverse_brake_axis = cfg->reverse_brake_axis; + swdc_di_reverse_accel_axis = cfg->reverse_accel_axis; + + /* FFB configuration */ + if (cfg->ffb_constant_force_strength < 0 || cfg->ffb_constant_force_strength > 100) { + dprintf("Wheel: Invalid constant force strength: %i\n", cfg->ffb_constant_force_strength); + + return E_INVALIDARG; + } + + if (cfg->ffb_rumble_strength < 0 || cfg->ffb_rumble_strength > 100) { + dprintf("Wheel: Invalid rumble strength: %i\n", cfg->ffb_rumble_strength); + + return E_INVALIDARG; + } + + if (cfg->ffb_damper_strength < 0 || cfg->ffb_damper_strength > 100) { + dprintf("Wheel: Invalid damper strength: %i\n", cfg->ffb_damper_strength); + + return E_INVALIDARG; + } + + return S_OK; +} + +static const struct swdc_di_axis *swdc_di_get_axis(const wchar_t *name) +{ + const struct swdc_di_axis *axis; + size_t i; + + for (i = 0 ; i < _countof(swdc_di_axes) ; i++) { + axis = &swdc_di_axes[i]; + + if (wstr_ieq(name, axis->name)) { + return axis; + } + } + + return NULL; +} + +static BOOL CALLBACK swdc_di_enum_callback( + const DIDEVICEINSTANCEW *dev, + void *ctx) +{ + const struct swdc_di_config *cfg; + HRESULT hr; + + cfg = ctx; + + if (wcsstr(dev->tszProductName, cfg->device_name) == NULL) { + return DIENUM_CONTINUE; + } + + dprintf("Wheel: Using DirectInput device \"%S\"\n", dev->tszProductName); + + hr = IDirectInput8_CreateDevice( + swdc_di_api, + &dev->guidInstance, + &swdc_di_dev, + NULL); + + if (FAILED(hr)) { + dprintf("Wheel: CreateDevice failed: %08x\n", (int) hr); + } + + return DIENUM_STOP; +} + +static BOOL CALLBACK swdc_di_enum_callback_pedals( + const DIDEVICEINSTANCEW *dev, + void *ctx) +{ + const struct swdc_di_config *cfg; + HRESULT hr; + + cfg = ctx; + + if (wcsstr(dev->tszProductName, cfg->pedals_name) == NULL) { + return DIENUM_CONTINUE; + } + + dprintf("Pedals: Using DirectInput device \"%S\"\n", dev->tszProductName); + + hr = IDirectInput8_CreateDevice( + swdc_di_api, + &dev->guidInstance, + &swdc_di_pedals, + NULL); + + if (FAILED(hr)) { + dprintf("Pedals: CreateDevice failed: %08x\n", (int) hr); + } + + return DIENUM_STOP; +} + +static void swdc_di_get_buttons(uint16_t *gamebtn_out) +{ + union swdc_di_state state; + uint16_t gamebtn; + HRESULT hr; + + assert(gamebtn_out != NULL); + + hr = swdc_di_dev_poll(swdc_di_dev, swdc_di_wnd, &state); + + if (FAILED(hr)) { + return; + } + + gamebtn = swdc_di_decode_pov(state.st.rgdwPOV[0]); + + if (swdc_di_start && state.st.rgbButtons[swdc_di_start - 1]) { + gamebtn |= SWDC_IO_GAMEBTN_START; + } + + if (swdc_di_view_chg && state.st.rgbButtons[swdc_di_view_chg - 1]) { + gamebtn |= SWDC_IO_GAMEBTN_VIEW_CHANGE; + } + + if (swdc_di_paddle_left && state.st.rgbButtons[swdc_di_paddle_left - 1]) { + gamebtn |= SWDC_IO_GAMEBTN_STEERING_PADDLE_LEFT; + } + + if (swdc_di_paddle_right && state.st.rgbButtons[swdc_di_paddle_right - 1]) { + gamebtn |= SWDC_IO_GAMEBTN_STEERING_PADDLE_RIGHT; + } + + if (swdc_di_wheel_green && state.st.rgbButtons[swdc_di_wheel_green - 1]) { + gamebtn |= SWDC_IO_GAMEBTN_STEERING_GREEN; + } + + if (swdc_di_wheel_red && state.st.rgbButtons[swdc_di_wheel_red - 1]) { + gamebtn |= SWDC_IO_GAMEBTN_STEERING_RED; + } + + if (swdc_di_wheel_blue && state.st.rgbButtons[swdc_di_wheel_blue - 1]) { + gamebtn |= SWDC_IO_GAMEBTN_STEERING_BLUE; + } + + if (swdc_di_wheel_yellow && state.st.rgbButtons[swdc_di_wheel_yellow - 1]) { + gamebtn |= SWDC_IO_GAMEBTN_STEERING_YELLOW; + } + + *gamebtn_out = gamebtn; +} + +static uint8_t swdc_di_decode_pov(DWORD pov) +{ + switch (pov) { + case 0: return SWDC_IO_GAMEBTN_UP; + case 4500: return SWDC_IO_GAMEBTN_UP | SWDC_IO_GAMEBTN_RIGHT; + case 9000: return SWDC_IO_GAMEBTN_RIGHT; + case 13500: return SWDC_IO_GAMEBTN_RIGHT | SWDC_IO_GAMEBTN_DOWN; + case 18000: return SWDC_IO_GAMEBTN_DOWN; + case 22500: return SWDC_IO_GAMEBTN_DOWN | SWDC_IO_GAMEBTN_LEFT; + case 27000: return SWDC_IO_GAMEBTN_LEFT; + case 31500: return SWDC_IO_GAMEBTN_LEFT | SWDC_IO_GAMEBTN_UP; + default: return 0; + } +} + +static void swdc_di_get_analogs(struct swdc_io_analog_state *out) +{ + union swdc_di_state state; + union swdc_di_state pedals_state; + const LONG *brake; + const LONG *accel; + HRESULT hr; + + assert(out != NULL); + + hr = swdc_di_dev_poll(swdc_di_dev, swdc_di_wnd, &state); + + if (FAILED(hr)) { + return; + } + + if (swdc_di_use_pedals) { + hr = swdc_di_dev_poll(swdc_di_pedals, swdc_di_wnd, &pedals_state); + + if (FAILED(hr)) { + return; + } + + brake = (LONG *) &pedals_state.bytes[swdc_di_off_brake]; + accel = (LONG *) &pedals_state.bytes[swdc_di_off_accel]; + } else { + brake = (LONG *) &state.bytes[swdc_di_off_brake]; + accel = (LONG *) &state.bytes[swdc_di_off_accel]; + } + + out->wheel = state.st.lX - 32768; + + if (swdc_di_reverse_brake_axis) { + out->brake = *brake; + } else { + out->brake = 65535 - *brake; + } + + if (swdc_di_reverse_accel_axis) { + out->accel = *accel; + } else { + out->accel = 65535 - *accel; + } +} diff --git a/games/swdcio/di.h b/games/swdcio/di.h new file mode 100644 index 0000000..6e4b32c --- /dev/null +++ b/games/swdcio/di.h @@ -0,0 +1,9 @@ +#pragma once + +#include "swdcio/backend.h" +#include "swdcio/config.h" + +HRESULT swdc_di_init( + const struct swdc_di_config *cfg, + HINSTANCE inst, + const struct swdc_io_backend **backend); diff --git a/games/swdcio/dllmain.c b/games/swdcio/dllmain.c new file mode 100644 index 0000000..a35b8e8 --- /dev/null +++ b/games/swdcio/dllmain.c @@ -0,0 +1,191 @@ +#include + +#include +#include +#include + +#include "swdcio/backend.h" +#include "swdcio/config.h" +#include "swdcio/di.h" +#include "swdcio/swdcio.h" +#include "swdcio/xi.h" + +#include "util/dprintf.h" +#include "util/str.h" +#include "util/env.h" + +static struct swdc_io_config swdc_io_cfg; +static const struct swdc_io_backend *swdc_io_backend; +static bool swdc_io_coin; + +uint16_t swdc_io_get_api_version(void) +{ + return 0x0102; +} + +HRESULT swdc_io_init(void) +{ + HINSTANCE inst; + HRESULT hr; + + assert(swdc_io_backend == NULL); + + inst = GetModuleHandleW(NULL); + + if (inst == NULL) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("GetModuleHandleW failed: %lx\n", hr); + + return hr; + } + + swdc_io_config_load(&swdc_io_cfg, get_config_path()); + + if (wstr_ieq(swdc_io_cfg.mode, L"dinput")) { + hr = swdc_di_init(&swdc_io_cfg.di, inst, &swdc_io_backend); + } else if (wstr_ieq(swdc_io_cfg.mode, L"xinput")) { + hr = swdc_xi_init(&swdc_io_cfg.xi, &swdc_io_backend); + } else { + hr = E_INVALIDARG; + dprintf("swdc IO: Invalid IO mode \"%S\", use dinput or xinput\n", + swdc_io_cfg.mode); + } + + return hr; +} + +void swdc_io_get_opbtns(uint8_t *opbtn_out) +{ + uint8_t opbtn; + + assert(swdc_io_backend != NULL); + assert(opbtn_out != NULL); + + opbtn = 0; + + /* Common operator buttons, not backend-specific */ + + if (GetAsyncKeyState(swdc_io_cfg.vk_test) & 0x8000) { + opbtn |= SWDC_IO_OPBTN_TEST; + } + + if (GetAsyncKeyState(swdc_io_cfg.vk_service) & 0x8000) { + opbtn |= SWDC_IO_OPBTN_SERVICE; + } + + if (GetAsyncKeyState(swdc_io_cfg.vk_coin) & 0x8000) { + if (!swdc_io_coin) { + swdc_io_coin = true; + opbtn |= SWDC_IO_OPBTN_COIN; + } + } else { + swdc_io_coin = false; + } + + *opbtn_out = opbtn; +} + + +void swdc_io_get_gamebtns(uint16_t *gamebtn_out) +{ + assert(swdc_io_backend != NULL); + assert(gamebtn_out != NULL); + + swdc_io_backend->get_gamebtns(gamebtn_out); +} + +void swdc_io_get_analogs(struct swdc_io_analog_state *out) +{ + struct swdc_io_analog_state tmp; + + assert(out != NULL); + assert(swdc_io_backend != NULL); + + swdc_io_backend->get_analogs(&tmp); + + /* Apply steering wheel restriction. Real cabs only report about 77% of + the IO-3's max ADC output value when the wheel is turned to either of + its maximum positions. To match this behavior we set the default value + for the wheel restriction config parameter to 97 (out of 128). This + scaling factor is applied using fixed-point arithmetic below. */ + + out->wheel = (tmp.wheel * swdc_io_cfg.restrict_) / 128; + out->accel = tmp.accel; + out->brake = tmp.brake; +} + +HRESULT swdc_io_led_init(void) +{ + return S_OK; +} + +void swdc_io_led_set_fet_output(uint8_t board, const uint8_t *rgb) +{ +#if 0 + dprintf("SWDC LED: LEFT SEAT LED: %02X\n", rgb[0]); + dprintf("SWDC LED: RIGHT SEAT LED: %02X\n", rgb[1]); +#endif + + return; +} + +void swdc_io_led_gs_update(uint8_t board, const uint8_t *rgb) +{ +#if 0 + for (int i = 0; i < 9; i++) { + dprintf("SWDC LED: LED %d: %02X %02X %02X Speed: %02X\n", + i, rgb[i * 4], rgb[i * 4 + 1], rgb[i * 4 + 2], rgb[i * 4 + 3]); + } +#endif + + return; +} + +void swdc_io_led_set_leds(uint8_t board, const uint8_t *rgb) +{ +#if 0 + dprintf("SWDC LED: START: %02X\n", rgb[0]); + dprintf("SWDC LED: VIEW CHANGE: %02X\n", rgb[1]); + dprintf("SWDC LED: UP: %02X\n", rgb[2]); + dprintf("SWDC LED: DOWN: %02X\n", rgb[3]); + dprintf("SWDC LED: RIGHT: %02X\n", rgb[4]); + dprintf("SWDC LED: LEFT: %02X\n", rgb[5]); +#endif + + return; +} + +HRESULT swdc_io_ffb_init(void) +{ + assert(swdc_io_backend != NULL); + + return swdc_io_backend->ffb_init(); +} + +void swdc_io_ffb_toggle(bool active) +{ + assert(swdc_io_backend != NULL); + + swdc_io_backend->ffb_toggle(active); +} + +void swdc_io_ffb_constant_force(uint8_t direction, uint8_t force) +{ + assert(swdc_io_backend != NULL); + + swdc_io_backend->ffb_constant_force(direction, force); +} + +void swdc_io_ffb_rumble(uint8_t period, uint8_t force) +{ + assert(swdc_io_backend != NULL); + + swdc_io_backend->ffb_rumble(period, force); +} + +void swdc_io_ffb_damper(uint8_t force) +{ + assert(swdc_io_backend != NULL); + + swdc_io_backend->ffb_damper(force); +} diff --git a/games/swdcio/meson.build b/games/swdcio/meson.build new file mode 100644 index 0000000..d9de43e --- /dev/null +++ b/games/swdcio/meson.build @@ -0,0 +1,29 @@ +swdcio_lib = static_library( + 'swdccio', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + dependencies : [ + dinput8_lib, + dxguid_lib, + xinput_lib, + ], + link_with : [ + util_lib, + ], + sources : [ + 'backend.h', + 'config.c', + 'config.h', + 'di.c', + 'di.h', + 'di-dev.c', + 'di-dev.h', + 'dllmain.c', + 'swdcio.h', + 'wnd.c', + 'wnd.h', + 'xi.c', + 'xi.h', + ], +) diff --git a/games/swdcio/swdcio.def b/games/swdcio/swdcio.def new file mode 100644 index 0000000..b90faba --- /dev/null +++ b/games/swdcio/swdcio.def @@ -0,0 +1,16 @@ +LIBRARY swdcio + +EXPORTS + swdc_io_init + swdc_io_get_opbtns + swdc_io_get_gamebtns + swdc_io_get_analogs + swdc_io_led_init + swdc_io_led_set_fet_output + swdc_io_led_gs_update + swdc_io_led_set_leds + swdc_io_ffb_init + swdc_io_ffb_toggle + swdc_io_ffb_constant_force + swdc_io_ffb_rumble + swdc_io_ffb_damper diff --git a/games/swdcio/swdcio.h b/games/swdcio/swdcio.h new file mode 100644 index 0000000..9d069f3 --- /dev/null +++ b/games/swdcio/swdcio.h @@ -0,0 +1,207 @@ +#pragma once + +#include + +#include +#include + +enum { + SWDC_IO_OPBTN_TEST = 0x01, + SWDC_IO_OPBTN_SERVICE = 0x02, + SWDC_IO_OPBTN_COIN = 0x04, +}; + +enum { + SWDC_IO_GAMEBTN_UP = 0x01, + SWDC_IO_GAMEBTN_DOWN = 0x02, + SWDC_IO_GAMEBTN_LEFT = 0x04, + SWDC_IO_GAMEBTN_RIGHT = 0x08, + SWDC_IO_GAMEBTN_START = 0x10, + SWDC_IO_GAMEBTN_VIEW_CHANGE = 0x20, + + SWDC_IO_GAMEBTN_STEERING_BLUE = 0x40, + SWDC_IO_GAMEBTN_STEERING_GREEN = 0x80, + SWDC_IO_GAMEBTN_STEERING_RED = 0x100, + SWDC_IO_GAMEBTN_STEERING_YELLOW = 0x200, + SWDC_IO_GAMEBTN_STEERING_PADDLE_LEFT = 0x400, + SWDC_IO_GAMEBTN_STEERING_PADDLE_RIGHT = 0x800, +}; + +enum { + /* These are the bitmasks to use when checking which + lights are triggered on incoming IO4 GPIO writes. */ + SWDC_IO_LED_START = 1 << 31, + SWDC_IO_LED_VIEW_CHANGE = 1 << 30, + SWDC_IO_LED_UP = 1 << 25, + SWDC_IO_LED_DOWN = 1 << 24, + SWDC_IO_LED_LEFT = 1 << 23, + SWDC_IO_LED_RIGHT = 1 << 22, +}; + +struct swdc_io_analog_state { + /* Current steering wheel position, where zero is the centered position. + + The game will accept any signed 16-bit position value, however a real + cabinet will report a value of approximately +/- 25230 when the wheel + is at full lock. Steering wheel positions of a magnitude greater than + this value are not possible on a real cabinet. */ + + int16_t wheel; + + /* Current position of the accelerator pedal, where 0 is released. */ + + uint16_t accel; + + /* Current position of the brake pedal, where 0 is released. */ + + uint16_t brake; +}; + +/* Get the version of the SWDC 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 swdc_io_get_api_version(void); + +/* Initialize the IO DLL. This is the second function that will be called on + your DLL, after SWDC_io_get_api_version. + + All subsequent calls to this API may originate from arbitrary threads. + + Minimum API version: 0x0100 */ + +HRESULT swdc_io_init(void); + +/* Get the state of the cabinet's operator buttons as of the last poll. See + SWDC_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 swdc_io_get_opbtns(uint8_t *opbtn); + +/* Get the state of the cabinet's gameplay buttons as of the last poll. See + SWDC_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 swdc_io_get_gamebtns(uint16_t *gamebtn); + +/* Poll the current state of the cabinet's JVS analog inputs. See structure + definition above for details. + + Minimum API version: 0x0100 */ + +void swdc_io_get_analogs(struct swdc_io_analog_state *out); + +/* Initialize LED emulation. This function will be called before any + other swdc_io_led_*() function calls. + + 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: 0x0101 */ + +HRESULT swdc_io_led_init(void); + +/* Update the FET outputs. rgb is a pointer to an array up to 3 bytes. + + The following bits are used to control the FET outputs: + [0]: LEFT SEAT LED + [1]: RIGHT SEAT LED + + The LED is truned on when the byte is 255 and turned off when the byte is 0. + + Minimum API version: 0x0101 */ + +void swdc_io_led_set_fet_output(uint8_t board, const uint8_t *rgb); + +/* Update the RGB LEDs. rgb is a pointer to an array up to 32 * 4 = 128 bytes. + + The LEDs are laid out as follows: + [0]: LEFT UP LED + [1-2]: LEFT CENTER LED + [3]: LEFT DOWN LED + [5]: RIGHT UP LED + [6-7]: RIGHT CENTER LED + [8]: RIGHT DOWN LED + + Each rgb value is comprised for 4 bytes in the order of R, G, B, Speed. + Speed is a value from 0 to 255, where 0 is the fastest speed and 255 is the slowest. + + Minimum API version: 0x0101 */ + +void swdc_io_led_gs_update(uint8_t board, const uint8_t *rgb); + +/* Update the cabinet button LEDs. rgb is a pointer to an array up to 6 bytes. + + The LEDs are laid out as follows: + [0]: START LED + [1]: VIEW CHANGE LED + [2]: UP LED + [3]: DOWN LED + [4]: RIGHT LED + [5]: LEFT LED + + The LED is turned on when the byte is 255 and turned off when the byte is 0. + + Minimum API version: 0x0101 */ + +void swdc_io_led_set_leds(uint8_t board, const uint8_t *rgb); + +/* Initialize FFB emulation. This function will be called before any + other swdc_io_ffb_*() function calls. + + This will always be called even if FFB board emulation is disabled to allow + the IO DLL to initialize any necessary resources. + + Minimum API version: 0x0102 */ + +HRESULT swdc_io_ffb_init(void); + +/* Toggle FFB emulation. If active is true, FFB emulation should be enabled. + If active is false, FFB emulation should be disabled and all FFB effects + should be stopped and released. + + Minimum API version: 0x0102 */ + +void swdc_io_ffb_toggle(bool active); + +/* Set a constant force FFB effect. + + Direction is 0 for right and 1 for left. + Force is the magnitude of the force, where 0 is no force and 127 is the + maximum force in a given direction. + + Minimum API version: 0x0102 */ + +void swdc_io_ffb_constant_force(uint8_t direction, uint8_t force); + +/* Set a (sine) periodic force FFB effect. + + Period is the period of the effect in milliseconds (not sure). + Force is the magnitude of the force, where 0 is no force and 127 is the + maximum force. + + Minimum API version: 0x0102 */ + +void swdc_io_ffb_rumble(uint8_t period, uint8_t force); + +/* Set a damper FFB effect. + + Force is the magnitude of the force, where 0 is no force and 40 is the + maximum force. Theoretically the maximum force is 127, but the game only + uses a maximum of 40. + + Minimum API version: 0x0102 */ + +void swdc_io_ffb_damper(uint8_t force); diff --git a/games/swdcio/wnd.c b/games/swdcio/wnd.c new file mode 100644 index 0000000..66699ee --- /dev/null +++ b/games/swdcio/wnd.c @@ -0,0 +1,86 @@ +#include + +#include +#include + +#include "util/dprintf.h" + +/* DirectInput requires a window for correct initialization (and also force + feedback), so this source file provides some utilities for creating a + generic message-only window. */ + +static LRESULT WINAPI swdc_io_wnd_proc( + HWND hwnd, + UINT msg, + WPARAM wparam, + LPARAM lparam); + +HRESULT swdc_io_wnd_create(HINSTANCE inst, HWND *out) +{ + HRESULT hr; + WNDCLASSEXW wcx; + ATOM atom; + HWND hwnd; + + assert(inst != NULL); /* We are not an EXE */ + assert(out != NULL); + + *out = NULL; + + memset(&wcx, 0, sizeof(wcx)); + wcx.cbSize = sizeof(wcx); + wcx.lpfnWndProc = swdc_io_wnd_proc; + wcx.hInstance = inst; + wcx.lpszClassName = L"SWDCIO"; + + atom = RegisterClassExW(&wcx); + + if (atom == 0) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("SWDCIO: RegisterClassExW failed: %08x\n", (int) hr); + + goto fail; + } + + hwnd = CreateWindowExW( + 0, + (wchar_t *) (intptr_t) atom, + L"", + 0, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + HWND_MESSAGE, + NULL, + inst, + NULL); + + if (hwnd == NULL) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("SWDCIO: CreateWindowExW failed: %08x\n", (int) hr); + + goto fail; + } + + *out = hwnd; + + return S_OK; + +fail: + UnregisterClassW((wchar_t *) (intptr_t) atom, inst); + + return hr; +} + +static LRESULT WINAPI swdc_io_wnd_proc( + HWND hwnd, + UINT msg, + WPARAM wparam, + LPARAM lparam) +{ + switch (msg) { + default: + return DefWindowProcW(hwnd, msg, wparam, lparam); + } +} diff --git a/games/swdcio/wnd.h b/games/swdcio/wnd.h new file mode 100644 index 0000000..5ab4188 --- /dev/null +++ b/games/swdcio/wnd.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +HRESULT swdc_io_wnd_create(HINSTANCE inst, HWND *out); diff --git a/games/swdcio/xi.c b/games/swdcio/xi.c new file mode 100644 index 0000000..7a6984a --- /dev/null +++ b/games/swdcio/xi.c @@ -0,0 +1,255 @@ +#include +#include + +#include +#include +#include +#include + +#include "swdcio/backend.h" +#include "swdcio/config.h" +#include "swdcio/swdcio.h" +#include "swdcio/xi.h" + +#include "util/dprintf.h" + +static void swdc_xi_get_gamebtns(uint16_t *gamebtn_out); +static void swdc_xi_get_analogs(struct swdc_io_analog_state *out); + +static HRESULT swdc_xi_ffb_init(void); +static void swdc_xi_ffb_toggle(bool active); +static void swdc_xi_ffb_constant_force(uint8_t direction, uint8_t force); +static void swdc_xi_ffb_rumble(uint8_t force, uint8_t period); +static void swdc_xi_ffb_damper(uint8_t force); + +static HRESULT swdc_xi_config_apply(const struct swdc_xi_config *cfg); + +static const struct swdc_io_backend swdc_xi_backend = { + .get_gamebtns = swdc_xi_get_gamebtns, + .get_analogs = swdc_xi_get_analogs, + .ffb_init = swdc_xi_ffb_init, + .ffb_toggle = swdc_xi_ffb_toggle, + .ffb_constant_force = swdc_xi_ffb_constant_force, + .ffb_rumble = swdc_xi_ffb_rumble, + .ffb_damper = swdc_xi_ffb_damper +}; + +static bool swdc_xi_single_stick_steering; +static bool swdc_xi_linear_steering; +static uint16_t swdc_xi_left_stick_deadzone; +static uint16_t swdc_xi_right_stick_deadzone; + +const uint16_t max_stick_value = 32767; +/* Apply steering wheel restriction. Real cabs only report about 76% of + the output value when the wheel is turned to either of its maximum positions. */ +const uint16_t max_wheel_value = 24831; + +HRESULT swdc_xi_init(const struct swdc_xi_config *cfg, const struct swdc_io_backend **backend) +{ + HRESULT hr; + assert(cfg != NULL); + assert(backend != NULL); + + hr = swdc_xi_config_apply(cfg); + + if (FAILED(hr)) { + return hr; + } + + dprintf("XInput: Using XInput controller\n"); + *backend = &swdc_xi_backend; + + return S_OK; +} + +HRESULT swdc_io_poll(void) +{ + return S_OK; +} + +static HRESULT swdc_xi_config_apply(const struct swdc_xi_config *cfg) { + /* Deadzones check */ + if (cfg->left_stick_deadzone > 32767 || cfg->left_stick_deadzone < 0) { + dprintf("XInput: Left stick deadzone is too large or negative\n"); + return E_INVALIDARG; + } + + if (cfg->right_stick_deadzone > 32767 || cfg->right_stick_deadzone < 0) { + dprintf("XInput: Right stick deadzone is too large or negative\n"); + return E_INVALIDARG; + } + + dprintf("XInput: --- Begin configuration ---\n"); + dprintf("XInput: Single Stick Steering : %i\n", cfg->single_stick_steering); + dprintf("XInput: Linear Steering . . . : %i\n", cfg->linear_steering); + dprintf("XInput: Left Deadzone . . . . : %i\n", cfg->left_stick_deadzone); + dprintf("XInput: Right Deadzone . . . : %i\n", cfg->right_stick_deadzone); + dprintf("XInput: --- End configuration ---\n"); + + swdc_xi_single_stick_steering = cfg->single_stick_steering; + swdc_xi_linear_steering = cfg->linear_steering; + swdc_xi_left_stick_deadzone = cfg->left_stick_deadzone; + swdc_xi_right_stick_deadzone = cfg->right_stick_deadzone; + + return S_OK; +} + +static void swdc_xi_get_gamebtns(uint16_t *gamebtn_out) +{ + uint16_t gamebtn; + XINPUT_STATE xi; + WORD xb; + + assert(gamebtn_out != NULL); + + gamebtn = 0; + + memset(&xi, 0, sizeof(xi)); + XInputGetState(0, &xi); + xb = xi.Gamepad.wButtons; + + if (xb & XINPUT_GAMEPAD_DPAD_UP) { + gamebtn |= SWDC_IO_GAMEBTN_UP; + } + + if (xb & XINPUT_GAMEPAD_DPAD_DOWN) { + gamebtn |= SWDC_IO_GAMEBTN_DOWN; + } + + if (xb & XINPUT_GAMEPAD_DPAD_LEFT) { + gamebtn |= SWDC_IO_GAMEBTN_LEFT; + } + + if (xb & XINPUT_GAMEPAD_DPAD_RIGHT) { + gamebtn |= SWDC_IO_GAMEBTN_RIGHT; + } + + if (xb & XINPUT_GAMEPAD_START) { + gamebtn |= SWDC_IO_GAMEBTN_START; + } + + if (xb & XINPUT_GAMEPAD_BACK) { + gamebtn |= SWDC_IO_GAMEBTN_VIEW_CHANGE; + } + + if (xb & XINPUT_GAMEPAD_A) { + gamebtn |= SWDC_IO_GAMEBTN_STEERING_GREEN; + } + + if (xb & XINPUT_GAMEPAD_B) { + gamebtn |= SWDC_IO_GAMEBTN_STEERING_RED; + } + + if (xb & XINPUT_GAMEPAD_X) { + gamebtn |= SWDC_IO_GAMEBTN_STEERING_BLUE; + } + + if (xb & XINPUT_GAMEPAD_Y) { + gamebtn |= SWDC_IO_GAMEBTN_STEERING_YELLOW; + } + + if (xb & XINPUT_GAMEPAD_LEFT_SHOULDER) { + gamebtn |= SWDC_IO_GAMEBTN_STEERING_PADDLE_LEFT; + } + + if (xb & XINPUT_GAMEPAD_RIGHT_SHOULDER) { + gamebtn |= SWDC_IO_GAMEBTN_STEERING_PADDLE_RIGHT; + } + + *gamebtn_out = gamebtn; +} + +static int16_t calculate_norm_steering(int16_t axis, uint16_t deadzone, bool linear_steering) { + // determine how far the controller is pushed + float magnitude = sqrt(axis*axis); + + // determine the direction the controller is pushed + float norm_axis = axis / magnitude; + + float norm_magnitude = 0.0; + + // check if the controller is outside a circular dead zone + if (magnitude > deadzone) + { + // clip the magnitude at its expected maximum value + if (magnitude > max_stick_value) magnitude = max_stick_value; + + // adjust magnitude relative to the end of the dead zone + magnitude -= deadzone; + + // optionally normalize the magnitude with respect to its expected range + // giving a magnitude value of 0.0 to 1.0 + norm_magnitude = (int16_t)(magnitude / (max_stick_value - deadzone)); + } else // if the controller is in the deadzone zero out the magnitude + { + magnitude = 0.0; + norm_magnitude = 0.0; + } + + // apply non-linear transform to the axis + if (!linear_steering) { + return (int16_t)(norm_axis * powf(norm_magnitude, 3.0f) * max_wheel_value); + } + + return (int16_t)(norm_axis * norm_magnitude * max_wheel_value); +} + +static void swdc_xi_get_analogs(struct swdc_io_analog_state *out) +{ + XINPUT_STATE xi; + int left; + int right; + + assert(out != NULL); + + memset(&xi, 0, sizeof(xi)); + XInputGetState(0, &xi); + + left = xi.Gamepad.sThumbLX; + right = xi.Gamepad.sThumbRX; + + // normalize the steering axis + left = calculate_norm_steering(left, swdc_xi_left_stick_deadzone, swdc_xi_linear_steering); + right = calculate_norm_steering(right, swdc_xi_right_stick_deadzone, swdc_xi_linear_steering); + + if (swdc_xi_single_stick_steering) { + out->wheel = left; + } else { + out->wheel = (left + right) / 2; + } + + out->accel = xi.Gamepad.bRightTrigger << 8; + out->brake = xi.Gamepad.bLeftTrigger << 8; +} + +static HRESULT swdc_xi_ffb_init(void) { + return S_OK; +} + +static void swdc_xi_ffb_toggle(bool active) { + XINPUT_VIBRATION vibration; + + memset(&vibration, 0, sizeof(vibration)); + + XInputSetState(0, &vibration); +} + +static void swdc_xi_ffb_constant_force(uint8_t direction, uint8_t force) { + return; +} + +static void swdc_xi_ffb_rumble(uint8_t force, uint8_t period) { + XINPUT_VIBRATION vibration; + /* XInput max strength is 65.535, so multiply the 127.0 by 516. */ + uint16_t strength = force * 516; + + memset(&vibration, 0, sizeof(vibration)); + vibration.wLeftMotorSpeed = strength; + vibration.wRightMotorSpeed = strength; + + XInputSetState(0, &vibration); +} + +static void swdc_xi_ffb_damper(uint8_t force) { + return; +} diff --git a/games/swdcio/xi.h b/games/swdcio/xi.h new file mode 100644 index 0000000..15937ff --- /dev/null +++ b/games/swdcio/xi.h @@ -0,0 +1,10 @@ +#pragma once + +/* Can't call this xinput.h or it will conflict with */ + +#include + +#include "swdcio/backend.h" +#include "swdcio/config.h" + +HRESULT swdc_xi_init(const struct swdc_xi_config *cfg, const struct swdc_io_backend **backend); diff --git a/games/tokyohook/config.c b/games/tokyohook/config.c new file mode 100644 index 0000000..3ab387a --- /dev/null +++ b/games/tokyohook/config.c @@ -0,0 +1,112 @@ +#include +#include +#include + +#include "board/config.h" + +#include "gfxhook/config.h" + +#include "hooklib/config.h" +#include "hooklib/dvd.h" + +#include "platform/config.h" + +#include "tokyohook/config.h" + +void tokyo_dll_config_load( + struct tokyo_dll_config *cfg, + const wchar_t *filename) { + assert(cfg != NULL); + assert(filename != NULL); + + GetPrivateProfileStringW( + L"tokyoio", + L"path", + L"", + cfg->path, + _countof(cfg->path), + filename); +} + +void led15093_config_load(struct led15093_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + wchar_t tmpstr[16]; + + memset(cfg->board_number, ' ', sizeof(cfg->board_number)); + memset(cfg->chip_number, ' ', sizeof(cfg->chip_number)); + memset(cfg->boot_chip_number, ' ', sizeof(cfg->boot_chip_number)); + + cfg->enable = GetPrivateProfileIntW(L"led15093", L"enable", 1, filename); + cfg->port_no[0] = GetPrivateProfileIntW(L"led15093", L"portNo1", 0, filename); + cfg->port_no[1] = GetPrivateProfileIntW(L"led15093", L"portNo2", 0, filename); + cfg->high_baudrate = GetPrivateProfileIntW(L"led15093", L"highBaud", 0, filename); + cfg->fw_ver = GetPrivateProfileIntW(L"led15093", L"fwVer", 0x90, filename); + cfg->fw_sum = GetPrivateProfileIntW(L"led15093", L"fwSum", 0xAED9, filename); + + GetPrivateProfileStringW( + L"led15093", + L"boardNumber", + L"15093-04", + tmpstr, + _countof(tmpstr), + filename); + + size_t n = wcstombs(cfg->board_number, tmpstr, sizeof(cfg->board_number)); + for (int i = n; i < sizeof(cfg->board_number); i++) + { + cfg->board_number[i] = ' '; + } + + GetPrivateProfileStringW( + L"led15093", + L"chipNumber", + L"6704 ", + tmpstr, + _countof(tmpstr), + filename); + + n = wcstombs(cfg->chip_number, tmpstr, sizeof(cfg->chip_number)); + for (int i = n; i < sizeof(cfg->chip_number); i++) + { + cfg->chip_number[i] = ' '; + } + + GetPrivateProfileStringW( + L"led15093", + L"bootChipNumber", + L"6709 ", + tmpstr, + _countof(tmpstr), + filename); + + n = wcstombs(cfg->boot_chip_number, tmpstr, sizeof(cfg->boot_chip_number)); + for (int i = n; i < sizeof(cfg->boot_chip_number); i++) + { + cfg->boot_chip_number[i] = ' '; + } +} + +void zinput_config_load(struct zinput_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"zinput", L"enable", 1, filename); +} + +void tokyo_hook_config_load( + struct tokyo_hook_config *cfg, + const wchar_t *filename) { + assert(cfg != NULL); + assert(filename != NULL); + + platform_config_load(&cfg->platform, filename); + dvd_config_load(&cfg->dvd, filename); + io4_config_load(&cfg->io4, filename); + zinput_config_load(&cfg->zinput, filename); + led15093_config_load(&cfg->led15093, filename); + tokyo_dll_config_load(&cfg->dll, filename); +} diff --git a/games/tokyohook/config.h b/games/tokyohook/config.h new file mode 100644 index 0000000..54dc751 --- /dev/null +++ b/games/tokyohook/config.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include "board/config.h" +#include "board/led15093.h" + +#include "hooklib/dvd.h" + +#include "tokyohook/tokyo-dll.h" +#include "tokyohook/zinput.h" + +#include "platform/config.h" + +struct tokyo_hook_config { + struct platform_config platform; + struct dvd_config dvd; + struct io4_config io4; + struct led15093_config led15093; + struct zinput_config zinput; + struct tokyo_dll_config dll; +}; + +void tokyo_dll_config_load( + struct tokyo_dll_config *cfg, + const wchar_t *filename); + +void tokyo_hook_config_load( + struct tokyo_hook_config *cfg, + const wchar_t *filename); diff --git a/games/tokyohook/dllmain.c b/games/tokyohook/dllmain.c new file mode 100644 index 0000000..9c3a262 --- /dev/null +++ b/games/tokyohook/dllmain.c @@ -0,0 +1,144 @@ +/* + "Mario & Sonic at the Tokyo 2020 Olympics Arcade" (tokyo) hook + + Devices + + USB: 837-15257 "Type 4" I/O Board + COM1: 837-15093-04 LED Controller Board +*/ + +#include + +#include + +#include "board/io4.h" + +#include "hook/process.h" + +#include "hooklib/dvd.h" +#include "hooklib/serial.h" +#include "hooklib/spike.h" + +#include "tokyohook/config.h" +#include "tokyohook/io4.h" +#include "tokyohook/tokyo-dll.h" + +#include "platform/platform.h" + +#include "util/dprintf.h" +#include "util/env.h" + +static HMODULE tokyo_hook_mod; +static process_entry_t tokyo_startup; +static struct tokyo_hook_config tokyo_hook_cfg; + +static DWORD CALLBACK tokyo_pre_startup(void) +{ + HRESULT hr; + + dprintf("--- Begin tokyo_pre_startup ---\n"); + + /* Load config */ + + tokyo_hook_config_load(&tokyo_hook_cfg, get_config_path()); + + /* Hook Win32 APIs */ + + dvd_hook_init(&tokyo_hook_cfg.dvd, tokyo_hook_mod); + zinput_hook_init(&tokyo_hook_cfg.zinput); + serial_hook_init(); + + /* Initialize emulation hooks */ + + struct dipsw_config new_dipsw_config[8] = { + {L"Delivery Server", L"Server", L"Client"}, + {L"Cabinet ID Setting", L"ON", L"OFF"}, + {L"Cabinet ID Setting", L"ON", L"OFF"}, + }; + + // Set the system dip switch configuration + memcpy(tokyo_hook_cfg.platform.system.dipsw_config, new_dipsw_config, + sizeof(new_dipsw_config)); + + hr = platform_hook_init( + &tokyo_hook_cfg.platform, + "SDFV", + "ACA1", + tokyo_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + bool *dipsw = &tokyo_hook_cfg.platform.system.dipsw[0]; + unsigned int cabinet_id = 0; + + if (dipsw[0] == 1 && dipsw[1] == 0 && dipsw[2] == 0) { + cabinet_id = 1; // Server 1 + } else if (dipsw[0] == 0 && dipsw[1] == 1 && dipsw[2] == 0) { + cabinet_id = 2; // Client 2 + } else if (dipsw[0] == 0 && dipsw[1] == 0 && dipsw[2] == 1) { + cabinet_id = 3; // Client 3 + } else if (dipsw[0] == 0 && dipsw[1] == 1 && dipsw[2] == 1) { + cabinet_id = 4; // Client 4 + } else { + dprintf("Error: Invalid dip switch configuration!\n"); + } + + // Print the correct Cabinet ID if valid + if (cabinet_id > 0) { + dprintf("System: Cabinet ID %d\n", cabinet_id); + } + + hr = tokyo_dll_init(&tokyo_hook_cfg.dll, tokyo_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + unsigned int led_port_no[2] = {1, 0}; + hr = led15093_hook_init(&tokyo_hook_cfg.led15093, + tokyo_dll.led_init, tokyo_dll.led_set_leds, led_port_no); + + if (FAILED(hr)) { + return hr; + } + + hr = tokyo_io4_hook_init(&tokyo_hook_cfg.io4); + + if (FAILED(hr)) { + goto fail; + } + + /* Initialize debug helpers */ + + spike_hook_init(get_config_path()); + + dprintf("--- End tokyo_pre_startup ---\n"); + + /* Jump to EXE start address */ + + return tokyo_startup(); + +fail: + ExitProcess(EXIT_FAILURE); +} + +BOOL WINAPI DllMain(HMODULE mod, DWORD cause, void *ctx) +{ + HRESULT hr; + + if (cause != DLL_PROCESS_ATTACH) { + return TRUE; + } + + tokyo_hook_mod = mod; + + hr = process_hijack_startup(tokyo_pre_startup, &tokyo_startup); + + if (!SUCCEEDED(hr)) { + dprintf("Failed to hijack process startup: %x\n", (int) hr); + } + + return SUCCEEDED(hr); +} diff --git a/games/tokyohook/io4.c b/games/tokyohook/io4.c new file mode 100644 index 0000000..b2924c7 --- /dev/null +++ b/games/tokyohook/io4.c @@ -0,0 +1,163 @@ +#include + +#include +#include +#include + +#include "board/io4.h" + +#include "tokyohook/tokyo-dll.h" + +#include "util/dprintf.h" + +static HRESULT tokyo_io4_poll(void *ctx, struct io4_state *state); +static HRESULT tokyo_io4_write_gpio(uint8_t* payload, size_t len); + +static uint16_t coins; + +static const struct io4_ops tokyo_io4_ops = { + .poll = tokyo_io4_poll, + .write_gpio = tokyo_io4_write_gpio, +}; + +HRESULT tokyo_io4_hook_init(const struct io4_config *cfg) +{ + HRESULT hr; + + assert(tokyo_dll.init != NULL); + + hr = io4_hook_init(cfg, &tokyo_io4_ops, NULL); + + if (FAILED(hr)) { + return hr; + } + + return tokyo_dll.init(); +} + +static HRESULT tokyo_io4_poll(void *ctx, struct io4_state *state) +{ + uint8_t opbtn; + uint8_t gamebtn; + uint8_t sense; + HRESULT hr; + + assert(tokyo_dll.get_opbtns != NULL); + assert(tokyo_dll.get_gamebtns != NULL); + assert(tokyo_dll.get_sensors != NULL); + + memset(state, 0, sizeof(*state)); + + opbtn = 0; + gamebtn = 0; + sense = 0; + + tokyo_dll.get_opbtns(&opbtn); + tokyo_dll.get_gamebtns(&gamebtn); + tokyo_dll.get_sensors(&sense); + + if (opbtn & TOKYO_IO_OPBTN_TEST) { + state->buttons[0] |= IO4_BUTTON_TEST; + } + + if (opbtn & TOKYO_IO_OPBTN_SERVICE) { + state->buttons[0] |= IO4_BUTTON_SERVICE; + } + + if (opbtn & TOKYO_IO_OPBTN_COIN) { + coins++; + } + state->chutes[0] = coins << 8; + + /* Update gamebtns */ + + if (gamebtn & TOKYO_IO_GAMEBTN_BLUE) { + state->buttons[0] |= 1 << 1; + } + + if (gamebtn & TOKYO_IO_GAMEBTN_YELLOW) { + state->buttons[0] |= 1 << 0; + } + + if (gamebtn & TOKYO_IO_GAMEBTN_RED) { + state->buttons[0] |= 1 << 15; + } + + /* Update sensors */ + + // Invert the logic so that it's active high + if (!(sense & TOKYO_IO_SENSE_FOOT_LEFT)) { + state->buttons[0] |= 1 << 13; + } + + if (!(sense & TOKYO_IO_SENSE_FOOT_RIGHT)) { + state->buttons[1] |= 1 << 13; + } + + if (sense & TOKYO_IO_SENSE_JUMP_1) { + state->buttons[0] |= 1 << 12; + } + + if (sense & TOKYO_IO_SENSE_JUMP_2) { + state->buttons[1] |= 1 << 12; + } + + if (sense & TOKYO_IO_SENSE_JUMP_3) { + state->buttons[0] |= 1 << 11; + } + + if (sense & TOKYO_IO_SENSE_JUMP_4) { + state->buttons[1] |= 1 << 11; + } + + if (sense & TOKYO_IO_SENSE_JUMP_5) { + state->buttons[0] |= 1 << 10; + } + + if (sense & TOKYO_IO_SENSE_JUMP_6) { + state->buttons[1] |= 1 << 10; + } + + return S_OK; +} + +static HRESULT tokyo_io4_write_gpio(uint8_t* payload, size_t len) +{ + // Just fast fail if there aren't enough bytes in the payload + if (len < 3) + return S_OK; + + // This command is used for lights in Mario & Sonic at the Tokyo 2020 Olympics + // Arcade, but it only contains button lights, and only in the first 3 bytes of + // the payload; everything else is padding to make the payload 62 bytes. The + // rest of the cabinet lights and the side button lights are handled separately, + // by the 15093 lights controller. + uint32_t lights_data = (uint32_t) ((uint8_t)(payload[0]) << 24 | + (uint8_t)(payload[1]) << 16 | + (uint8_t)(payload[2]) << 8); + + // Since Sega uses an odd ordering for the first part of the bitfield, + // let's normalize the data and just send over bytes for the receiver + // to interpret as RGB values. + uint8_t rgb_out[5 * 3] = { + lights_data & TOKYO_IO_LED_LEFT_BLUE ? 0xFF : 0x00, + lights_data & TOKYO_IO_LED_CENTER_YELLOW ? 0xFF : 0x00, + lights_data & TOKYO_IO_LED_RIGHT_RED ? 0xFF : 0x00, + lights_data & TOKYO_IO_LED_CONTROL_LEFT_R ? 0xFF : 0x00, + lights_data & TOKYO_IO_LED_CONTROL_LEFT_G ? 0xFF : 0x00, + lights_data & TOKYO_IO_LED_CONTROL_LEFT_B ? 0xFF : 0x00, + lights_data & TOKYO_IO_LED_CONTROL_RIGHT_R ? 0xFF : 0x00, + lights_data & TOKYO_IO_LED_CONTROL_RIGHT_G ? 0xFF : 0x00, + lights_data & TOKYO_IO_LED_CONTROL_RIGHT_B ? 0xFF : 0x00, + lights_data & TOKYO_IO_LED_FLOOR_LEFT_R ? 0xFF : 0x00, + lights_data & TOKYO_IO_LED_FLOOR_LEFT_G ? 0xFF : 0x00, + lights_data & TOKYO_IO_LED_FLOOR_LEFT_B ? 0xFF : 0x00, + lights_data & TOKYO_IO_LED_FLOOR_RIGHT_R ? 0xFF : 0x00, + lights_data & TOKYO_IO_LED_FLOOR_RIGHT_G ? 0xFF : 0x00, + lights_data & TOKYO_IO_LED_FLOOR_RIGHT_B ? 0xFF : 0x00, + }; + + tokyo_dll.led_set_leds(1, rgb_out); + + return S_OK; +} diff --git a/games/tokyohook/io4.h b/games/tokyohook/io4.h new file mode 100644 index 0000000..508d802 --- /dev/null +++ b/games/tokyohook/io4.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "board/io4.h" + +HRESULT tokyo_io4_hook_init(const struct io4_config *cfg); diff --git a/games/tokyohook/meson.build b/games/tokyohook/meson.build new file mode 100644 index 0000000..0bbee19 --- /dev/null +++ b/games/tokyohook/meson.build @@ -0,0 +1,31 @@ +shared_library( + 'tokyohook', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + vs_module_defs : 'tokyohook.def', + dependencies : [ + capnhook.get_variable('hook_dep'), + capnhook.get_variable('hooklib_dep'), + xinput_lib, + ], + link_with : [ + aimeio_lib, + board_lib, + hooklib_lib, + tokyoio_lib, + platform_lib, + util_lib, + ], + sources : [ + 'config.c', + 'config.h', + 'dllmain.c', + 'io4.c', + 'io4.h', + 'zinput.c', + 'zinput.h', + 'tokyo-dll.c', + 'tokyo-dll.h', + ], +) diff --git a/games/tokyohook/tokyo-dll.c b/games/tokyohook/tokyo-dll.c new file mode 100644 index 0000000..0187205 --- /dev/null +++ b/games/tokyohook/tokyo-dll.c @@ -0,0 +1,115 @@ +#include + +#include +#include + +#include "tokyohook/tokyo-dll.h" + +#include "util/dll-bind.h" +#include "util/dprintf.h" + +const struct dll_bind_sym tokyo_dll_syms[] = { + { + .sym = "tokyo_io_init", + .off = offsetof(struct tokyo_dll, init), + }, { + .sym = "tokyo_io_get_opbtns", + .off = offsetof(struct tokyo_dll, get_opbtns), + }, { + .sym = "tokyo_io_get_gamebtns", + .off = offsetof(struct tokyo_dll, get_gamebtns), + }, { + .sym = "tokyo_io_get_sensors", + .off = offsetof(struct tokyo_dll, get_sensors), + }, { + .sym = "tokyo_io_led_init", + .off = offsetof(struct tokyo_dll, led_init), + }, { + .sym = "tokyo_io_led_set_colors", + .off = offsetof(struct tokyo_dll, led_set_leds), + } +}; + +struct tokyo_dll tokyo_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 tokyo_dll_init(const struct tokyo_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("Tokyo IO: Failed to load IO DLL: %lx: %S\n", + hr, + cfg->path); + + goto end; + } + + dprintf("Tokyo IO: Using custom IO DLL: %S\n", cfg->path); + src = owned; + } else { + owned = NULL; + src = self; + } + + get_api_version = (void *) GetProcAddress(src, "tokyo_io_get_api_version"); + + if (get_api_version != NULL) { + tokyo_dll.api_version = get_api_version(); + } else { + tokyo_dll.api_version = 0x0100; + dprintf("Custom IO DLL does not expose tokyo_io_get_api_version, " + "assuming API version 1.0.\n" + "Please ask the developer to update their DLL.\n"); + } + + if (tokyo_dll.api_version >= 0x0200) { + hr = E_NOTIMPL; + dprintf("Tokyo IO: Custom IO DLL implements an unsupported " + "API version (%#04x). Please update Segatools.\n", + tokyo_dll.api_version); + + goto end; + } + + sym = tokyo_dll_syms; + hr = dll_bind(&tokyo_dll, src, &sym, _countof(tokyo_dll_syms)); + + if (FAILED(hr)) { + if (src != self) { + dprintf("Tokyo 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/games/tokyohook/tokyo-dll.h b/games/tokyohook/tokyo-dll.h new file mode 100644 index 0000000..9bfeb5a --- /dev/null +++ b/games/tokyohook/tokyo-dll.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include "tokyoio/tokyoio.h" + +struct tokyo_dll { + uint16_t api_version; + HRESULT (*init)(void); + void (*get_opbtns)(uint8_t *opbtn); + void (*get_gamebtns)(uint8_t *gamebtn); + void (*get_sensors)(uint8_t *sense); + HRESULT (*gpio_out)(uint32_t state); + HRESULT (*led_init)(void); + void (*led_set_leds)(uint8_t board, uint8_t *rgb); +}; + +struct tokyo_dll_config { + wchar_t path[MAX_PATH]; +}; + +extern struct tokyo_dll tokyo_dll; + +HRESULT tokyo_dll_init(const struct tokyo_dll_config *cfg, HINSTANCE self); diff --git a/games/tokyohook/tokyohook.def b/games/tokyohook/tokyohook.def new file mode 100644 index 0000000..d60bf17 --- /dev/null +++ b/games/tokyohook/tokyohook.def @@ -0,0 +1,20 @@ +LIBRARY tokyohook + +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 + tokyo_io_get_api_version + tokyo_io_init + tokyo_io_get_opbtns + tokyo_io_get_gamebtns + tokyo_io_get_sensors + tokyo_io_led_init + tokyo_io_led_set_colors diff --git a/games/tokyohook/zinput.c b/games/tokyohook/zinput.c new file mode 100644 index 0000000..2152d6c --- /dev/null +++ b/games/tokyohook/zinput.c @@ -0,0 +1,118 @@ +#include +#include +#include + +#include +#include + +#include "tokyohook/config.h" +#include "tokyohook/zinput.h" + +#include "hook/table.h" + +#include "util/lib.h" +#include "util/dprintf.h" + +HRESULT WINAPI hook_DirectInput8Create( + HINSTANCE hinst, + DWORD dwVersion, + REFIID riidltf, + LPVOID *ppvOut, + LPUNKNOWN punkOuter); + +static HRESULT WINAPI hook_EnumDevices( + IDirectInput8W *self, + DWORD dwDevType, + LPDIENUMDEVICESCALLBACKW lpCallback, + LPVOID pvRef, + DWORD dwFlags); + +static unsigned long WINAPI hook_AddRef(IUnknown *self); +static unsigned long WINAPI hook_Release(IUnknown *self); + +static const IDirectInput8WVtbl api_vtbl = { + .EnumDevices = hook_EnumDevices, + .AddRef = (void *) hook_AddRef, + .Release = (void *) hook_Release, +}; + +static const IDirectInput8W api = { (void *) &api_vtbl }; + +static const struct hook_symbol zinput_hook_syms[] = { + { + .name = "DirectInput8Create", + .patch = hook_DirectInput8Create, + .link = NULL, + } +}; + +HRESULT zinput_hook_init(struct zinput_config *cfg) +{ + wchar_t *module_path; + wchar_t *file_name; + + assert(cfg != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + module_path = module_file_name(NULL); + + if (module_path != NULL) { + file_name = PathFindFileNameW(module_path); + + free(module_path); + module_path = NULL; + + _wcslwr(file_name); + + if (wcsstr(file_name, L"amdaemon") != NULL) { + // dprintf("Executable filename contains 'amdaemon', disabling zinput\n"); + return S_OK; + } + } + + hook_table_apply( + NULL, + "dinput8.dll", + zinput_hook_syms, + _countof(zinput_hook_syms)); + + return S_OK; +} + +HRESULT WINAPI hook_DirectInput8Create( + HINSTANCE hinst, + DWORD dwVersion, + REFIID riidltf, + LPVOID *ppvOut, + LPUNKNOWN punkOuter) +{ + dprintf("ZInput: Blocking built-in DirectInput support\n"); + *ppvOut = (void *) &api; + + return DI_OK; +} + +static HRESULT WINAPI hook_EnumDevices( + IDirectInput8W *self, + DWORD dwDevType, + LPDIENUMDEVICESCALLBACKW lpCallback, + LPVOID pvRef, + DWORD dwFlags) +{ + dprintf("ZInput: %s\n", __func__); + + return DI_OK; +} + +static unsigned long WINAPI hook_AddRef(IUnknown *self) +{ + return 1; +} + +static unsigned long WINAPI hook_Release(IUnknown *self) +{ + return 1; +} diff --git a/games/tokyohook/zinput.h b/games/tokyohook/zinput.h new file mode 100644 index 0000000..13a46cd --- /dev/null +++ b/games/tokyohook/zinput.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#include + +struct zinput_config { + bool enable; +}; + +HRESULT zinput_hook_init(struct zinput_config *cfg); diff --git a/games/tokyoio/backend.h b/games/tokyoio/backend.h new file mode 100644 index 0000000..52fee61 --- /dev/null +++ b/games/tokyoio/backend.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +#include "tokyoio/tokyoio.h" + +struct tokyo_io_backend { + void (*get_gamebtns)(uint8_t *gamebtn); + void (*get_sensors)(uint8_t *sense); +}; diff --git a/games/tokyoio/config.c b/games/tokyoio/config.c new file mode 100644 index 0000000..2522154 --- /dev/null +++ b/games/tokyoio/config.c @@ -0,0 +1,55 @@ +#include + +#include +#include +#include +#include + +#include "tokyoio/config.h" + + +void tokyo_kb_config_load( + struct tokyo_kb_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + /* Load game button keyboard bindings */ + cfg->vk_push_left_b = GetPrivateProfileIntW(L"keyboard", L"leftBlue", 'A', filename); + cfg->vk_push_center_y = GetPrivateProfileIntW(L"keyboard", L"centerYellow", 'S', filename); + cfg->vk_push_right_r = GetPrivateProfileIntW(L"keyboard", L"rightRed", 'D', filename); + + /* Load sensor keyboard bindings */ + cfg->vk_foot_l = GetPrivateProfileIntW(L"keyboard", L"footLeft", VK_LEFT, filename); + cfg->vk_foot_r = GetPrivateProfileIntW(L"keyboard", L"footRight", VK_RIGHT, filename); + cfg->vk_jump_1 = GetPrivateProfileIntW(L"keyboard", L"jump1", 'Z', filename); + cfg->vk_jump_2 = GetPrivateProfileIntW(L"keyboard", L"jump2", 'X', filename); + cfg->vk_jump_3 = GetPrivateProfileIntW(L"keyboard", L"jump3", 'C', filename); + cfg->vk_jump_4 = GetPrivateProfileIntW(L"keyboard", L"jump4", 'B', filename); + cfg->vk_jump_5 = GetPrivateProfileIntW(L"keyboard", L"jump5", 'N', filename); + cfg->vk_jump_6 = GetPrivateProfileIntW(L"keyboard", L"jump6", 'M', filename); + cfg->vk_jump_all = GetPrivateProfileIntW(L"keyboard", L"jumpAll", VK_SPACE, filename); +} + +void tokyo_io_config_load( + struct tokyo_io_config *cfg, + const wchar_t *filename) +{ + 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); + + GetPrivateProfileStringW( + L"io4", + L"mode", + L"xinput", + cfg->mode, + _countof(cfg->mode), + filename); + + tokyo_kb_config_load(&cfg->kb, filename); +} diff --git a/games/tokyoio/config.h b/games/tokyoio/config.h new file mode 100644 index 0000000..839b2c3 --- /dev/null +++ b/games/tokyoio/config.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +#include + +struct tokyo_kb_config { + uint8_t vk_push_left_b; + uint8_t vk_push_center_y; + uint8_t vk_push_right_r; + uint8_t vk_foot_l; + uint8_t vk_foot_r; + uint8_t vk_jump_1; + uint8_t vk_jump_2; + uint8_t vk_jump_3; + uint8_t vk_jump_4; + uint8_t vk_jump_5; + uint8_t vk_jump_6; + uint8_t vk_jump_all; +}; + +struct tokyo_io_config { + uint8_t vk_test; + uint8_t vk_service; + uint8_t vk_coin; + wchar_t mode[9]; + + struct tokyo_kb_config kb; +}; + +void tokyo_io_config_load( + struct tokyo_io_config *cfg, + const wchar_t *filename); diff --git a/games/tokyoio/dllmain.c b/games/tokyoio/dllmain.c new file mode 100644 index 0000000..18d0039 --- /dev/null +++ b/games/tokyoio/dllmain.c @@ -0,0 +1,136 @@ +#include + +#include +#include +#include + +#include "tokyoio/backend.h" +#include "tokyoio/config.h" +#include "tokyoio/kb.h" +#include "tokyoio/tokyoio.h" +#include "tokyoio/xi.h" + +#include "util/dprintf.h" +#include "util/str.h" +#include "util/env.h" + +static struct tokyo_io_config tokyo_io_cfg; +static const struct tokyo_io_backend *tokyo_io_backend; +static bool tokyo_io_coin; + +uint16_t tokyo_io_get_api_version(void) +{ + return 0x0100; +} + +HRESULT tokyo_io_init(void) +{ + HINSTANCE inst; + HRESULT hr; + + assert(tokyo_io_backend == NULL); + + inst = GetModuleHandleW(NULL); + + if (inst == NULL) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("GetModuleHandleW failed: %lx\n", hr); + + return hr; + } + + tokyo_io_config_load(&tokyo_io_cfg, get_config_path()); + + if (wstr_ieq(tokyo_io_cfg.mode, L"keyboard")) { + hr = tokyo_kb_init(&tokyo_io_cfg.kb, &tokyo_io_backend); + } else if (wstr_ieq(tokyo_io_cfg.mode, L"xinput")) { + hr = tokyo_xi_init(&tokyo_io_backend); + } else { + hr = E_INVALIDARG; + dprintf("IDAC IO: Invalid IO mode \"%S\", use keyboard or xinput\n", + tokyo_io_cfg.mode); + } + + return hr; +} + +void tokyo_io_get_opbtns(uint8_t *opbtn_out) +{ + uint8_t opbtn; + + assert(tokyo_io_backend != NULL); + assert(opbtn_out != NULL); + + opbtn = 0; + + /* Common operator buttons, not backend-specific */ + + if (GetAsyncKeyState(tokyo_io_cfg.vk_test) & 0x8000) { + opbtn |= TOKYO_IO_OPBTN_TEST; + } + + if (GetAsyncKeyState(tokyo_io_cfg.vk_service) & 0x8000) { + opbtn |= TOKYO_IO_OPBTN_SERVICE; + } + + if (GetAsyncKeyState(tokyo_io_cfg.vk_coin) & 0x8000) { + if (!tokyo_io_coin) { + tokyo_io_coin = true; + opbtn |= TOKYO_IO_OPBTN_COIN; + } + } else { + tokyo_io_coin = false; + } + + *opbtn_out = opbtn; +} + + +void tokyo_io_get_gamebtns(uint8_t *gamebtn_out) +{ + assert(tokyo_io_backend != NULL); + assert(gamebtn_out != NULL); + + tokyo_io_backend->get_gamebtns(gamebtn_out); +} + +void tokyo_io_get_sensors(uint8_t *sense_out) +{ + assert(sense_out != NULL); + assert(tokyo_io_backend != NULL); + + tokyo_io_backend->get_sensors(sense_out); +} + +HRESULT tokyo_io_led_init(void) +{ + return S_OK; +} + +void tokyo_io_led_set_colors(uint8_t board, uint8_t *rgb) +{ +#if 0 + if (board == 0) { + dprintf("Board 0:\n"); + // Change GRB order to RGB order + for (int i = 0; i < 27; i++) { + dprintf("Tokyo LED: MONITOR LEFT: %02X %02X %02X\n", rgb[i * 3 + 1], rgb[i * 3], rgb[i * 3 + 2]); + } + + for (int i = 27; i < 54; i++) { + dprintf("Tokyo LED: MONITOR RIGHT: %d, %02X %02X %02X\n", i, rgb[i * 3 + 1], rgb[i * 3], rgb[i * 3 + 2]); + } + } else { + dprintf("Board 1:\n"); + dprintf("Tokyo LED: LEFT BLUE: %02X\n", rgb[0]); + dprintf("Tokyo LED: CENTER YELLOW: %02X\n", rgb[1]); + dprintf("Tokyo LED: RIGHT RED: %02X\n", rgb[2]); + dprintf("Tokyo LED: CONTROL LEFT: %02X %02X %02X\n", rgb[3], rgb[4], rgb[5]); + dprintf("Tokyo LED: CONTROL RIGHT: %02X %02X %02X\n", rgb[6], rgb[7], rgb[8]); + dprintf("Tokyo LED: FLOOR LEFT: %02X %02X %02X\n", rgb[9], rgb[10], rgb[11]); + dprintf("Tokyo LED: FLOOR RIGHT: %02X %02X %02X\n", rgb[12], rgb[13], rgb[14]); + } +#endif + + return; +} diff --git a/games/tokyoio/kb.c b/games/tokyoio/kb.c new file mode 100644 index 0000000..3b3c34d --- /dev/null +++ b/games/tokyoio/kb.c @@ -0,0 +1,135 @@ +#include + +#include +#include +#include + +#include "tokyoio/backend.h" +#include "tokyoio/config.h" +#include "tokyoio/kb.h" +#include "tokyoio/tokyoio.h" + +#include "util/dprintf.h" +#include "util/str.h" + +static HRESULT tokyo_kb_config_apply(const struct tokyo_kb_config *cfg); + +static void tokyo_kb_get_gamebtns(uint8_t *gamebtn_out); +static void tokyo_kb_get_sensors(uint8_t *sense_out); + +static const struct tokyo_io_backend tokyo_kb_backend = { + .get_gamebtns = tokyo_kb_get_gamebtns, + .get_sensors = tokyo_kb_get_sensors, +}; + +static struct tokyo_kb_config tokyo_kb_cfg; + +HRESULT tokyo_kb_init(const struct tokyo_kb_config *cfg, const struct tokyo_io_backend **backend) +{ + HRESULT hr; + + assert(cfg != NULL); + assert(backend != NULL); + + hr = tokyo_kb_config_apply(cfg); + + if (FAILED(hr)) { + return hr; + } + + dprintf("TokyoIO: Using keyboard input\n"); + *backend = &tokyo_kb_backend; + return S_OK; +} + +static HRESULT tokyo_kb_config_apply(const struct tokyo_kb_config *cfg) +{ + tokyo_kb_cfg = *cfg; + + return S_OK; +} + +static void tokyo_kb_get_gamebtns(uint8_t *gamebtn_out) +{ + uint8_t gamebtn; + + assert(gamebtn_out != NULL); + + gamebtn = 0; + + /* PUSH BUTTON inputs */ + + if (GetAsyncKeyState(tokyo_kb_cfg.vk_push_left_b) & 0x8000) { + gamebtn |= TOKYO_IO_GAMEBTN_BLUE; + } + + if (GetAsyncKeyState(tokyo_kb_cfg.vk_push_center_y) & 0x8000) { + gamebtn |= TOKYO_IO_GAMEBTN_YELLOW; + } + + if (GetAsyncKeyState(tokyo_kb_cfg.vk_push_right_r) & 0x8000) { + gamebtn |= TOKYO_IO_GAMEBTN_RED; + } + + *gamebtn_out = gamebtn; +} + +static void tokyo_kb_get_sensors(uint8_t *sense_out) +{ + uint8_t sense; + + assert(sense_out != NULL); + + sense = 0; + + /* FOOT SENSOR inputs */ + + if (GetAsyncKeyState(tokyo_kb_cfg.vk_foot_l) & 0x8000) { + sense |= TOKYO_IO_SENSE_FOOT_LEFT; + } + + if (GetAsyncKeyState(tokyo_kb_cfg.vk_foot_r) & 0x8000) { + sense |= TOKYO_IO_SENSE_FOOT_RIGHT; + } + + /* JUMP SENSOR inputs */ + + if (GetAsyncKeyState(tokyo_kb_cfg.vk_jump_1) & 0x8000) { + sense |= (TOKYO_IO_SENSE_FOOT_LEFT + TOKYO_IO_SENSE_FOOT_RIGHT + + TOKYO_IO_SENSE_JUMP_1); + } + + if (GetAsyncKeyState(tokyo_kb_cfg.vk_jump_2) & 0x8000) { + sense |= (TOKYO_IO_SENSE_FOOT_LEFT + TOKYO_IO_SENSE_FOOT_RIGHT + + TOKYO_IO_SENSE_JUMP_2); + } + + if (GetAsyncKeyState(tokyo_kb_cfg.vk_jump_3) & 0x8000) { + sense |= (TOKYO_IO_SENSE_FOOT_LEFT + TOKYO_IO_SENSE_FOOT_RIGHT + + TOKYO_IO_SENSE_JUMP_3); + } + + if (GetAsyncKeyState(tokyo_kb_cfg.vk_jump_4) & 0x8000) { + sense |= (TOKYO_IO_SENSE_FOOT_LEFT + TOKYO_IO_SENSE_FOOT_RIGHT + + TOKYO_IO_SENSE_JUMP_4); + } + + if (GetAsyncKeyState(tokyo_kb_cfg.vk_jump_5) & 0x8000) { + sense |= (TOKYO_IO_SENSE_FOOT_LEFT + TOKYO_IO_SENSE_FOOT_RIGHT + + TOKYO_IO_SENSE_JUMP_5); + } + + if (GetAsyncKeyState(tokyo_kb_cfg.vk_jump_6) & 0x8000) { + sense |= (TOKYO_IO_SENSE_FOOT_LEFT + TOKYO_IO_SENSE_FOOT_RIGHT + + TOKYO_IO_SENSE_JUMP_6); + } + + if (GetAsyncKeyState(tokyo_kb_cfg.vk_jump_all) & 0x8000) { + sense |= (TOKYO_IO_SENSE_FOOT_LEFT+ TOKYO_IO_SENSE_FOOT_RIGHT + + TOKYO_IO_SENSE_JUMP_1 + TOKYO_IO_SENSE_JUMP_2 + + TOKYO_IO_SENSE_JUMP_3 + TOKYO_IO_SENSE_JUMP_4 + + TOKYO_IO_SENSE_JUMP_5 + TOKYO_IO_SENSE_JUMP_6); + } + + *sense_out = sense; +} diff --git a/games/tokyoio/kb.h b/games/tokyoio/kb.h new file mode 100644 index 0000000..2a6691f --- /dev/null +++ b/games/tokyoio/kb.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +#include "tokyoio/backend.h" +#include "tokyoio/config.h" + +HRESULT tokyo_kb_init( + const struct tokyo_kb_config *cfg, + const struct tokyo_io_backend **backend); diff --git a/games/tokyoio/meson.build b/games/tokyoio/meson.build new file mode 100644 index 0000000..9e9e9fe --- /dev/null +++ b/games/tokyoio/meson.build @@ -0,0 +1,20 @@ +tokyoio_lib = static_library( + 'tokyoio', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + dependencies : [ + xinput_lib, + ], + sources : [ + 'backend.h', + 'config.c', + 'config.h', + 'dllmain.c', + 'tokyoio.h', + 'kb.c', + 'kb.h', + 'xi.c', + 'xi.h', + ], +) diff --git a/games/tokyoio/tokyoio.h b/games/tokyoio/tokyoio.h new file mode 100644 index 0000000..6edec63 --- /dev/null +++ b/games/tokyoio/tokyoio.h @@ -0,0 +1,139 @@ +#pragma once + +#include + +#include + +enum { + TOKYO_IO_OPBTN_TEST = 0x01, + TOKYO_IO_OPBTN_SERVICE = 0x02, + TOKYO_IO_OPBTN_COIN = 0x04, +}; + +enum { + TOKYO_IO_GAMEBTN_BLUE = 0x01, + TOKYO_IO_GAMEBTN_YELLOW = 0x02, + TOKYO_IO_GAMEBTN_RED = 0x04, +}; + +enum { + TOKYO_IO_SENSE_FOOT_LEFT = 0x01, + TOKYO_IO_SENSE_FOOT_RIGHT = 0x02, + TOKYO_IO_SENSE_JUMP_1 = 0x04, + TOKYO_IO_SENSE_JUMP_2 = 0x08, + TOKYO_IO_SENSE_JUMP_3 = 0x10, + TOKYO_IO_SENSE_JUMP_4 = 0x20, + TOKYO_IO_SENSE_JUMP_5 = 0x40, + TOKYO_IO_SENSE_JUMP_6 = 0x80, +}; + +enum { + /* These are the bitmasks to use when checking which + lights are triggered on incoming IO4 GPIO writes. */ + TOKYO_IO_LED_LEFT_BLUE = 1 << 31, + TOKYO_IO_LED_CENTER_YELLOW = 1 << 30, + TOKYO_IO_LED_RIGHT_RED = 1 << 29, + TOKYO_IO_LED_CONTROL_LEFT_R = 1 << 25, + TOKYO_IO_LED_CONTROL_LEFT_G = 1 << 24, + TOKYO_IO_LED_CONTROL_LEFT_B = 1 << 23, + TOKYO_IO_LED_CONTROL_RIGHT_R = 1 << 22, + TOKYO_IO_LED_CONTROL_RIGHT_G = 1 << 21, + TOKYO_IO_LED_CONTROL_RIGHT_B = 1 << 20, + TOKYO_IO_LED_FLOOR_LEFT_R = 1 << 19, + TOKYO_IO_LED_FLOOR_LEFT_G = 1 << 18, + TOKYO_IO_LED_FLOOR_LEFT_B = 1 << 17, + TOKYO_IO_LED_FLOOR_RIGHT_R = 1 << 16, + TOKYO_IO_LED_FLOOR_RIGHT_G = 1 << 15, + TOKYO_IO_LED_FLOOR_RIGHT_B = 1 << 14, +}; + +/* Get the version of the Mario & Sonic at the Olympic Games Tokyo 2020 Arcade + Edition 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 tokyo_io_get_api_version(void); + +/* Initialize the IO DLL. This is the second function that will be called on + your DLL, after tokyo_io_get_api_version. + + All subsequent calls to this API may originate from arbitrary threads. + + Minimum API version: 0x0100 */ + +HRESULT tokyo_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 tokyo_io_poll(void); + +/* Get the state of the cabinet's operator buttons as of the last poll. See + TOKYO_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 tokyo_io_get_opbtns(uint8_t *opbtn); + +/* Get the state of the cabinet's gameplay buttons as of the last poll. See + TOKYO_IO_GAMEBTN enum above: this contains bit mask definitions for button + states returned in *gamebtn. All buttons are active-high. + + Minimum API version: 0x0100 */ + +void tokyo_io_get_gamebtns(uint8_t *gamebtn); + +/* Get the state of the cabinet's gameplay buttons as of the last poll. See + TOKYO_IO_SENSE enum above: this contains bit mask definitions for button + states returned in *sense. All buttons are active-high. + + Minimum API version: 0x0100 */ + +void tokyo_io_get_sensors(uint8_t *sense); + +/* Initialize LED emulation. This function will be called before any + other tokyo_io_led_*() function calls. + + 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 tokyo_io_led_init(void); + +/* Update the RGB LEDs. rgb is a pointer to an array of up to 54 * 3 = 162 bytes. + + Mario & Sonic at the Tokyo 2020 Olympics Arcade uses one board with 15 LEDs for + all buttons, control panel, and floor LEDs. Board 1 is just used for the + left and right monitor LEDs. + + Board 0 has 54 LEDs (GRB order): + [0]-[26]: left monitor LEDs + [27]-[53]: right monitor LEDs + + Board 1 has 15 LEDs (RGB order): + [0]: left blue LED + [1]: center yellow LED + [2]: right red LED + [3]-[5]: left control panel LEDs + [6]-[8]: right control panel LEDs + [9]-[11]: left floor LEDs + [12]-[14]: right floor LEDs + + Each rgb value is comprised of 3 bytes in G,R,B order for board 0 and R,G,B + order for board 1. The tricky part is that the board 0 is called from app and + the board 1 is called from amdaemon. So the library must be able to handle both + calls, using shared memory f.e. This is up to the developer to decide how to + handle this, recommended way is to use the amdaemon process as the main one + and the app process as a sub one. + + Minimum API version: 0x0100 */ + +void tokyo_io_led_set_colors(uint8_t board, uint8_t *rgb); diff --git a/games/tokyoio/xi.c b/games/tokyoio/xi.c new file mode 100644 index 0000000..b6b5749 --- /dev/null +++ b/games/tokyoio/xi.c @@ -0,0 +1,130 @@ +#include +#include +#include + +#include +#include +#include + +#include "tokyoio/backend.h" +#include "tokyoio/config.h" +#include "tokyoio/tokyoio.h" +#include "tokyoio/xi.h" + +#include "util/dprintf.h" +#include "util/str.h" + +static void tokyo_xi_get_gamebtns(uint8_t *gamebtn_out); +static void tokyo_xi_get_sensors(uint8_t *sense_out); + +static const struct tokyo_io_backend tokyo_xi_backend = { + .get_gamebtns = tokyo_xi_get_gamebtns, + .get_sensors = tokyo_xi_get_sensors, +}; + +HRESULT tokyo_xi_init(const struct tokyo_io_backend **backend) +{ + wchar_t dll_path[MAX_PATH]; + HMODULE xinput; + HRESULT hr; + UINT path_pos; + + assert(backend != NULL); + + dprintf("TokyoIO: IO4: Using XInput controller\n"); + *backend = &tokyo_xi_backend; + return S_OK; +} + +static void tokyo_xi_get_gamebtns(uint8_t *gamebtn_out) +{ + uint8_t gamebtn; + + assert(gamebtn_out != NULL); + + gamebtn = 0; + + XINPUT_STATE xi; + WORD xb; + + memset(&xi, 0, sizeof(xi)); + XInputGetState(0, &xi); + xb = xi.Gamepad.wButtons; + + /* PUSH BUTTON inputs */ + + if ((xb & XINPUT_GAMEPAD_X) || (xb & XINPUT_GAMEPAD_DPAD_LEFT)) { + gamebtn |= TOKYO_IO_GAMEBTN_BLUE; + } + + if (xb & XINPUT_GAMEPAD_Y || (xb & XINPUT_GAMEPAD_A)) { + gamebtn |= TOKYO_IO_GAMEBTN_YELLOW; + } + + if ((xb & XINPUT_GAMEPAD_B) || (xb & XINPUT_GAMEPAD_DPAD_RIGHT)) { + gamebtn |= TOKYO_IO_GAMEBTN_RED; + } + + *gamebtn_out = gamebtn; +} + +static void tokyo_xi_get_sensors(uint8_t *sense_out) +{ + uint8_t sense; + + XINPUT_STATE xi; + WORD xb; + BYTE xt_l; + BYTE xt_r; + + assert(sense_out != NULL); + + sense = 0; + + memset(&xi, 0, sizeof(xi)); + XInputGetState(0, &xi); + xb = xi.Gamepad.wButtons; + xt_l = xi.Gamepad.bLeftTrigger; + xt_r = xi.Gamepad.bRightTrigger; + + float xt_l_f = xt_l / 255.0f; + float xt_r_f = xt_r / 255.0f; + + // Normalize both triggers to 0..1 and find the max directly + float trigger = fmaxf(xt_l_f, xt_r_f); + + const int max_jump_levels = 6; + float jump_threshold = 1.0f / max_jump_levels; + + /* FOOT SENSOR inputs */ + + // Determine if both foot sensors should be set + bool left_active = xt_l_f > jump_threshold; + bool right_active = xt_r_f > jump_threshold; + + // Set foot sensors based on individual trigger activity + if (left_active) { + sense |= TOKYO_IO_SENSE_FOOT_LEFT; + } + if (right_active) { + sense |= TOKYO_IO_SENSE_FOOT_RIGHT; + } + + /* JUMP SENSOR inputs */ + + // If both triggers are active, set jump levels and both foot sensors + if (left_active && right_active) { + float trigger_avg = (xt_l_f + xt_r_f) / 2.0f; + + // Calculate the appropriate jump level + for (int i = 1; i <= max_jump_levels; ++i) { + if (trigger_avg >= i * jump_threshold) { + sense |= (TOKYO_IO_SENSE_JUMP_1 << (i - 1)); + } else { + break; + } + } + } + + *sense_out = sense; +} diff --git a/games/tokyoio/xi.h b/games/tokyoio/xi.h new file mode 100644 index 0000000..368fff3 --- /dev/null +++ b/games/tokyoio/xi.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +#include "tokyoio/backend.h" +#include "tokyoio/config.h" + +HRESULT tokyo_xi_init(const struct tokyo_io_backend **backend); diff --git a/gfxhook/gfx.c b/gfxhook/gfx.c deleted file mode 100644 index 1af3647..0000000 --- a/gfxhook/gfx.c +++ /dev/null @@ -1,48 +0,0 @@ -#include - -#include -#include - -#include "gfxhook/gfx.h" - -#include "hook/table.h" - -#include "util/dprintf.h" - -typedef BOOL (WINAPI *ShowWindow_t)(HWND hWnd, int nCmdShow); - -static BOOL WINAPI hook_ShowWindow(HWND hWnd, int nCmdShow); - -static struct gfx_config gfx_config; -static ShowWindow_t next_ShowWindow; - -static const struct hook_symbol gfx_hooks[] = { - { - .name = "ShowWindow", - .patch = hook_ShowWindow, - .link = (void **) &next_ShowWindow, - }, -}; - -void gfx_hook_init(const struct gfx_config *cfg) -{ - assert(cfg != NULL); - - if (!cfg->enable) { - return; - } - - memcpy(&gfx_config, cfg, sizeof(*cfg)); - hook_table_apply(NULL, "user32.dll", gfx_hooks, _countof(gfx_hooks)); -} - -static BOOL WINAPI hook_ShowWindow(HWND hWnd, int nCmdShow) -{ - dprintf("Gfx: ShowWindow hook hit\n"); - - if (!gfx_config.framed && nCmdShow == SW_RESTORE) { - nCmdShow = SW_SHOW; - } - - return next_ShowWindow(hWnd, nCmdShow); -} diff --git a/hooklib/config.c b/hooklib/config.c deleted file mode 100644 index 5fc9383..0000000 --- a/hooklib/config.c +++ /dev/null @@ -1,16 +0,0 @@ -#include - -#include -#include -#include - -#include "hooklib/config.h" -#include "hooklib/dvd.h" - -void dvd_config_load(struct dvd_config *cfg, const wchar_t *filename) -{ - assert(cfg != NULL); - assert(filename != NULL); - - cfg->enable = GetPrivateProfileIntW(L"dvd", L"enable", 1, filename); -} diff --git a/hooklib/config.h b/hooklib/config.h deleted file mode 100644 index 5da045d..0000000 --- a/hooklib/config.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include - -#include "hooklib/dvd.h" - -void dvd_config_load(struct dvd_config *cfg, const wchar_t *filename); diff --git a/hooklib/procaddr.c b/hooklib/procaddr.c deleted file mode 100644 index 2409f28..0000000 --- a/hooklib/procaddr.c +++ /dev/null @@ -1,125 +0,0 @@ -#include -#include -#include -#include - -#include "hooklib/procaddr.h" - -#include "hook/table.h" - -#include "util/dprintf.h" - -static struct proc_addr_table *proc_addr_hook_list; -static size_t proc_addr_hook_count; -static CRITICAL_SECTION proc_addr_hook_lock; -static bool proc_addr_hook_initted; - -static FARPROC WINAPI my_GetProcAddress(HMODULE hModule, const char *name); -static FARPROC (WINAPI *next_GetProcAddress)(HMODULE hModule, const char *name); -static void proc_addr_hook_init(void); - -static const struct hook_symbol win32_hooks[] = { - { - .name = "GetProcAddress", - .patch = my_GetProcAddress, - .link = (void **) &next_GetProcAddress - } -}; - -HRESULT proc_addr_table_push( - const char *target, - struct hook_symbol *syms, - size_t nsyms -) -{ - HRESULT hr; - struct proc_addr_table *new_item; - struct proc_addr_table *new_mem; - - proc_addr_hook_init(); - - EnterCriticalSection(&proc_addr_hook_lock); - - new_mem = realloc( - proc_addr_hook_list, - (proc_addr_hook_count + 1) * sizeof(struct proc_addr_table)); - - if (new_mem == NULL) { - hr = E_OUTOFMEMORY; - - LeaveCriticalSection(&proc_addr_hook_lock); - return hr; - } - - new_item = &new_mem[proc_addr_hook_count]; - new_item->name = target; - new_item->nsyms = nsyms; - new_item->syms = syms; - - proc_addr_hook_list = new_mem; - proc_addr_hook_count++; - hr = S_OK; - - LeaveCriticalSection(&proc_addr_hook_lock); - - return hr; -} - -static void proc_addr_hook_init(void) -{ - if (proc_addr_hook_initted) { - return; - } - - dprintf("ProcAddr: Hook init\n"); - proc_addr_hook_initted = true; - - InitializeCriticalSection(&proc_addr_hook_lock); - - hook_table_apply( - NULL, - "kernel32.dll", - win32_hooks, - _countof(win32_hooks)); -} - -FARPROC WINAPI my_GetProcAddress(HMODULE hModule, const char *name) -{ - uintptr_t ordinal = (uintptr_t) name; - char mod_path[PATH_MAX]; - char *mod_name; - const struct hook_symbol *sym; - FARPROC result = next_GetProcAddress(hModule, name); - - GetModuleFileNameA(hModule, mod_path, PATH_MAX); - mod_name = basename(mod_path); - - for (int i = 0; i < proc_addr_hook_count; i++) { - - if (strcmp(proc_addr_hook_list[i].name, mod_name) == 0) { - - for (int j = 0; j < proc_addr_hook_list[i].nsyms; j++) { - sym = &proc_addr_hook_list[i].syms[j]; - - if (ordinal > 0xFFFF) { - - if (strcmp(sym->name, name) == 0) { - - dprintf("ProcAddr: Hooking %s from %s\n", name, mod_name); - result = (FARPROC) sym->patch; - } - } - - else { - if (sym->ordinal == ordinal) { - - dprintf("ProcAddr: Hooking Ord %p from %s\n", (void *)ordinal, mod_name); - result = (FARPROC) sym->patch; - } - } - } - } - } - - return result; -} \ No newline at end of file diff --git a/hooklib/procaddr.h b/hooklib/procaddr.h deleted file mode 100644 index 750e154..0000000 --- a/hooklib/procaddr.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once -#include -#include -#include - -#include "hook/table.h" - -struct proc_addr_table { - const char *name; - size_t nsyms; - struct hook_symbol *syms; -}; - -HRESULT proc_addr_table_push( - const char *target, - struct hook_symbol *syms, - size_t nsyms -); \ No newline at end of file diff --git a/iccard/felica.c b/iccard/felica.c deleted file mode 100644 index 09e3a77..0000000 --- a/iccard/felica.c +++ /dev/null @@ -1,196 +0,0 @@ -#include - -#include -#include -#include - -#include "hook/iobuf.h" - -#include "iccard/felica.h" - -#include "util/dprintf.h" -#include "util/dump.h" - -static HRESULT felica_cmd_poll( - struct felica *f, - struct const_iobuf *req, - struct iobuf *res); - -static HRESULT felica_cmd_get_system_code( - struct felica *f, - struct const_iobuf *req, - struct iobuf *res); - -static HRESULT felica_cmd_nda_a4( - struct felica *f, - struct const_iobuf *req, - struct iobuf *res); - -uint64_t felica_get_generic_PMm(void) -{ - /* A FeliCa PMm contains low-level protocol timing information for - communicating with a particular IC card. The exact values are not - particularly important for our purposes, so we'll just return a hard- - coded PMm. This current value has been taken from an iPhone, emulating - a Suica pass via Apple Wallet, which seems to be one of the few - universally accepted FeliCa types for these games. Certain older - Suica passes and other payment and transportation cards - do not seem to be supported anymore. */ - - return 0x01168B868FBECBFFULL; -} - -HRESULT felica_transact( - struct felica *f, - struct const_iobuf *req, - struct iobuf *res) -{ - uint64_t IDm; - uint8_t code; - HRESULT hr; - - assert(f != NULL); - assert(req != NULL); - assert(res != NULL); - - hr = iobuf_read_8(req, &code); - - if (FAILED(hr)) { - return hr; - } - - hr = iobuf_write_8(res, code + 1); - - if (FAILED(hr)) { - return hr; - } - - if (code != FELICA_CMD_POLL) { - hr = iobuf_read_be64(req, &IDm); - - if (FAILED(hr)) { - return hr; - } - - if (IDm != f->IDm) { - return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); - } - - hr = iobuf_write_be64(res, IDm); - - if (FAILED(hr)) { - return hr; - } - } - - switch (code) { - case FELICA_CMD_POLL: - return felica_cmd_poll(f, req, res); - - case FELICA_CMD_GET_SYSTEM_CODE: - return felica_cmd_get_system_code(f, req, res); - - case FELICA_CMD_NDA_A4: - return felica_cmd_nda_a4(f, req, res); - - default: - dprintf("FeliCa: Unimplemented command %02x, payload:\n", code); - dump_const_iobuf(req); - - return E_NOTIMPL; - } -} - -static HRESULT felica_cmd_poll( - struct felica *f, - struct const_iobuf *req, - struct iobuf *res) -{ - uint16_t system_code; - uint8_t request_code; - uint8_t time_slot; - HRESULT hr; - - /* Request: */ - - hr = iobuf_read_be16(req, &system_code); - - if (FAILED(hr)) { - return hr; - } - - hr = iobuf_read_8(req, &request_code); - - if (FAILED(hr)) { - return hr; - } - - hr = iobuf_read_8(req, &time_slot); - - if (FAILED(hr)) { - return hr; - } - - if (system_code != 0xFFFF && system_code != f->system_code) { - return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); - } - - // TODO handle other params correctly... - - /* Response: */ - - hr = iobuf_write_be64(res, f->IDm); - - if (FAILED(hr)) { - return hr; - } - - hr = iobuf_write_be64(res, f->PMm); - - if (FAILED(hr)) { - return hr; - } - - if (request_code == 0x01) { - hr = iobuf_write_be16(res, f->system_code); - - if (FAILED(hr)) { - return hr; - } - } - - return S_OK; -} - -static HRESULT felica_cmd_get_system_code( - struct felica *f, - struct const_iobuf *req, - struct iobuf *res) -{ - HRESULT hr; - - hr = iobuf_write_8(res, 1); /* Number of system codes */ - - if (FAILED(hr)) { - return hr; - } - - hr = iobuf_write_be16(res, f->system_code); - - if (FAILED(hr)) { - return hr; - } - - return S_OK; -} - -static HRESULT felica_cmd_nda_a4( - struct felica *f, - struct const_iobuf *req, - struct iobuf *res) -{ - /* The specification for this command is probably only available under NDA. - Returning what the driver seems to want. */ - - return iobuf_write_8(res, 0); -} diff --git a/idzio/di-dev.c b/idzio/di-dev.c deleted file mode 100644 index 6f66bf0..0000000 --- a/idzio/di-dev.c +++ /dev/null @@ -1,163 +0,0 @@ -#include -#include - -#include - -#include "idzio/di-dev.h" - -#include "util/dprintf.h" - -HRESULT idz_di_dev_start(IDirectInputDevice8W *dev, HWND wnd) -{ - HRESULT hr; - - assert(dev != NULL); - assert(wnd != NULL); - - hr = IDirectInputDevice8_SetCooperativeLevel( - dev, - wnd, - DISCL_BACKGROUND | DISCL_EXCLUSIVE); - - if (FAILED(hr)) { - dprintf("DirectInput: SetCooperativeLevel failed: %08x\n", (int) hr); - - return hr; - } - - hr = IDirectInputDevice8_SetDataFormat(dev, &c_dfDIJoystick); - - if (FAILED(hr)) { - dprintf("DirectInput: SetDataFormat failed: %08x\n", (int) hr); - - return hr; - } - - hr = IDirectInputDevice8_Acquire(dev); - - if (FAILED(hr)) { - dprintf("DirectInput: Acquire failed: %08x\n", (int) hr); - - return hr; - } - - return hr; -} - -void idz_di_dev_start_fx(IDirectInputDevice8W *dev, IDirectInputEffect **out) -{ - /* Set up force-feedback on devices that support it. This is just a stub - for the time being, since we don't yet know how the serial port force - feedback protocol works. - - I'm currently developing with an Xbox One Thrustmaster TMX wheel, if - we don't perform at least some perfunctory FFB initialization of this - nature (or indeed if no DirectInput application is running) then the - wheel exhibits considerable resistance, similar to that of a stationary - car. Changing cf.lMagnitude to a nonzero value does cause the wheel to - continuously turn in the given direction with the given force as one - would expect (max magnitude per DirectInput docs is +/- 10000). - - Failure here is non-fatal, we log any errors and move on. - - https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ee416353(v=vs.85) - */ - - IDirectInputEffect *obj; - DWORD axis; - LONG direction; - DIEFFECT fx; - DICONSTANTFORCE cf; - HRESULT hr; - - assert(dev != NULL); - assert(out != NULL); - - *out = NULL; - - dprintf("DirectInput: Starting force feedback (may take a sec)\n"); - - axis = DIJOFS_X; - direction = 0; - - memset(&cf, 0, sizeof(cf)); - cf.lMagnitude = 0; - - memset(&fx, 0, sizeof(fx)); - fx.dwSize = sizeof(fx); - fx.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; - fx.dwDuration = INFINITE; - fx.dwGain = DI_FFNOMINALMAX; - fx.dwTriggerButton = DIEB_NOTRIGGER; - fx.dwTriggerRepeatInterval = INFINITE; - fx.cAxes = 1; - fx.rgdwAxes = &axis; - fx.rglDirection = &direction; - fx.cbTypeSpecificParams = sizeof(cf); - fx.lpvTypeSpecificParams = &cf; - - hr = IDirectInputDevice8_CreateEffect( - dev, - &GUID_ConstantForce, - &fx, - &obj, - NULL); - - if (FAILED(hr)) { - dprintf("DirectInput: DirectInput force feedback unavailable: %08x\n", - (int) hr); - - return; - } - - hr = IDirectInputEffect_Start(obj, INFINITE, 0); - - if (FAILED(hr)) { - IDirectInputEffect_Release(obj); - dprintf("DirectInput: DirectInput force feedback start failed: %08x\n", - (int) hr); - - return; - } - - *out = obj; - - dprintf("DirectInput: Force feedback initialized and set to zero\n"); -} - -HRESULT idz_di_dev_poll( - IDirectInputDevice8W *dev, - HWND wnd, - union idz_di_state *out) -{ - HRESULT hr; - MSG msg; - - assert(dev != NULL); - assert(wnd != NULL); - assert(out != NULL); - - memset(out, 0, sizeof(*out)); - - /* Pump our dummy window's message queue just in case DirectInput or an - IHV DirectInput driver somehow relies on it */ - - while (PeekMessageW(&msg, wnd, 0, 0, PM_REMOVE)) { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - - hr = IDirectInputDevice8_GetDeviceState( - dev, - sizeof(out->st), - &out->st); - - if (FAILED(hr)) { - dprintf("DirectInput: GetDeviceState error: %08x\n", (int) hr); - } - - /* JVS lacks a protocol for reporting hardware errors from poll command - responses, so this ends up returning zeroed input state instead. */ - - return hr; -} diff --git a/idzio/di-dev.h b/idzio/di-dev.h deleted file mode 100644 index 9c8b2d2..0000000 --- a/idzio/di-dev.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include -#include - -#include - -union idz_di_state { - DIJOYSTATE st; - uint8_t bytes[sizeof(DIJOYSTATE)]; -}; - -HRESULT idz_di_dev_start(IDirectInputDevice8W *dev, HWND wnd); -void idz_di_dev_start_fx(IDirectInputDevice8W *dev, IDirectInputEffect **out); -HRESULT idz_di_dev_poll( - IDirectInputDevice8W *dev, - HWND wnd, - union idz_di_state *out); - diff --git a/idzio/idzio.def b/idzio/idzio.def deleted file mode 100644 index b177000..0000000 --- a/idzio/idzio.def +++ /dev/null @@ -1,8 +0,0 @@ -LIBRARY idzio - -EXPORTS - idz_io_jvs_init - idz_io_jvs_read_analogs - idz_io_jvs_read_buttons - idz_io_jvs_read_coin_counter - idz_io_jvs_read_shifter diff --git a/idzio/xi.c b/idzio/xi.c deleted file mode 100644 index c3d33d6..0000000 --- a/idzio/xi.c +++ /dev/null @@ -1,165 +0,0 @@ -#include -#include - -#include -#include -#include - -#include "idzio/backend.h" -#include "idzio/config.h" -#include "idzio/idzio.h" -#include "idzio/shifter.h" -#include "idzio/xi.h" - -#include "util/dprintf.h" - -static void idz_xi_jvs_read_buttons(uint8_t *gamebtn_out); -static void idz_xi_jvs_read_shifter(uint8_t *gear); -static void idz_xi_jvs_read_analogs(struct idz_io_analog_state *out); - -static HRESULT idz_xi_config_apply(const struct idz_xi_config *cfg); - -static const struct idz_io_backend idz_xi_backend = { - .jvs_read_buttons = idz_xi_jvs_read_buttons, - .jvs_read_shifter = idz_xi_jvs_read_shifter, - .jvs_read_analogs = idz_xi_jvs_read_analogs, -}; - -static bool idz_xi_single_stick_steering; - -HRESULT idz_xi_init(const struct idz_xi_config *cfg, const struct idz_io_backend **backend) -{ - HRESULT hr; - assert(cfg != NULL); - assert(backend != NULL); - - hr = idz_xi_config_apply(cfg); - - if (FAILED(hr)) { - return hr; - } - - dprintf("XInput: Using XInput controller\n"); - *backend = &idz_xi_backend; - - return S_OK; -} - -static HRESULT idz_xi_config_apply(const struct idz_xi_config *cfg) -{ - dprintf("XInput: --- Begin configuration ---\n"); - dprintf("XInput: Single Stick Steering : %i\n", cfg->single_stick_steering); - dprintf("XInput: --- End configuration ---\n"); - - idz_xi_single_stick_steering = cfg->single_stick_steering; - - return S_OK; -} - -static void idz_xi_jvs_read_buttons(uint8_t *gamebtn_out) -{ - uint8_t gamebtn; - XINPUT_STATE xi; - WORD xb; - - assert(gamebtn_out != NULL); - - gamebtn = 0; - - memset(&xi, 0, sizeof(xi)); - XInputGetState(0, &xi); - xb = xi.Gamepad.wButtons; - - if (xb & XINPUT_GAMEPAD_DPAD_UP) { - gamebtn |= IDZ_IO_GAMEBTN_UP; - } - - if (xb & XINPUT_GAMEPAD_DPAD_DOWN) { - gamebtn |= IDZ_IO_GAMEBTN_DOWN; - } - - if (xb & XINPUT_GAMEPAD_DPAD_LEFT) { - gamebtn |= IDZ_IO_GAMEBTN_LEFT; - } - - if (xb & XINPUT_GAMEPAD_DPAD_RIGHT) { - gamebtn |= IDZ_IO_GAMEBTN_RIGHT; - } - - if (xb & (XINPUT_GAMEPAD_START | XINPUT_GAMEPAD_A)) { - gamebtn |= IDZ_IO_GAMEBTN_START; - } - - if (xb & (XINPUT_GAMEPAD_BACK | XINPUT_GAMEPAD_B)) { - gamebtn |= IDZ_IO_GAMEBTN_VIEW_CHANGE; - } - - *gamebtn_out = gamebtn; -} - -static void idz_xi_jvs_read_shifter(uint8_t *gear) -{ - bool shift_dn; - bool shift_up; - XINPUT_STATE xi; - WORD xb; - - assert(gear != NULL); - - memset(&xi, 0, sizeof(xi)); - XInputGetState(0, &xi); - xb = xi.Gamepad.wButtons; - - if (xb & XINPUT_GAMEPAD_START) { - /* Reset to Neutral when start is pressed */ - idz_shifter_reset(); - } - - shift_dn = xb & (XINPUT_GAMEPAD_Y | XINPUT_GAMEPAD_LEFT_SHOULDER); - shift_up = xb & (XINPUT_GAMEPAD_X | XINPUT_GAMEPAD_RIGHT_SHOULDER); - - idz_shifter_update(shift_dn, shift_up); - - *gear = idz_shifter_current_gear(); -} - -static void idz_xi_jvs_read_analogs(struct idz_io_analog_state *out) -{ - XINPUT_STATE xi; - int left; - int right; - - assert(out != NULL); - - memset(&xi, 0, sizeof(xi)); - XInputGetState(0, &xi); - - left = xi.Gamepad.sThumbLX; - - if (left < -XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) { - left += XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE; - } else if (left > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) { - left -= XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE; - } else { - left = 0; - } - - right = xi.Gamepad.sThumbRX; - - if (right < -XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) { - right += XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE; - } else if (right > XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) { - right -= XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE; - } else { - right = 0; - } - - if(idz_xi_single_stick_steering) { - out->wheel = left; - } else { - out->wheel = (left + right) / 2; - } - - out->accel = xi.Gamepad.bRightTrigger << 8; - out->brake = xi.Gamepad.bLeftTrigger << 8; -} diff --git a/meson.build b/meson.build index 2c6b673..b21d194 100644 --- a/meson.build +++ b/meson.build @@ -13,14 +13,17 @@ add_project_arguments( '-DWIN32_LEAN_AND_MEAN', '-D_WIN32_WINNT=_WIN32_WINNT_WIN7', '-DMINGW_HAS_SECURE_API=1', + # '-ggdb', # Add debug information language: 'c', ) -# Use get_argument_syntax() instead once Meson 0.49.0 releases -if meson.get_compiler('c').get_id() != 'msvc' +cc = meson.get_compiler('c') + +if cc.get_id() != 'msvc' add_project_arguments( '-ffunction-sections', '-fdata-sections', + '-Wno-unused', language: 'c', ) @@ -29,43 +32,110 @@ if meson.get_compiler('c').get_id() != 'msvc' '-Wl,--exclude-all-symbols', '-Wl,--gc-sections', '-static-libgcc', + # '-ggdb', # Add debug information + '-lcrypt32', # Bcrypt needed for prashook + '-lws2_32', # WSAHtons / WSANtohs needed for porthook + # '-Wl,-s', # Strip debug symbols language: 'c', ) endif -cc = meson.get_compiler('c') +if get_option('log_all') or get_option('log_jvs') + add_project_arguments('-DLOG_JVS', language: 'c') +endif +if get_option('log_all') or get_option('log_io3') + add_project_arguments('-DLOG_IO3', language: 'c') +endif +if get_option('log_all') or get_option('log_led15070') + add_project_arguments('-DLOG_LED15070', language: 'c') +endif +if get_option('log_all') or get_option('log_led15093') + add_project_arguments('-DLOG_LED15093', language: 'c') +endif +if get_option('log_all') or get_option('log_nfc') + add_project_arguments('-DLOG_NFC', language: 'c') +endif +if get_option('log_all') or get_option('log_carol_control_bd') + add_project_arguments('-DLOG_CAROL_CONTROL_BD', language: 'c') +endif +if get_option('log_all') or get_option('log_carol_led_bd') + add_project_arguments('-DLOG_CAROL_LED_BD', language: 'c') +endif +if get_option('log_all') or get_option('log_carol_touch') + add_project_arguments('-DLOG_CAROL_TOUCH', language: 'c') +endif +if get_option('log_all') or get_option('log_mai2_touch') + add_project_arguments('-DLOG_MAI2_TOUCH', language: 'c') +endif +if get_option('log_all') or get_option('log_chuni_slider') + add_project_arguments('-DLOG_CHUNI_SLIDER', language: 'c') +endif +if get_option('log_all') or get_option('log_chusan_slider') + add_project_arguments('-DLOG_CHUSAN_SLIDER', language: 'c') +endif +if get_option('log_all') or get_option('log_diva_slider') + add_project_arguments('-DLOG_DIVA_SLIDER', language: 'c') +endif +if get_option('log_all') or get_option('log_mercury_slider') + add_project_arguments('-DLOG_MERCURY_SLIDER', language: 'c') +endif +if get_option('log_all') or get_option('log_clock') + add_project_arguments('-DLOG_CLOCK', language: 'c') +endif +if get_option('log_all') or get_option('log_vfs') + add_project_arguments('-DLOG_VFS', language: 'c') +endif + shlwapi_lib = cc.find_library('shlwapi') dinput8_lib = cc.find_library('dinput8') dxguid_lib = cc.find_library('dxguid') xinput_lib = cc.find_library('xinput') +pathcch_lib = cc.find_library('pathcch') +imagehlp_lib = cc.find_library('imagehlp') +ws2_32_lib = cc.find_library('ws2_32') -inc = include_directories('.') +inc = include_directories('common', 'games') capnhook = subproject('capnhook') -subdir('amex') -subdir('iccard') -subdir('board') -subdir('hooklib') -subdir('jvs') -subdir('platform') -subdir('util') +subdir('common/amex') +subdir('common/iccard') +subdir('common/board') +subdir('common/hooklib') +subdir('common/jvs') +subdir('common/platform') +subdir('common/util') +subdir('common/aimeio') -subdir('gfxhook') +subdir('common/gfxhook') +subdir('common/unityhook') -subdir('aimeio') -subdir('chuniio') -subdir('divaio') -subdir('carolio') -subdir('idzio') -subdir('mu3io') -subdir('mercuryio') -subdir('cxbio') +subdir('games/chuniio') +subdir('games/divaio') +subdir('games/carolio') +subdir('games/idzio') +subdir('games/idacio') +subdir('games/swdcio') +subdir('games/mu3io') +subdir('games/mai2io') +subdir('games/cmio') +subdir('games/mercuryio') +subdir('games/cxbio') +subdir('games/tokyoio') +subdir('games/fgoio') +subdir('games/kemonoio') -subdir('chunihook') -subdir('divahook') -subdir('carolhook') -subdir('idzhook') -subdir('minihook') -subdir('mu3hook') -subdir('mercuryhook') -subdir('cxbhook') +subdir('games/chunihook') +subdir('games/divahook') +subdir('games/carolhook') +subdir('games/idzhook') +subdir('games/idachook') +subdir('games/swdchook') +subdir('games/chusanhook') +subdir('games/mu3hook') +subdir('games/mai2hook') +subdir('games/cmhook') +subdir('games/mercuryhook') +subdir('games/cxbhook') +subdir('games/tokyohook') +subdir('games/fgohook') +subdir('games/kemonohook') diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..b6989f4 --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,80 @@ +option('log_all', + type : 'boolean', + value : false, + description : 'Enables all of the subsequent debug logging options' +) +option('log_jvs', + type : 'boolean', + value : false, + description : 'Enable debug logging for JVS' +) +option('log_io3', + type : 'boolean', + value : false, + description : 'Enable debug logging for JVS' +) +option('log_led15070', + type : 'boolean', + value : false, + description : 'Enable debug logging for the 15070 LED board emulation' +) +option('log_led15093', + type : 'boolean', + value : false, + description : 'Enable debug logging for the 15093 LED board emulation' +) +option('log_nfc', + type : 'boolean', + value : false, + description : 'Enable debug logging for NFC' +) +option('log_carol_control_bd', + type : 'boolean', + value : false, + description : 'Enable debug logging for the Carlo Control Board' +) +option('log_carol_led_bd', + type : 'boolean', + value : false, + description : 'Enable debug logging for the Carlo LED Board' +) +option('log_carol_touch', + type : 'boolean', + value : false, + description : 'Enable debug logging for the Carlo Touchscreen' +) +option('log_mai2_touch', + type : 'boolean', + value : false, + description : 'Enable debug logging for the mai2 TouchPanel' +) +option('log_chuni_slider', + type : 'boolean', + value : false, + description : 'Enable debug logging for the Chunithm Slider' +) +option('log_chusan_slider', + type : 'boolean', + value : false, + description : 'Enable debug logging for the Chusan Slider' +) +option('log_diva_slider', + type : 'boolean', + value : false, + description : 'Enable debug logging for the Diva Slider' +) +option('log_mercury_slider', + type : 'boolean', + value : false, + description : 'Enable debug logging for the WACCA Slider' +) +option('log_clock', + type : 'boolean', + value : false, + description : 'Enable debug logging for clock APIs' +) +option('log_vfs', + type : 'boolean', + value : false, + description : 'Enable debug logging for file system redirections' +) diff --git a/minihook/dllmain.c b/minihook/dllmain.c deleted file mode 100644 index bdc45b8..0000000 --- a/minihook/dllmain.c +++ /dev/null @@ -1,75 +0,0 @@ -#include - -#include - -#include "amex/config.h" -#include "amex/ds.h" - -#include "hook/process.h" - -#include "hooklib/spike.h" - -#include "platform/clock.h" -#include "platform/config.h" -#include "platform/nusec.h" - -#include "util/dprintf.h" - -static process_entry_t app_startup; - -static DWORD CALLBACK app_pre_startup(void) -{ - struct clock_config clock_cfg; - struct ds_config ds_cfg; - struct nusec_config nusec_cfg; - HRESULT hr; - - dprintf("--- Begin %s ---\n", __func__); - - clock_config_load(&clock_cfg, L".\\segatools.ini"); - ds_config_load(&ds_cfg, L".\\segatools.ini"); - nusec_config_load(&nusec_cfg, L".\\segatools.ini"); - spike_hook_init(L".\\segatools.ini"); - - hr = clock_hook_init(&clock_cfg); - - if (FAILED(hr)) { - goto fail; - } - - hr = nusec_hook_init(&nusec_cfg, "SSSS", "AAV0"); - - if (FAILED(hr)) { - goto fail; - } - - hr = ds_hook_init(&ds_cfg); - - if (FAILED(hr)) { - goto fail; - } - - dprintf("--- End %s ---\n", __func__); - - return app_startup(); - -fail: - ExitProcess(EXIT_FAILURE); -} - -BOOL WINAPI DllMain(HMODULE mod, DWORD cause, void *ctx) -{ - HRESULT hr; - - if (cause != DLL_PROCESS_ATTACH) { - return TRUE; - } - - hr = process_hijack_startup(app_pre_startup, &app_startup); - - if (!SUCCEEDED(hr)) { - dprintf("Failed to hijack process startup: %x\n", (int) hr); - } - - return SUCCEEDED(hr); -} diff --git a/minihook/meson.build b/minihook/meson.build deleted file mode 100644 index 8acbf1e..0000000 --- a/minihook/meson.build +++ /dev/null @@ -1,19 +0,0 @@ -shared_library( - 'minihook', - name_prefix : '', - include_directories: inc, - implicit_include_directories : false, - c_pch : '../precompiled.h', - dependencies : [ - capnhook.get_variable('hook_dep'), - ], - link_with : [ - amex_lib, - hooklib_lib, - platform_lib, - util_lib, - ], - sources : [ - 'dllmain.c', - ], -) diff --git a/msvc-build.bat b/msvc-build.bat new file mode 100644 index 0000000..460b5fd --- /dev/null +++ b/msvc-build.bat @@ -0,0 +1,137 @@ +@echo off +setlocal enabledelayedexpansion + +set BUILD_DIR=build +set BUILD_DIR_32=%BUILD_DIR%\build32 +set BUILD_DIR_64=%BUILD_DIR%\build64 +set BUILD_DIR_GAMES_32=%BUILD_DIR_32%\games +set BUILD_DIR_GAMES_64=%BUILD_DIR_64%\games +set BUILD_DIR_ZIP=%BUILD_DIR%\zip +set DIST_DIR=dist +set DOC_DIR=doc + +REM Set your Visual Studio install path if Visual Studio installation can not be detected +set VS_INSTALLATION=C:\Program Files\Microsoft Visual Studio\2022\Community + +if /I "%1"=="build" ( + call :detect-visual-studio + if ERRORLEVEL 2 exit /b + if ERRORLEVEL 1 ( + echo Failed to detect Visual Studio installation path. + echo. + echo If Visual Studio is installed then edit VS_INSTALLATION in this file + echo to manually specify Visual Studio install path. + exit /b + ) + + call :detect-meson + if ERRORLEVEL 1 ( + echo Meson is not installed. + exit /b + ) + + set VSVARSALL=!VSVARSALL! + set MESON=!MESON! + + call :build %2 + + echo. + echo Build done! + exit /b +) +if /I "%1"=="zip" ( + powershell -NoProfile -ExecutionPolicy Bypass -Command "& './package.ps1'" + exit /b +) + +echo %~nx0 [action] [switch] +echo build: Build the for both x86 and x64 +echo /PROJECTONLY: Only create projects +echo. +echo zip: Make zip file +echo. +exit /b + +rem This should works for Visual Studio 2017+ +:detect-visual-studio ( + rem Fall back to x86 program directory for MSVC standalone if it can't be found in x64, because even though it's x64 compilers, they install in x86 program files for whatever dumb reason + set VSWHERE="%ProgramFiles%\Microsoft Visual Studio\Installer\vswhere.exe" + if not exist %VSWHERE% ( + set VSWHERE="%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" + ) + + if exist %VSWHERE% ( + REM get vcvarsall by using vswhere + set VSVARSALL="" + for /f "tokens=* usebackq" %%i in (`%VSWHERE% -products * -find VC\Auxiliary\Build\vcvarsall.bat`) do set VSVARSALL="%%i" + ) else ( + REM fallback to old method + set VSVARSALL="%VS_INSTALLATION%\VC\Auxiliary\Build\vcvarsall.bat" + ) + + :check-vcvarsall + if /i %VSVARSALL%=="" ( + echo Microsoft Visual C++ Component is not installed + echo Install it from Visual Studio Installer + exit /b 2 + ) + + rem if a path is returned by vswhere, then it's sure that the result path exists + rem if path not exists than it was assumed from VS_INSTALLATION + if not exist %VSVARSALL% ( + echo vsvarsall.bat not exists in VS_INSTALLATION, + echo either Visual C++ Component is not installed + echo or VS_INSTALLATION is wrong. + exit /b 1 + ) + + exit /b 0 +) + +:detect-meson ( + set MESON="" + for /f "tokens=* usebackq" %%i in (`where meson`) do set MESON="%%i" + if not exist %MESON% ( + exit /b 1 + ) + + exit /b 0 +) + +:build ( + :build_x64 ( + call %VSVARSALL% x64 + + if exist %BUILD_DIR_64% ( + %MESON% setup %BUILD_DIR_64% --buildtype release --reconfigure + ) else ( + %MESON% setup %BUILD_DIR_64% --backend vs --buildtype release + ) + + if /I not "%1"=="/PROJECTONLY" ( + pushd %BUILD_DIR_64% + msbuild /m /p:Configuration=release /p:Platform=x64 segatools.sln + popd + ) + ) + + :build_x86 ( + call %VSVARSALL% x86 + + if exist %BUILD_DIR_32% ( + %MESON% setup %BUILD_DIR_32% --buildtype release --reconfigure + ) else ( + %MESON% setup %BUILD_DIR_32% --backend vs --buildtype release + ) + + if /I not "%1"=="/PROJECTONLY" ( + pushd %BUILD_DIR_32% + msbuild /m /p:Configuration=release /p:Platform=Win32 segatools.sln + popd + ) + ) + + :end ( + exit /b + ) +) diff --git a/mu3hook/unity.c b/mu3hook/unity.c deleted file mode 100644 index efefc32..0000000 --- a/mu3hook/unity.c +++ /dev/null @@ -1,95 +0,0 @@ -#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.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/mu3hook/unity.h b/mu3hook/unity.h deleted file mode 100644 index 99c3bd9..0000000 --- a/mu3hook/unity.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void unity_hook_init(void); diff --git a/mu3io/mu3io.c b/mu3io/mu3io.c deleted file mode 100644 index 0bbd37f..0000000 --- a/mu3io/mu3io.c +++ /dev/null @@ -1,148 +0,0 @@ -#include -#include - -#include -#include - -#include "mu3io/mu3io.h" - -static uint8_t mu3_opbtn; -static uint8_t mu3_left_btn; -static uint8_t mu3_right_btn; -static int16_t mu3_lever_pos; -static int16_t mu3_lever_xpos; - -uint16_t mu3_io_get_api_version(void) -{ - return 0x0100; -} - -HRESULT mu3_io_init(void) -{ - return S_OK; -} - -HRESULT mu3_io_poll(void) -{ - int lever; - int xlever; - XINPUT_STATE xi; - WORD xb; - - mu3_opbtn = 0; - mu3_left_btn = 0; - mu3_right_btn = 0; - - if (GetAsyncKeyState('1') & 0x8000) { - mu3_opbtn |= MU3_IO_OPBTN_TEST; - } - - if (GetAsyncKeyState('2') & 0x8000) { - mu3_opbtn |= MU3_IO_OPBTN_SERVICE; - } - - memset(&xi, 0, sizeof(xi)); - XInputGetState(0, &xi); - xb = xi.Gamepad.wButtons; - - if (xb & XINPUT_GAMEPAD_DPAD_LEFT) { - mu3_left_btn |= MU3_IO_GAMEBTN_1; - } - - if (xb & XINPUT_GAMEPAD_DPAD_UP) { - mu3_left_btn |= MU3_IO_GAMEBTN_2; - } - - if (xb & XINPUT_GAMEPAD_DPAD_RIGHT) { - mu3_left_btn |= MU3_IO_GAMEBTN_3; - } - - if (xb & XINPUT_GAMEPAD_X) { - mu3_right_btn |= MU3_IO_GAMEBTN_1; - } - - if (xb & XINPUT_GAMEPAD_Y) { - mu3_right_btn |= MU3_IO_GAMEBTN_2; - } - - if (xb & XINPUT_GAMEPAD_B) { - mu3_right_btn |= MU3_IO_GAMEBTN_3; - } - - if (xb & XINPUT_GAMEPAD_BACK) { - mu3_left_btn |= MU3_IO_GAMEBTN_MENU; - } - - if (xb & XINPUT_GAMEPAD_START) { - mu3_right_btn |= MU3_IO_GAMEBTN_MENU; - } - - if (xb & XINPUT_GAMEPAD_LEFT_SHOULDER) { - mu3_left_btn |= MU3_IO_GAMEBTN_SIDE; - } - - if (xb & XINPUT_GAMEPAD_RIGHT_SHOULDER) { - mu3_right_btn |= MU3_IO_GAMEBTN_SIDE; - } - - lever = mu3_lever_pos; - - if (abs(xi.Gamepad.sThumbLX) > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) { - lever += xi.Gamepad.sThumbLX / 24; - } - - if (abs(xi.Gamepad.sThumbRX) > XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) { - lever += xi.Gamepad.sThumbRX / 24; - } - - if (lever < INT16_MIN) { - lever = INT16_MIN; - } - - if (lever > INT16_MAX) { - lever = INT16_MAX; - } - - mu3_lever_pos = lever; - - xlever = mu3_lever_pos - - xi.Gamepad.bLeftTrigger * 64 - + xi.Gamepad.bRightTrigger * 64; - - if (xlever < INT16_MIN) { - xlever = INT16_MIN; - } - - if (xlever > INT16_MAX) { - xlever = INT16_MAX; - } - - mu3_lever_xpos = xlever; - - return S_OK; -} - -void mu3_io_get_opbtns(uint8_t *opbtn) -{ - if (opbtn != NULL) { - *opbtn = mu3_opbtn; - } -} - -void mu3_io_get_gamebtns(uint8_t *left, uint8_t *right) -{ - if (left != NULL) { - *left = mu3_left_btn; - } - - if (right != NULL ){ - *right = mu3_right_btn; - } -} - -void mu3_io_get_lever(int16_t *pos) -{ - if (pos != NULL) { - *pos = mu3_lever_xpos; - } -} diff --git a/package.ps1 b/package.ps1 new file mode 100644 index 0000000..d2f0316 --- /dev/null +++ b/package.ps1 @@ -0,0 +1,102 @@ +if ($null -eq $env:BUILD_DIR) { + $BUILD_DIR="build" + $BUILD_DIR_32="$BUILD_DIR\build32" + $BUILD_DIR_64="$BUILD_DIR\build64" + $BUILD_DIR_GAMES_32="$BUILD_DIR_32\games" + $BUILD_DIR_GAMES_64="$BUILD_DIR_64\games" + $BUILD_DIR_ZIP="$BUILD_DIR\zip" + $DIST_DIR="dist" + $DOC_DIR="doc" +} else { + $BUILD_DIR = $env:BUILD_DIR; + $BUILD_DIR_32 = $env:BUILD_DIR_32; + $BUILD_DIR_64 = $env:BUILD_DIR_64; + $BUILD_DIR_GAMES_32 = $env:BUILD_DIR_GAMES_32; + $BUILD_DIR_GAMES_64 = $env:BUILD_DIR_GAMES_64; + $BUILD_DIR_ZIP = $env:BUILD_DIR_ZIP; + $DIST_DIR = $env:DIST_DIR; + $DOC_DIR = $env:DOC_DIR; +} + +$target = $null; +$line = ''; +[System.Collections.ArrayList]$files = @(); +$folder = Get-Location + +cat .\Package.mk | % { + $trimmed = $_.TrimEnd('\'). + TrimStart("`t "). + Replace('$(V)', ''). + Replace('$(BUILD_DIR)', $BUILD_DIR). + Replace('$(BUILD_DIR_32)', $BUILD_DIR_32). + Replace('$(BUILD_DIR_64)', $BUILD_DIR_64). + Replace('$(BUILD_DIR_GAMES_32)', $BUILD_DIR_GAMES_32). + Replace('$(BUILD_DIR_GAMES_64)', $BUILD_DIR_GAMES_64). + Replace('$(BUILD_DIR_ZIP)', $BUILD_DIR_ZIP). + Replace('$(DIST_DIR)', $DIST_DIR). + Replace('$(DOC_DIR)', $DOC_DIR). + Replace('$@', $target). + Replace('/', '\'); + + if ($trimmed.EndsWith(': ') -or $trimmed.EndsWith(':')) { + $target = $trimmed.TrimEnd(': '); + $line = ''; + $files.Clear(); + cd $folder; + + return; + } + + if (-not $trimmed.StartsWith('|')) { + $line += $trimmed; + + if ($_.EndsWith('\')) { + return; + } + } + + $line.Split(';') | % { + $cmd = $_.Trim(' '); + if ($cmd.StartsWith('echo')) { + echo $cmd.TrimStart('echo ') + } elseif ($cmd.StartsWith('mkdir')) { + $cmd = $cmd.Replace('-p', '-Force') + echo " - $cmd" + Invoke-Expression $cmd | Out-Null + } elseif ($cmd.StartsWith('cp')) { + $tokens = $cmd.Replace('cp ', '').Split(' '); + $srcs = $tokens[0..($tokens.Count - 2)]; + $dest = $tokens[$tokens.Count - 1]; + echo " - cp -Path $srcs -Destination $dest"; + cp -Path $srcs -Destination $dest + } elseif ($cmd.StartsWith('cd')) { + echo " - $cmd" + Invoke-Expression $cmd | Out-Null + } elseif ($cmd.StartsWith('zip ')) { + $tokens = $cmd.Substring(4, $cmd.Length - 4).Replace('-r ', '').Replace('-j ', '').Replace('*', '.\*'); + $tokens = $tokens.Split(' '); + $target = $tokens[0] + + if (-not $tokens.Contains('$^')) { + $files.AddRange($tokens[1..($tokens.Count - 1)]); + } + + echo " - Compress-Archive -Path $files -DestinationPath $target -Force" + Compress-Archive -Path $files -DestinationPath $target -Force + } else { + $allExists = $true + $args = $cmd.Split(' '); + $args | ? { -not $_ -eq '' } | % { + if (-not (Test-Path $_)) { + $allExists = $false + } + } + + if ($allExists) { + $files.AddRange($args); + } + } + } + + $line = ''; +} diff --git a/platform/dns.c b/platform/dns.c deleted file mode 100644 index 5486d42..0000000 --- a/platform/dns.c +++ /dev/null @@ -1,83 +0,0 @@ -#include - -#include - -#include "hooklib/dns.h" - -#include "platform/dns.h" - -HRESULT dns_platform_hook_init(const struct dns_config *cfg) -{ - HRESULT hr; - - assert(cfg != NULL); - - if (!cfg->enable) { - return S_FALSE; - } - - hr = dns_hook_push(L"tenporouter.loc", cfg->router); - - if (FAILED(hr)) { - return hr; - } - - hr = dns_hook_push(L"bbrouter.loc", cfg->router); - - if (FAILED(hr)) { - return hr; - } - - hr = dns_hook_push(L"naominet.jp", cfg->startup); - - if (FAILED(hr)) { - return hr; - } - - hr = dns_hook_push(L"anbzvarg.wc", cfg->startup); - - if (FAILED(hr)) { - return hr; - } - - hr = dns_hook_push(L"ib.naominet.jp", cfg->billing); - - if (FAILED(hr)) { - return hr; - } - - hr = dns_hook_push(L"vo.anbzvarg.wc", cfg->billing); - - if (FAILED(hr)) { - return hr; - } - - hr = dns_hook_push(L"aime.naominet.jp", cfg->aimedb); - - if (FAILED(hr)) { - return hr; - } - - hr = dns_hook_push(L"nvzr.anbzvarg.wc", cfg->aimedb); - - if (FAILED(hr)) { - return hr; - } - - // if your ISP resolves bad domains, it will kill the network. These 2 - // *cannot* resolve - - hr = dns_hook_push(L"mobirouter.loc", NULL); - - if (FAILED(hr)) { - return hr; - } - - hr = dns_hook_push(L"dslrouter.loc", NULL); - - if (FAILED(hr)) { - return hr; - } - - return S_OK; -} diff --git a/precompiled.h b/precompiled.h deleted file mode 100644 index 7c85da7..0000000 --- a/precompiled.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - Making NTSTATUS available is slightly awkward. See: - https://kirkshoop.github.io/2011/09/20/ntstatus.html -*/ - -/* Win32 user-mode API */ -#define WIN32_NO_STATUS -#include -#undef WIN32_NO_STATUS -#include -#include -#include -#include -#include -#include -#include -#include - -/* Win32 kernel-mode definitions */ -#ifdef __GNUC__ -/* MinGW needs to include this for PHYSICAL_ADDRESS to be defined. - The MS SDK throws a bunch of duplicate symbol errors instead. */ -#include -#else -#include -#endif -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include diff --git a/reg/chunithm.reg b/reg/chunithm.reg deleted file mode 100644 index f7c1240..0000000 --- a/reg/chunithm.reg +++ /dev/null @@ -1,27 +0,0 @@ -Windows Registry Editor Version 5.00 - -[HKEY_LOCAL_MACHINE\SYSTEM\SEGA] - -[HKEY_LOCAL_MACHINE\SYSTEM\SEGA\SystemProperty] - -[HKEY_LOCAL_MACHINE\SYSTEM\SEGA\SystemProperty\keychip] -"systemFlag"=dword:00000064 -"modelType"=dword:00000001 -"region"=dword:00000001 -"serverIpIpv4"=hex:c0,a8,8b,00 -"keychipId"=hex:41,36,39,45,2d,30,31,41,39,39,39,39,39,39,39,39 -"platformId"=hex:41,41,56 -"gameId"=hex:53,42,5a,56 - -[HKEY_LOCAL_MACHINE\SYSTEM\SEGA\SystemProperty\static] -"CpuTempWarning"=dword:0000003c -"CpuTempError"=dword:00000050 -"PlatformId"=hex:41,41,56 - -[HKEY_LOCAL_MACHINE\SYSTEM\SEGA\SystemProperty\wirelessNetwork] -"main_nic"=dword:00000000 - -[HKEY_LOCAL_MACHINE\SYSTEM\SEGA\SystemProperty\mount] -"AMFS"="E:\\" -"APPDATA"="Y:\\" - diff --git a/subprojects/capnhook.wrap b/subprojects/capnhook.wrap index af30eae..4c349ab 100644 --- a/subprojects/capnhook.wrap +++ b/subprojects/capnhook.wrap @@ -1,4 +1,4 @@ [wrap-git] directory = capnhook -url = https://github.com/decafcode/capnhook -revision = 69f7e3b48c2e0ff5be1d7a83cdcc2597a458357b +url = https://gitea.tendokyu.moe/TeamTofuShop/capnhook +revision = dafd0aa336ab80ba87a82370a9dc95a3389ef5e5