fgo: fgohook finally added

Credits:
- Coburn
- domeori
- Mitsuhide
- OLEG
- rakisaionji
This commit is contained in:
Dniel97 2023-11-11 22:47:47 +01:00
parent 946ea7ef3b
commit a2db39c58c
Signed by untrusted user: Dniel97
GPG Key ID: 6180B3C768FB2E08
30 changed files with 4478 additions and 0 deletions

View File

@ -73,6 +73,22 @@ $(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_64)/fgohook/fgohook.dll \
$(DIST_DIR)/fgo/config_hook.json \
$(DIST_DIR)/fgo/segatools.ini \
$(DIST_DIR)/fgo/start.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

12
dist/fgo/config_hook.json vendored Normal file
View File

@ -0,0 +1,12 @@
{
"aime" :
{
"firmware_path" :
[
"am\\aime_firm\\update_15396_6728_94.bin",
"am\\aime_firm\\TN32MSEC003S_V12.hex",
"am\\aime_firm\\837-15286-P_LPC1112_NFC_RW_LED_BD_0x92.bin"
],
"high_baudrate" : true
}
}

84
dist/fgo/segatools.ini vendored Normal file
View File

@ -0,0 +1,84 @@
[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=
[aime]
; Controls emulation of the Aime card reader assembly.
enable=1
aimePath=DEVICE\aime.txt
[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
[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
[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
[deckReader]
; 837-15345 RFID deck reader emulation setting.
enable=1
[ledstrip]
; 837-15093-06 LED strip emulation setting.
enable=1
; FTDI board currently not working properly. Use com0com (Create a virtual pair
; for 21 and 51) or use any USB to Serial Adapter.
port=21
; -----------------------------------------------------------------------------
; 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.
; 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

11
dist/fgo/start.bat vendored Normal file
View File

@ -0,0 +1,11 @@
@echo off
cd /d %~dp0
inject -d -k fgohook.dll ago.exe
taskkill /f /im amdaemon.exe > nul 2>&1
echo.
echo Game processes have terminated
pause

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',
],
)

20
fgoio/config.c Normal file
View File

@ -0,0 +1,20 @@
#include <windows.h>
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include "fgoio/config.h"
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", '1', filename);
cfg->vk_service = GetPrivateProfileIntW(L"io4", L"service", '2', filename);
cfg->vk_coin = GetPrivateProfileIntW(L"io4", L"coin", '3', filename);
}

16
fgoio/config.h Normal file
View File

@ -0,0 +1,16 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
struct fgo_io_config {
uint8_t vk_test;
uint8_t vk_service;
uint8_t vk_coin;
};
void fgo_io_config_load(
struct fgo_io_config *cfg,
const wchar_t *filename);

141
fgoio/fgoio.c Normal file
View File

@ -0,0 +1,141 @@
#include <windows.h>
#include <xinput.h>
#include <math.h>
#include <limits.h>
#include <stdint.h>
#include "fgoio/fgoio.h"
#include "fgoio/config.h"
#include "util/dprintf.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 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, L".\\segatools.ini");
return S_OK;
}
HRESULT fgo_io_poll(void)
{
XINPUT_STATE xi;
WORD xb;
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;
}
memset(&xi, 0, sizeof(xi));
XInputGetState(0, &xi);
xb = xi.Gamepad.wButtons;
if (xi.Gamepad.bLeftTrigger > 64) {
fgo_gamebtn |= FGO_IO_GAMEBTN_SPEED_UP;
}
if (xb & XINPUT_GAMEPAD_LEFT_SHOULDER) {
fgo_gamebtn |= FGO_IO_GAMEBTN_TARGET;
}
if (xb & XINPUT_GAMEPAD_A) {
fgo_gamebtn |= FGO_IO_GAMEBTN_ATTACK;
}
if (xb & XINPUT_GAMEPAD_Y) {
fgo_gamebtn |= FGO_IO_GAMEBTN_NOBLE_PHANTASHM;
}
if (xb & XINPUT_GAMEPAD_LEFT_THUMB) {
fgo_gamebtn |= FGO_IO_GAMEBTN_CAMERA;
}
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 > XINPUT_GAMEPAD_LEFT_THUMB_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 -= XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE;
// optionally normalize the magnitude with respect to its expected range
// giving a magnitude value of 0.0 to 1.0
normalizedMagnitude = magnitude / (32767 - XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
} else // if the controller is in the deadzone zero out the magnitude
{
magnitude = 0.0;
normalizedMagnitude = 0.0;
}
fgo_stick_x = normalizedLX * normalizedMagnitude * 32767;
fgo_stick_y = normalizedLY * normalizedMagnitude * 32767;
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)
{
if (btn != NULL) {
*btn = fgo_gamebtn;
}
}
void fgo_io_get_analogs(int16_t *stick_x, int16_t *stick_y)
{
if (stick_x != NULL) {
*stick_x = fgo_stick_x;
}
if (stick_y != NULL) {
*stick_y = fgo_stick_y;
}
}

71
fgoio/fgoio.h Normal file
View File

@ -0,0 +1,71 @@
#pragma once
#include <windows.h>
#include <stdint.h>
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_PHANTASHM = 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 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 fgo_io_get_gamebtns(uint8_t *btn);
/* 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);

16
fgoio/meson.build Normal file
View File

@ -0,0 +1,16 @@
fgoio_lib = static_library(
'fgoio',
name_prefix : '',
include_directories : inc,
implicit_include_directories : false,
c_pch : '../precompiled.h',
dependencies : [
xinput_lib,
],
sources : [
'fgoio.c',
'fgoio.h',
'config.c',
'config.h',
],
)

View File

@ -21,4 +21,63 @@ void touch_screen_config_load(struct touch_screen_config *cfg, const wchar_t *fi
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);
}

View File

@ -4,6 +4,8 @@
#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);

View File

@ -31,5 +31,7 @@ hooklib_lib = static_library(
'spike.h',
'touch.c',
'touch.h',
'printer.c',
'printer.h',
],
)

2380
hooklib/printer.c Normal file

File diff suppressed because it is too large Load Diff

16
hooklib/printer.h Normal file
View File

@ -0,0 +1,16 @@
#pragma once
#include <windows.h>
#include <stdbool.h>
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];
};
void printer_hook_init(const struct printer_config *cfg, int rfid_port_no, HINSTANCE self);

View File

@ -65,6 +65,7 @@ subdir('mai2io')
subdir('cmio')
subdir('mercuryio')
subdir('cxbio')
subdir('fgoio')
subdir('chunihook')
subdir('divahook')
@ -79,3 +80,4 @@ subdir('mai2hook')
subdir('cmhook')
subdir('mercuryhook')
subdir('cxbhook')
subdir('fgohook')