fgo: fgohook finally added

Credits:
- Coburn
- domeori
- Mitsuhide
- OLEG
- rakisaionji
This commit is contained in:
2023-11-11 22:47:47 +01:00
parent 946ea7ef3b
commit a2db39c58c
30 changed files with 4478 additions and 0 deletions

94
fgohook/config.c Normal file
View File

@ -0,0 +1,94 @@
#include <assert.h>
#include <stddef.h>
#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);
}
void led1509306_config_load(struct led1509306_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));
cfg->enable = GetPrivateProfileIntW(L"ledstrip", L"enable", 1, filename);
cfg->port_no = GetPrivateProfileIntW(L"ledstrip", L"port", 21, filename);
cfg->fw_ver = GetPrivateProfileIntW(L"ledstrip", L"fw_ver", 0xA0, filename);
cfg->fw_sum = GetPrivateProfileIntW(L"ledstrip", L"fw_sum", 0xaa53, filename);
GetPrivateProfileStringW(L"ledstrip", L"board_number", 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"ledstrip", L"chip_number", 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] = ' ';
}
}
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);
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);
led1509306_config_load(&cfg->led1509306, filename);
fgo_dll_config_load(&cfg->dll, filename);
}

37
fgohook/config.h Normal file
View File

@ -0,0 +1,37 @@
#pragma once
#include <stddef.h>
#include "board/config.h"
#include "hooklib/dvd.h"
#include "hooklib/touch.h"
#include "hooklib/printer.h"
#include "fgohook/deck.h"
#include "fgohook/ftdi.h"
#include "fgohook/led1509306.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 touch_screen_config touch;
struct printer_config printer;
struct deck_config deck;
struct ftdi_config ftdi;
struct led1509306_config led1509306;
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);

511
fgohook/deck.c Normal file
View File

@ -0,0 +1,511 @@
/*
SEGA 837-15345 RFID Deck Reader emulator
Credits:
OLEG
Coburn
Mitsuhide
*/
#include <windows.h>
#include <assert.h>
#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, int port)
{
assert(cfg != NULL);
if (!cfg->enable) {
return S_FALSE;
}
InitializeCriticalSection(&deck_lock);
uart_init(&deck_uart, port);
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);
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;
}

11
fgohook/deck.h Normal file
View File

@ -0,0 +1,11 @@
#pragma once
#include <windows.h>
#include <stdbool.h>
struct deck_config {
bool enable;
};
HRESULT deck_hook_init(const struct deck_config *cfg, int port);

144
fgohook/dllmain.c Normal file
View File

@ -0,0 +1,144 @@
#include <windows.h>
#include <stdlib.h>
#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/printer.h"
#include "hooklib/createprocess.h"
#include "hooklib/serial.h"
#include "hooklib/spike.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"
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;
dprintf("--- Begin fgo_pre_startup ---\n");
/* Load config */
fgo_hook_config_load(&fgo_hook_cfg, L".\\segatools.ini");
/* Hook Win32 APIs */
dvd_hook_init(&fgo_hook_cfg.dvd, fgo_hook_mod);
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);
/* 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, fgo_hook_mod);
if (FAILED(hr)) {
goto fail;
}
hr = vfd_hook_init(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);
if (FAILED(hr)) {
goto fail;
}
*/
hr = led1509306_hook_init(&fgo_hook_cfg.led1509306);
if (FAILED(hr)) {
goto fail;
}
hr = createprocess_push_hook_a("am/amdaemon.exe", "inject -d -k fgohook.dll ", " -c config_hook.json", false);
if (FAILED(hr)) {
goto fail;
}
/* Initialize debug helpers */
spike_hook_init(L".\\segatools.ini");
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);
}

112
fgohook/fgo-dll.c Normal file
View File

@ -0,0 +1,112 @@
#include <windows.h>
#include <assert.h>
#include <stdlib.h>
#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),
}
};
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;
}

22
fgohook/fgo-dll.h Normal file
View File

@ -0,0 +1,22 @@
#pragma once
#include <windows.h>
#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);
};
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);

82
fgohook/fgohook.def Normal file
View File

@ -0,0 +1,82 @@
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
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

55
fgohook/ftdi.c Normal file
View File

@ -0,0 +1,55 @@
/*
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".
*/
#include <windows.h>
#include <assert.h>
#include "fgohook/ftdi.h"
#include "hook/iohook.h"
#include "hooklib/setupapi.h"
#include "util/dprintf.h"
static struct ftdi_config ftdi_cfg;
static HANDLE ftdi_fd;
HRESULT ftdi_hook_init(const struct ftdi_config *cfg)
{
HRESULT hr;
assert(cfg != NULL);
if (!cfg->enable) {
return S_FALSE;
}
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;
}
dprintf("FTDI: Hook enabled.\n");
return S_OK;
}

20
fgohook/ftdi.h Normal file
View File

@ -0,0 +1,20 @@
#pragma once
#include <windows.h>
#include <initguid.h>
#include <stdbool.h>
#include <stddef.h>
struct ftdi_config {
bool enable;
};
DEFINE_GUID(
ftdi_guid,
0x86E0D1E0,
0x8089,
0x11D0,
0x9C, 0xE4, 0x08, 0x00, 0x3E, 0x30, 0x1F, 0x73);
HRESULT ftdi_hook_init(const struct ftdi_config *cfg);

104
fgohook/io4.c Normal file
View File

@ -0,0 +1,104 @@
#include <windows.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#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_PHANTASHM) {
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;
}

7
fgohook/io4.h Normal file
View File

@ -0,0 +1,7 @@
#pragma once
#include <windows.h>
#include "board/io4.h"
HRESULT fgo_io4_hook_init(const struct io4_config *cfg);

379
fgohook/led1509306.c Normal file
View File

@ -0,0 +1,379 @@
#include <windows.h>
#include <assert.h>
#include <process.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "board/led1509306-cmd.h"
#include "board/led1509306-frame.h"
#include "fgohook/led1509306.h"
#include "hook/iobuf.h"
#include "hook/iohook.h"
#include "hooklib/uart.h"
#include "util/dprintf.h"
#include "util/dump.h"
static HRESULT led1509306_handle_irp(struct irp *irp);
static HRESULT led1509306_handle_irp_locked(int board, struct irp *irp);
static HRESULT led1509306_req_dispatch(int board, const struct led1509306_req_any *req);
static HRESULT led1509306_req_reset(int board, const struct led1509306_req_any *req);
static HRESULT led1509306_req_get_board_info(int board);
static HRESULT led1509306_req_get_fw_sum(int board);
static HRESULT led1509306_req_get_protocol_ver(int board);
static HRESULT led1509306_req_get_board_status(int board);
static HRESULT led1509306_req_set_led(int board, const struct led1509306_req_any *req);
static HRESULT led1509306_req_set_disable_response(int board, const struct led1509306_req_any *req);
static HRESULT led1509306_req_set_timeout(int board, const struct led1509306_req_any *req);
static char led1509306_board_num[8];
static char led1509306_chip_num[5];
static uint8_t led1509306_fw_ver;
static uint16_t led1509306_fw_sum;
static uint8_t led1509306_board_adr = 2;
static uint8_t led1509306_host_adr = 1;
#define led1509306_nboards 2
typedef struct {
CRITICAL_SECTION lock;
struct uart boarduart;
uint8_t written_bytes[520];
uint8_t readable_bytes[520];
bool enable_response;
} _led1509306_per_board_vars;
_led1509306_per_board_vars led1509306_per_board_vars[led1509306_nboards];
HRESULT led1509306_hook_init(const struct led1509306_config *cfg)
{
assert(cfg != NULL);
if (!cfg->enable) {
return S_FALSE;
}
memcpy(led1509306_board_num, cfg->board_number, sizeof(led1509306_board_num));
memcpy(led1509306_chip_num, cfg->chip_number, sizeof(led1509306_chip_num));
led1509306_fw_ver = cfg->fw_ver;
led1509306_fw_sum = cfg->fw_sum;
for (int i = 0; i < led1509306_nboards; i++)
{
_led1509306_per_board_vars *v = &led1509306_per_board_vars[i];
InitializeCriticalSection(&v->lock);
uart_init(&v->boarduart, cfg->port_no + i);
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);
v->enable_response = true;
}
dprintf("LED Strip: hook enabled.\n");
return iohook_push_handler(led1509306_handle_irp);
}
static HRESULT led1509306_handle_irp(struct irp *irp)
{
HRESULT hr;
assert(irp != NULL);
for (int i = 0; i < led1509306_nboards; i++)
{
_led1509306_per_board_vars *v = &led1509306_per_board_vars[i];
struct uart *boarduart = &v->boarduart;
if (uart_match_irp(boarduart, irp))
{
CRITICAL_SECTION lock = v->lock;
EnterCriticalSection(&lock);
hr = led1509306_handle_irp_locked(i, irp);
LeaveCriticalSection(&lock);
return hr;
}
}
return iohook_invoke_next(irp);
}
static HRESULT led1509306_handle_irp_locked(int board, struct irp *irp)
{
struct led1509306_req_any req;
struct iobuf req_iobuf;
HRESULT hr;
struct uart *boarduart = &led1509306_per_board_vars[board].boarduart;
hr = uart_handle_irp(boarduart, irp);
if (FAILED(hr) || irp->op != IRP_OP_WRITE) {
return hr;
}
for (;;) {
#if 0
dprintf("TX Buffer:\n");
dump_iobuf(&boarduart->written);
#endif
req_iobuf.bytes = (byte*)&req;
req_iobuf.nbytes = sizeof(req.hdr) + sizeof(req.cmd) + sizeof(req.payload);
req_iobuf.pos = 0;
hr = led1509306_frame_decode(&req_iobuf, &boarduart->written);
if (hr != S_OK) {
if (FAILED(hr)) {
dprintf("LED Strip: Deframe error: %x\n", (int) hr);
}
return hr;
}
#if 0
dprintf("Deframe Buffer:\n");
dump_iobuf(&req_iobuf);
#endif
hr = led1509306_req_dispatch(board, &req);
if (FAILED(hr)) {
dprintf("LED Strip: Processing error: %x\n", (int) hr);
}
}
}
static HRESULT led1509306_req_dispatch(int board, const struct led1509306_req_any *req)
{
switch (req->cmd) {
case LED_15093_06_CMD_RESET:
return led1509306_req_reset(board, req);
case LED_15093_06_CMD_BOARD_INFO:
return led1509306_req_get_board_info(board);
case LED_15093_06_CMD_FW_SUM:
return led1509306_req_get_fw_sum(board);
case LED_15093_06_CMD_PROTOCOL_VER:
return led1509306_req_get_protocol_ver(board);
case LED_15093_06_CMD_BOARD_STATUS:
return led1509306_req_get_board_status(board);
case LED_15093_06_CMD_SET_LED:
return led1509306_req_set_led(board, req);
case LED_15093_06_CMD_SET_DISABLE_RESPONSE:
return led1509306_req_set_disable_response(board, req);
case LED_15093_06_CMD_SET_TIMEOUT:
return led1509306_req_set_timeout(board, req);
default:
dprintf("LED Strip: Unhandled command %02x\n", req->cmd);
return S_OK;
}
}
static HRESULT led1509306_req_reset(int board, const struct led1509306_req_any *req)
{
dprintf("LED Strip: Reset (board %u, type %02x)\n", board, req->payload[0]);
if (req->payload[0] != 0xd9)
dprintf("LED Strip: Warning -- Unknown reset type %02x\n", req->payload[0]);
led1509306_per_board_vars[board].enable_response = true;
struct led1509306_resp_any resp;
memset(&resp, 0, sizeof(resp));
resp.hdr.sync = LED_15093_06_FRAME_SYNC;
resp.hdr.dest_adr = led1509306_host_adr;
resp.hdr.src_adr = led1509306_board_adr;
resp.hdr.nbytes = 3;
resp.status = 1;
resp.cmd = LED_15093_06_CMD_RESET;
resp.report = 1;
return led1509306_frame_encode(&led1509306_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes);
}
static HRESULT led1509306_req_get_board_info(int board)
{
dprintf("LED Strip: Get board info (board %u)\n", board);
struct led1509306_resp_board_info resp;
memset(&resp, 0, sizeof(resp));
resp.hdr.sync = LED_15093_06_FRAME_SYNC;
resp.hdr.dest_adr = led1509306_host_adr;
resp.hdr.src_adr = led1509306_board_adr;
resp.hdr.nbytes = sizeof(resp.data) + 3;
resp.status = 1;
resp.cmd = LED_15093_06_CMD_BOARD_INFO;
resp.report = 1;
memcpy(resp.data.board_num, led1509306_board_num, sizeof(resp.data.board_num));
resp.data._0a = 0x0a;
memcpy(resp.data.chip_num, led1509306_chip_num, sizeof(resp.data.chip_num));
resp.data._ff = 0xff;
resp.data.fw_ver = led1509306_fw_ver;
return led1509306_frame_encode(&led1509306_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes);
}
static HRESULT led1509306_req_get_fw_sum(int board)
{
dprintf("LED Strip: Get firmware checksum (board %u)\n", board);
struct led1509306_resp_any resp;
memset(&resp, 0, sizeof(resp));
resp.hdr.sync = LED_15093_06_FRAME_SYNC;
resp.hdr.dest_adr = led1509306_host_adr;
resp.hdr.src_adr = led1509306_board_adr;
resp.hdr.nbytes = 2 + 3;
resp.status = 1;
resp.cmd = LED_15093_06_CMD_FW_SUM;
resp.report = 1;
resp.data[0] = (led1509306_fw_sum >> 8) & 0xff;
resp.data[1] = led1509306_fw_sum & 0xff;
return led1509306_frame_encode(&led1509306_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes);
}
static HRESULT led1509306_req_get_protocol_ver(int board)
{
dprintf("LED Strip: Get protocol version (board %u)\n", board);
struct led1509306_resp_any resp;
memset(&resp, 0, sizeof(resp));
resp.hdr.sync = LED_15093_06_FRAME_SYNC;
resp.hdr.dest_adr = led1509306_host_adr;
resp.hdr.src_adr = led1509306_board_adr;
resp.hdr.nbytes = 3 + 3;
resp.status = 1;
resp.cmd = LED_15093_06_CMD_PROTOCOL_VER;
resp.report = 1;
resp.data[0] = 1;
resp.data[1] = 1;
resp.data[2] = 4;
return led1509306_frame_encode(&led1509306_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes);
}
static HRESULT led1509306_req_get_board_status(int board)
{
dprintf("LED Strip: Get board status (board %u)\n", board);
struct led1509306_resp_any resp;
memset(&resp, 0, sizeof(resp));
resp.hdr.sync = LED_15093_06_FRAME_SYNC;
resp.hdr.dest_adr = led1509306_host_adr;
resp.hdr.src_adr = led1509306_board_adr;
resp.hdr.nbytes = 4 + 3;
resp.status = 1;
resp.cmd = LED_15093_06_CMD_BOARD_STATUS;
resp.report = 1;
resp.data[0] = 0;
resp.data[1] = 0;
resp.data[2] = 0;
resp.data[3] = 0;
return led1509306_frame_encode(&led1509306_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes);
}
static HRESULT led1509306_req_set_led(int board, const struct led1509306_req_any *req)
{
// dprintf("LED Strip: Set LED (board %u)\n", board);
if (!led1509306_per_board_vars[board].enable_response)
return S_OK;
struct led1509306_resp_any resp;
memset(&resp, 0, sizeof(resp));
resp.hdr.sync = LED_15093_06_FRAME_SYNC;
resp.hdr.dest_adr = led1509306_host_adr;
resp.hdr.src_adr = led1509306_board_adr;
resp.hdr.nbytes = 3;
resp.status = 1;
resp.cmd = LED_15093_06_CMD_SET_LED;
resp.report = 1;
return led1509306_frame_encode(&led1509306_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes);
}
static HRESULT led1509306_req_set_disable_response(int board, const struct led1509306_req_any *req)
{
dprintf("LED Strip: Disable LED responses (board %u)\n", board);
led1509306_per_board_vars[board].enable_response = !req->payload[0];
struct led1509306_resp_any resp;
memset(&resp, 0, sizeof(resp));
resp.hdr.sync = LED_15093_06_FRAME_SYNC;
resp.hdr.dest_adr = led1509306_host_adr;
resp.hdr.src_adr = led1509306_board_adr;
resp.hdr.nbytes = 1 + 3;
resp.status = 1;
resp.cmd = LED_15093_06_CMD_SET_DISABLE_RESPONSE;
resp.report = 1;
resp.data[0] = req->payload[0];
return led1509306_frame_encode(&led1509306_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes);
}
static HRESULT led1509306_req_set_timeout(int board, const struct led1509306_req_any *req)
{
dprintf("LED Strip: Set timeout (board %u)\n", board);
// not actually implemented, but respond correctly anyway
struct led1509306_resp_any resp;
memset(&resp, 0, sizeof(resp));
resp.hdr.sync = LED_15093_06_FRAME_SYNC;
resp.hdr.dest_adr = led1509306_host_adr;
resp.hdr.src_adr = led1509306_board_adr;
resp.hdr.nbytes = 2 + 3;
resp.status = 1;
resp.cmd = LED_15093_06_CMD_SET_TIMEOUT;
resp.report = 1;
resp.data[0] = req->payload[0];
resp.data[1] = req->payload[1];
return led1509306_frame_encode(&led1509306_per_board_vars[board].boarduart.readable, &resp, sizeof(resp.hdr) + resp.hdr.nbytes);
}

16
fgohook/led1509306.h Normal file
View File

@ -0,0 +1,16 @@
#pragma once
#include <windows.h>
#include <stdbool.h>
struct led1509306_config {
bool enable;
unsigned int port_no;
char board_number[8];
char chip_number[5];
uint8_t fw_ver;
uint16_t fw_sum;
};
HRESULT led1509306_hook_init(const struct led1509306_config *cfg);

36
fgohook/meson.build Normal file
View File

@ -0,0 +1,36 @@
shared_library(
'fgohook',
name_prefix : '',
include_directories : inc,
implicit_include_directories : false,
vs_module_defs : 'fgohook.def',
c_pch : '../precompiled.h',
dependencies : [
capnhook.get_variable('hook_dep'),
capnhook.get_variable('hooklib_dep'),
xinput_lib,
],
link_with : [
aimeio_lib,
board_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',
'led1509306.c',
'led1509306.h',
],
)