49 Commits

Author SHA1 Message Date
4ffcf25555 added freeplay setting to ALLS games 2023-12-03 22:22:17 +01:00
3dd6054a1e chusan, fgo, mu3: fixed LED 15093 board 2023-12-03 21:34:38 +01:00
793417e891 added new aime card reader generation
- Added new aime generation: 837-15286 and 837-15396
- New config setting `[aime] gen=3` for 837-15396
- Updated LED information for card reader
- Updated all games with the needed reader generation?
2023-12-03 18:45:42 +01:00
8c12853051 cm: disabled coin selector AS6DB 2023-11-30 00:02:00 +01:00
a3fd2fb926 Merge branch 'master' into develop 2023-11-29 23:07:24 +01:00
8b1d0cfefa LED board improvements and cleanup 2023-11-25 23:00:05 +01:00
d86baab852 fgo: working FTDI hook by @OLEG
from: 88a0026f0b
2023-11-22 21:43:42 +01:00
b9fd59fd70 idz: added fullscreen patch by @Felix (again) 2023-11-20 18:04:45 +01:00
146fac9287 dns: block more All.NET requests 2023-11-12 21:16:02 +01:00
3cf5cbb793 added LED 15093-06 credits and docs 2023-11-12 19:14:42 +01:00
a2db39c58c fgo: fgohook finally added
Credits:
- Coburn
- domeori
- Mitsuhide
- OLEG
- rakisaionji
2023-11-11 23:05:11 +01:00
946ea7ef3b chusan: fixed LED configs 2023-11-11 22:41:04 +01:00
25562e37f9 nusec: fixes keychip not found error 0949 2023-11-02 23:32:13 +01:00
d521eeb43e idz, idac, swdc: Added separate pedals config, better XInput controls
- Configure separate pedals which are not part of the steering wheel (base)
 - It is now possible to have a different steering wheel, pedal and shifter brand all connected to 3 different USB ports
- XInput does not have a deadzone at the end of the max steering anymore and the `restrict` setting works as intended
2023-10-05 00:46:54 +02:00
6c45d0995b fix cursor being annoying by @OLEG
95273ccecb
2023-10-05 00:37:25 +02:00
a4bd570cfc chusan, mai2, mu3, swdc: removed emoney config 2023-10-05 00:35:55 +02:00
37793fc051 Merge branch 'master' into develop 2023-10-05 00:30:24 +02:00
f5a7e5b821 epay: added config because git is dumb 2023-09-15 19:58:46 +02:00
5ef0cf6181 Merge branch 'master' into develop 2023-09-15 19:56:48 +02:00
31203daa09 idz, idac, swdc: fixed restrict config setting 2023-09-05 00:24:43 +02:00
80718104f6 swdc: updated config, fixed start.bat 2023-09-05 00:23:17 +02:00
eb2eef927a idac: added left and right button mapping 2023-09-04 23:31:52 +02:00
9bef9e49f8 swdc: added aime firm to config_hook.json 2023-09-04 02:05:37 +02:00
d5a551482b idac: mapped left and right thumbstick to dpad left and right 2023-09-04 02:04:42 +02:00
49f729c501 idz, idac, swdc: switched to [xinput] config instead of [io] 2023-09-04 02:03:31 +02:00
91f69beae6 idac: fixed config error 2023-08-30 02:24:02 +02:00
51b11469d0 added aimeGen as default instead of feliciaGen 2023-08-29 22:57:04 +02:00
f0dc51d01e idac: added config_hook.json 2023-08-29 20:03:33 +02:00
ca4a8bd84d idz, idac, swdc: Added deadzone config 2023-08-29 02:22:05 +02:00
f6e961d4f4 Removed -f from start.bat and fixed idz 2023-08-29 00:49:58 +02:00
a69a9b5917 swdc: disabled built in XInput 2023-08-20 16:21:55 +02:00
97234f26d7 cm, chusan, mai2, mu3, swdc: added emoney config 2023-08-15 22:02:38 +02:00
2277bf7526 all: fix accounting issue (my bad) 2023-08-15 20:23:28 +02:00
80d5fc4bb2 mu3: Added basic keyboard and mouse lever emulation support 2023-08-15 17:40:32 +02:00
608c9ac1a6 chusan, cm, mai2, mu3: Added dipSw support and highBaudrate AiMe 2023-08-15 17:28:19 +02:00
3dc2ec6e69 idac, idz, swdc: Fixed DInput brake/accel, added cubic steering 2023-08-15 17:20:27 +02:00
28ef2d719a dinput: fixed POV bug 2023-07-19 12:31:57 +02:00
600f795104 disable dipsw by default 2023-07-14 01:07:22 +02:00
e5d17b82b2 updated Package.mk for mai2, cm 2023-07-14 01:03:36 +02:00
0ee081ca1b mai2: segatools added 2023-07-14 01:00:28 +02:00
01be6ee33c cardmaker: hook, touch screen hook added
Thanks @domeori https://dev.s-ul.net/domeori/segatools/-/tree/mr-imports
2023-07-14 00:59:24 +02:00
2a6a8bf8b2 chusan: added chusanhook, led board fix, config added
credits go to @domeori https://dev.s-ul.net/domeori/segatools/-/tree/mr-imports
2023-07-14 00:58:02 +02:00
90a6f1be7c mu3: coin input added 2023-07-14 00:54:30 +02:00
ec072667b3 swdc: first segatools added 2023-07-14 00:52:50 +02:00
89195ed60b Merge branch 'idac_test' into develop 2023-07-14 00:43:38 +02:00
c27ef9674d idac: added dipswitch support (beta) 2023-07-14 00:41:23 +02:00
da97d23b51 idac: first segatools support 2023-06-29 11:27:52 +02:00
ee6675dd73 Merge branch 'master' into idac_test 2023-06-27 21:11:45 +02:00
555784258a idac: test 2023-04-23 16:13:51 +02:00
206 changed files with 15871 additions and 173 deletions

View File

@ -73,6 +73,52 @@ $(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/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
$(V)mkdir -p $(BUILD_DIR_ZIP)/idac/DEVICE
$(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \
$(BUILD_DIR_64)/idachook/idachook.dll \
$(DIST_DIR)/idac/segatools.ini \
$(DIST_DIR)/idac/config_hook.json \
$(DIST_DIR)/idac/start.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_64)/swdchook/swdchook.dll \
$(DIST_DIR)/swdc/segatools.ini \
$(DIST_DIR)/swdc/start.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
@ -88,6 +134,27 @@ $(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/start.bat \
$(BUILD_DIR_ZIP)/chusan
$(V)cp $(BUILD_DIR_32)/chusanhook/chusanhook.dll \
$(BUILD_DIR_ZIP)/chusan/chusanhook_x86.dll
$(V)cp $(BUILD_DIR_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 ... $@
@ -104,6 +171,37 @@ $(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_64)/mai2hook/mai2hook.dll \
$(DIST_DIR)/mai2/segatools.ini \
$(DIST_DIR)/mai2/start.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_64)/cmhook/cmhook.dll \
$(DIST_DIR)/cm/config_hook.json \
$(DIST_DIR)/cm/segatools.ini \
$(DIST_DIR)/cm/start.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)/doc.zip: \
$(DOC_DIR)/config \
$(DOC_DIR)/chunihook.md \
@ -119,8 +217,13 @@ $(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 \
CHANGELOG.md \
README.md \

View File

@ -1,6 +1,6 @@
# Segatools
Version: `v005`
Version: `2023-11-22`
Loaders and hardware emulators for SEGA games that run on the Nu and ALLS platforms.
@ -12,10 +12,22 @@ Loaders and hardware emulators for SEGA games that run on the Nu and ALLS platfo
* [Chunithm Star (Plus)](doc/chunihook.md)
* [Chunithm Amazon (Plus)](doc/chunihook.md)
* [Chunithm Crystal (Plus)](doc/chunihook.md)
* Chunithm SUN
* Initial D
* [Initial D Arcade Stage Zero](doc/idzhook.md)
* Initial D THE ARCADE
* SEGA World Drivers Championship
* up to SEGA World Drivers Championship 2019
* Fate/Grand Order
* Fate/Grand Order Arcade
* ONGEKI
* up to bright MEMORY
* maimai DX
* up to maimai DX FESTiVAL PLUS
* Card Maker
* up to Card Maker 1.35
* Wacca
* Wacca Lilly R (WIP)
* up to WACCA Reverse
## End-users

View File

@ -17,6 +17,7 @@ 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 +41,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)
@ -67,6 +73,12 @@ static void aime_io_config_read(
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;
@ -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(

View File

@ -30,6 +30,8 @@ void aime_config_load(struct aime_config *cfg, const wchar_t *filename)
aime_dll_config_load(&cfg->dll, filename);
cfg->enable = GetPrivateProfileIntW(L"aime", L"enable", 1, filename);
cfg->high_baudrate = GetPrivateProfileIntW(L"aime", L"highbaud", 1, filename);
cfg->gen = GetPrivateProfileIntW(L"aime", L"gen", 0, filename);
}
void io4_config_load(struct io4_config *cfg, const wchar_t *filename)

View File

@ -28,7 +28,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 +40,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");
@ -232,15 +232,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 +316,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 */

222
board/led15093-cmd.h Normal file
View File

@ -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;
uint8_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;
uint8_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;
};

196
board/led15093-frame.c Normal file
View File

@ -0,0 +1,196 @@
#include <windows.h>
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#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;
}

34
board/led15093-frame.h Normal file
View File

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

1107
board/led15093.c Normal file

File diff suppressed because it is too large Load Diff

21
board/led15093.h Normal file
View File

@ -0,0 +1,21 @@
#pragma once
#include <windows.h>
#include <stdbool.h>
#include <stdint.h>
struct led15093_config {
bool enable;
bool high_baudrate;
unsigned int port_no;
char board_number[8];
char chip_number[5];
char boot_chip_number[5];
uint8_t fw_ver;
uint16_t fw_sum;
};
HRESULT led15093_hook_init(const struct led15093_config *cfg, unsigned int first_port,
unsigned int num_boards, uint8_t board_adr, uint8_t host_adr);

View File

@ -20,6 +20,11 @@ board_lib = static_library(
'io3.h',
'io4.c',
'io4.h',
'led15093-cmd.h',
'led15093-frame.c',
'led15093-frame.h',
'led15093.c',
'led15093.h',
'sg-cmd.c',
'sg-cmd.h',
'sg-frame.c',

View File

@ -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 {

View File

@ -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,
const char *sg_led_info[] = {
"15084\xFF\x10\x00\x12",
"000-00000\xFF\x11\x40",
// maybe the same?
"000-00000\xFF\x11\x40"
};
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));
unsigned int len = strlen(sg_led_info[led->gen - 1]);
sg_res_init(&res->res, req, len);
memcpy(res->payload, sg_led_info[led->gen - 1], len);
return S_OK;
}

View File

@ -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(

View File

@ -15,6 +15,7 @@ enum {
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_SEND_HEX_DATA = 0x61,
SG_NFC_CMD_RESET = 0x62,
SG_NFC_CMD_FELICA_ENCAP = 0x71,
};

View File

@ -65,10 +65,23 @@ static HRESULT sg_nfc_cmd_dummy(
const struct sg_req_header *req,
struct sg_res_header *res);
static const char *hw_version[] = {
"TN32MSEC003S H/W Ver3.0",
"837-15286",
"837-15396"
};
static const char *fw_version[] = {
"TN32MSEC003S F/W Ver1.2",
"\x94",
"\x94"
};
void sg_nfc_init(
struct sg_nfc *nfc,
uint8_t addr,
const struct sg_nfc_ops *ops,
unsigned int gen,
void *ops_ctx)
{
assert(nfc != NULL);
@ -77,6 +90,7 @@ void sg_nfc_init(
nfc->ops = ops;
nfc->ops_ctx = ops_ctx;
nfc->addr = addr;
nfc->gen = gen;
}
#ifdef NDEBUG
@ -176,6 +190,7 @@ static HRESULT sg_nfc_dispatch(
case SG_NFC_CMD_MIFARE_SET_KEY_BANA:
case SG_NFC_CMD_RADIO_ON:
case SG_NFC_CMD_RADIO_OFF:
case SG_NFC_CMD_SEND_HEX_DATA: // TODO: implement?
return sg_nfc_cmd_dummy(nfc, &req->simple, &res->simple);
default:
@ -202,9 +217,11 @@ static HRESULT sg_nfc_cmd_get_fw_version(
const struct sg_req_header *req,
struct sg_nfc_res_get_fw_version *res)
{
unsigned int len = strlen(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, len);
memcpy(res->version, fw_version[nfc->gen - 1], len);
return S_OK;
}
@ -214,9 +231,11 @@ static HRESULT sg_nfc_cmd_get_hw_version(
const struct sg_req_header *req,
struct sg_nfc_res_get_hw_version *res)
{
unsigned int len = strlen(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, len);
memcpy(res->version, hw_version[nfc->gen - 1], len);
return S_OK;
}

View File

@ -22,6 +22,7 @@ struct sg_nfc {
const struct sg_nfc_ops *ops;
void *ops_ctx;
uint8_t addr;
unsigned int gen;
struct felica felica;
struct mifare mifare;
};
@ -30,6 +31,7 @@ void sg_nfc_init(
struct sg_nfc *nfc,
uint8_t addr,
const struct sg_nfc_ops *ops,
unsigned int gen,
void *ops_ctx);
void sg_nfc_transact(

View File

@ -48,6 +48,7 @@ static struct sg_led sg_reader_led;
HRESULT sg_reader_hook_init(
const struct aime_config *cfg,
unsigned int port_no,
unsigned int gen,
HINSTANCE self)
{
HRESULT hr;
@ -65,11 +66,25 @@ 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);
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, 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;
}
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);

View File

@ -9,9 +9,12 @@
struct aime_config {
struct aime_dll_config dll;
bool enable;
bool high_baudrate;
unsigned int gen;
};
HRESULT sg_reader_hook_init(
const struct aime_config *cfg,
unsigned int port_no,
unsigned int gen,
HINSTANCE self);

View File

@ -102,7 +102,7 @@ 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;

View File

@ -43,6 +43,66 @@ void slider_config_load(struct slider_config *cfg, const wchar_t *filename)
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;
cfg->high_baudrate = GetPrivateProfileIntW(L"led15093", L"highBaudrate", 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)
@ -58,4 +118,5 @@ void chuni_hook_config_load(
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);
}

View File

@ -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(

View File

@ -96,7 +96,13 @@ static DWORD CALLBACK chuni_pre_startup(void)
goto fail;
}
hr = sg_reader_hook_init(&chuni_hook_cfg.aime, 12, chuni_hook_mod);
hr = led15093_hook_init(&chuni_hook_cfg.led15093, 10, 2, 2, 1);
if (FAILED(hr)) {
goto fail;
}
hr = sg_reader_hook_init(&chuni_hook_cfg.aime, 12, 1, chuni_hook_mod);
if (FAILED(hr)) {
goto fail;

View File

@ -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;
}

View File

@ -3,10 +3,13 @@
#include <process.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include "chuniio/chuniio.h"
#include "chuniio/config.h"
#include "util/dprintf.h"
static unsigned int __stdcall chuni_io_slider_thread_proc(void *ctx);
static bool chuni_io_coin;
@ -15,6 +18,7 @@ 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;
static HANDLE chuni_io_slider_led_port;
uint16_t chuni_io_get_api_version(void)
{
@ -50,27 +54,48 @@ 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_test) & 0x8000) {
*opbtn |= CHUNI_IO_OPBTN_TEST;
}
if (GetAsyncKeyState(chuni_io_cfg.vk_service)) {
*opbtn |= 0x02; /* Service */
if (GetAsyncKeyState(chuni_io_cfg.vk_service) & 0x8000) {
*opbtn |= CHUNI_IO_OPBTN_SERVICE;
}
if (GetAsyncKeyState(chuni_io_cfg.vk_ir)) {
if (chuni_io_hand_pos < 6) {
chuni_io_hand_pos++;
if (GetAsyncKeyState(chuni_io_cfg.vk_coin) & 0x8000) {
if (!chuni_io_coin) {
chuni_io_coin = true;
*opbtn |= CHUNI_IO_OPBTN_COIN;
}
} else {
if (chuni_io_hand_pos > 0) {
chuni_io_hand_pos--;
}
chuni_io_coin = false;
}
for (i = 0 ; i < 6 ; i++) {
if (chuni_io_hand_pos > i) {
*beams |= (1 << i);
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
// IR format is beams[5:0] = {b5,b6,b3,b4,b1,b2};
for (i = 0 ; i < 3 ; i++) {
if (GetAsyncKeyState(chuni_io_cfg.vk_ir[i*2]) & 0x8000)
*beams |= (1 << (i*2+1));
if (GetAsyncKeyState(chuni_io_cfg.vk_ir[i*2+1]) & 0x8000)
*beams |= (1 << (i*2));
}
}
}
@ -82,6 +107,8 @@ HRESULT chuni_io_slider_init(void)
void chuni_io_slider_start(chuni_io_slider_callback_t callback)
{
BOOL status;
if (chuni_io_slider_thread != NULL) {
return;
}
@ -93,6 +120,39 @@ void chuni_io_slider_start(chuni_io_slider_callback_t callback)
callback,
0,
NULL);
chuni_io_slider_led_port = CreateFileW(chuni_io_cfg.led_com,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
0,
NULL);
if (chuni_io_slider_led_port == INVALID_HANDLE_VALUE)
dprintf("Chunithm LEDs: Failed to open COM port (Attempted on %S)\n", chuni_io_cfg.led_com);
else
dprintf("Chunithm LEDs: COM Port Success!\n");
DCB dcb_serial_params = { 0 };
dcb_serial_params.DCBlength = sizeof(dcb_serial_params);
status = GetCommState(chuni_io_slider_led_port, &dcb_serial_params);
dcb_serial_params.BaudRate = CBR_115200; // Setting BaudRate = 115200
dcb_serial_params.ByteSize = 8; // Setting ByteSize = 8
dcb_serial_params.StopBits = ONESTOPBIT;// Setting StopBits = 1
dcb_serial_params.Parity = NOPARITY; // Setting Parity = None
SetCommState(chuni_io_slider_led_port, &dcb_serial_params);
COMMTIMEOUTS timeouts = { 0 };
timeouts.ReadIntervalTimeout = 50; // in milliseconds
timeouts.ReadTotalTimeoutConstant = 50; // in milliseconds
timeouts.ReadTotalTimeoutMultiplier = 10; // in milliseconds
timeouts.WriteTotalTimeoutConstant = 50; // in milliseconds
timeouts.WriteTotalTimeoutMultiplier = 10; // in milliseconds
SetCommTimeouts(chuni_io_slider_led_port, &timeouts);
}
void chuni_io_slider_stop(void)
@ -107,10 +167,34 @@ void chuni_io_slider_stop(void)
CloseHandle(chuni_io_slider_thread);
chuni_io_slider_thread = NULL;
chuni_io_slider_stop_flag = false;
dprintf("Chunithm LEDs: Closing COM port\n");
CloseHandle(chuni_io_slider_led_port);
}
void chuni_io_slider_set_leds(const uint8_t *rgb)
{
if (chuni_io_slider_led_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(chuni_io_slider_led_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);
}
}
static unsigned int __stdcall chuni_io_slider_thread_proc(void *ctx)

View File

@ -15,6 +15,12 @@
#include <stdbool.h>
#include <stdint.h>
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

View File

@ -3,6 +3,7 @@
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include "chuniio/config.h"
@ -17,20 +18,35 @@ static const int chuni_io_default_cells[] = {
'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", '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);
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);
@ -40,4 +56,8 @@ void chuni_io_config_load(
chuni_io_default_cells[i],
filename);
}
GetPrivateProfileStringW(L"slider", L"ledport", L"COM2", port_input, 6, filename);
wcsncpy(cfg->led_com, L"\\\\.\\", 4);
wcsncat_s(cfg->led_com, 11, port_input, 6);
}

View File

@ -7,8 +7,10 @@ struct chuni_io_config {
uint8_t vk_test;
uint8_t vk_service;
uint8_t vk_coin;
uint8_t vk_ir;
uint8_t vk_ir_emu;
uint8_t vk_ir[6];
uint8_t vk_cell[32];
wchar_t led_com[12];
};
void chuni_io_config_load(

118
chusanhook/chuni-dll.c Normal file
View File

@ -0,0 +1,118 @@
#include <windows.h>
#include <assert.h>
#include <stdlib.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),
}
};
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);
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;
} else {
owned = NULL;
src = self;
}
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 = chuni_dll_syms;
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);
goto end;
} else {
dprintf("Internal error: could not reflect \"%s\"\n", sym->sym);
}
}
owned = NULL;
end:
if (owned != NULL) {
FreeLibrary(owned);
}
return hr;
}

24
chusanhook/chuni-dll.h Normal file
View File

@ -0,0 +1,24 @@
#pragma once
#include <windows.h>
#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);
};
struct chuni_dll_config {
wchar_t path[MAX_PATH];
};
extern struct chuni_dll chuni_dll;
HRESULT chuni_dll_init(const struct chuni_dll_config *cfg, HINSTANCE self);

22
chusanhook/chusanhook.def Normal file
View File

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

152
chusanhook/config.c Normal file
View File

@ -0,0 +1,152 @@
#include <assert.h>
#include <stddef.h>
#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
// for else.. is that possible? idk
#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;
cfg->high_baudrate = GetPrivateProfileIntW(L"led15093", L"highBaudrate", 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));
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);
chuni_dll_config_load(&cfg->dll, filename);
slider_config_load(&cfg->slider, filename);
led15093_config_load(&cfg->led15093, filename);
}

34
chusanhook/config.h Normal file
View File

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

171
chusanhook/dllmain.c Normal file
View File

@ -0,0 +1,171 @@
#include <windows.h>
#include <stddef.h>
#include <stdlib.h>
#include "amex/amex.h"
#include "board/sg-reader.h"
#include "board/vfd.h"
#include "chusanhook/config.h"
#include "chusanhook/io4.h"
#include "chusanhook/slider.h"
#include "chuniio/chuniio.h"
#include "hook/process.h"
#include "gfxhook/d3d9.h"
#include "gfxhook/gfx.h"
#include "hooklib/serial.h"
#include "hooklib/spike.h"
#include "platform/platform.h"
#include "util/dprintf.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, L".\\segatools.ini");
/* 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 */
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 *dipsw = &chusan_hook_cfg.platform.dipsw.dipsw[0];
if (dipsw[1] != dipsw[2]) {
dprintf("DipSw: DipSw2 and 3 must be set to the same value!\n");
goto fail;
}
for (int i = 0; i < 3; i++) {
switch (i) {
case 0:
dprintf("DipSw: NetInstall: %s\n", dipsw[0] ? "Server" : "Client");
break;
case 1:
dprintf("DipSw: Monitor Type: %dFPS\n", dipsw[1] ? 60 : 120);
break;
case 2:
dprintf("DipSw: Aime Reader: %s\n", dipsw[2] ? "CVT" : "SP");
break;
}
}
unsigned int first_port = dipsw[1] ? 2 : 20;
hr = led15093_hook_init(&chusan_hook_cfg.led15093, first_port, 2, 2, 1);
if (FAILED(hr)) {
goto fail;
}
hr = sg_reader_hook_init(&chusan_hook_cfg.aime, 4, dipsw[2] ? 2 : 3, chusan_hook_mod);
if (FAILED(hr)) {
goto fail;
}
/* Initialize debug helpers */
spike_hook_init(L".\\segatools.ini");
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);
}

107
chusanhook/io4.c Normal file
View File

@ -0,0 +1,107 @@
#include <windows.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#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);
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;
}
if (opbtn & CHUNI_IO_OPBTN_COIN) {
coins++;
}
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;
}

5
chusanhook/io4.h Normal file
View File

@ -0,0 +1,5 @@
#pragma once
#include <windows.h>
HRESULT chusan_io4_hook_init(const struct io4_config *cfg);

16
chusanhook/led1509306.h Normal file
View File

@ -0,0 +1,16 @@
#pragma once
#include <windows.h>
#include <stdbool.h>
struct led1509306_config {
bool enable;
bool cvt_port;
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);

32
chusanhook/meson.build Normal file
View File

@ -0,0 +1,32 @@
shared_library(
'chusanhook',
name_prefix : '',
include_directories : inc,
implicit_include_directories : false,
vs_module_defs : 'chusanhook.def',
c_pch : '../precompiled.h',
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',
],
)

249
chusanhook/slider.c Normal file
View File

@ -0,0 +1,249 @@
#include <windows.h>
#include <assert.h>
#include <process.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#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 0
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 0
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);
}

11
chusanhook/slider.h Normal file
View File

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

106
cmhook/cm-dll.c Normal file
View File

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

20
cmhook/cm-dll.h Normal file
View File

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

17
cmhook/cmhook.def Normal file
View File

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

42
cmhook/config.c Normal file
View File

@ -0,0 +1,42 @@
#include <assert.h>
#include <stddef.h>
#include "board/config.h"
#include "hooklib/config.h"
#include "hooklib/dvd.h"
#include "cmhook/config.h"
#include "platform/config.h"
void cm_dll_config_load(
struct cm_dll_config *cfg,
const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
GetPrivateProfileStringW(
L"cmio",
L"path",
L"",
cfg->path,
_countof(cfg->path),
filename);
}
void cm_hook_config_load(
struct cm_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);
cm_dll_config_load(&cfg->dll, filename);
}

29
cmhook/config.h Normal file
View File

@ -0,0 +1,29 @@
#pragma once
#include <stddef.h>
#include "board/config.h"
#include "hooklib/dvd.h"
#include "hooklib/touch.h"
#include "cmhook/cm-dll.h"
#include "platform/config.h"
struct cm_hook_config {
struct platform_config platform;
struct aime_config aime;
struct dvd_config dvd;
struct io4_config io4;
struct cm_dll_config dll;
struct touch_screen_config touch;
};
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);

119
cmhook/dllmain.c Normal file
View File

@ -0,0 +1,119 @@
#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/serial.h"
#include "hooklib/spike.h"
#include "cmhook/config.h"
#include "cmhook/io4.h"
#include "cmhook/cm-dll.h"
#include "cmhook/unity.h"
#include "platform/platform.h"
#include "util/dprintf.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, L".\\segatools.ini");
/* 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();
/* Initialize emulation hooks */
hr = platform_hook_init(
&cm_hook_cfg.platform,
"SDED",
"ACA1",
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(2);
if (FAILED(hr)) {
goto fail;
}
hr = cm_dll_init(&cm_hook_cfg.dll, cm_hook_mod);
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();
/* Initialize debug helpers */
spike_hook_init(L".\\segatools.ini");
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);
}

69
cmhook/io4.c Normal file
View File

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

7
cmhook/io4.h Normal file
View File

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

32
cmhook/meson.build Normal file
View File

@ -0,0 +1,32 @@
shared_library(
'cmhook',
name_prefix : '',
include_directories : inc,
implicit_include_directories : false,
vs_module_defs : 'cmhook.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,
cmio_lib,
platform_lib,
util_lib,
],
sources : [
'config.c',
'config.h',
'dllmain.c',
'io4.c',
'io4.h',
'cm-dll.c',
'cm-dll.h',
'unity.h',
'unity.c',
],
)

95
cmhook/unity.c Normal file
View File

@ -0,0 +1,95 @@
#include <stdbool.h>
#include <windows.h>
#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;
}

3
cmhook/unity.h Normal file
View File

@ -0,0 +1,3 @@
#pragma once
void unity_hook_init(void);

54
cmio/cmio.c Normal file
View File

@ -0,0 +1,54 @@
#include <windows.h>
#include <xinput.h>
#include <limits.h>
#include <stdint.h>
#include "cmio/cmio.h"
#include "cmio/config.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, L".\\segatools.ini");
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;
}
}

44
cmio/cmio.h Normal file
View File

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

22
cmio/config.c Normal file
View File

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

16
cmio/config.h Normal file
View File

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

13
cmio/meson.build Normal file
View File

@ -0,0 +1,13 @@
cmio_lib = static_library(
'cmio',
name_prefix : '',
include_directories : inc,
implicit_include_directories : false,
c_pch : '../precompiled.h',
sources : [
'cmio.c',
'cmio.h',
'config.c',
'config.h',
],
)

View File

@ -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;

View File

@ -38,6 +38,10 @@ monitor=0
; Leave empty if you want to use Segatools built-in keyboard input.
path=
[led15093]
; 837-15093-06 LED strip emulation setting.
enable=1
[chuniio]
; To use a custom Chunithm IO DLL enter its path here.
; Leave empty if you want to use Segatools built-in keyboard input.

6
dist/chusan/config_hook.json vendored Normal file
View File

@ -0,0 +1,6 @@
{
"allnet_auth":
{
"type": "1.0"
}
}

128
dist/chusan/segatools.ini vendored Normal file
View File

@ -0,0 +1,128 @@
[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]
; Enable aime reader emulation.
enable=1
aimePath=DEVICE\aime.txt
; Enable high baud rate.
;highbaud=1
[aimeio]
; x64 aimeio dll path.
; Uncomment this if you have custom aime implementation.
;path64=
[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
[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
[gpio]
; ALLS DIP switches.
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 (SP), 1 = 60FPS (CVT)
dipsw2=1
; Aime reader hardware type: 0 = SP, 1 = CVT. Both dipsw2 and dipsw3 must be
; the same value.
dipsw3=1
[gfx]
; 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
[led15093]
; 837-15093-06 LED strip emulation setting.
enable=1
[chuniio]
; Uncomment this if you have custom chuniio implementation.
; 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 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
; 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
; Enable slider LED serial output. This follows OpeNITHM Serial LED Protocol.
; eg. COM5
;ledport=

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

@ -0,0 +1,11 @@
@echo off
pushd %~dp0
start /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

9
dist/cm/config_hook.json vendored Normal file
View File

@ -0,0 +1,9 @@
{
"credit" :
{
"coin_selector_AS6DB" :
{
"enable" : false
}
}
}

63
dist/cm/segatools.ini vendored Normal file
View File

@ -0,0 +1,63 @@
[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]
; Enable aime reader emulation.
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
[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
[gpio]
; ALLS DIP switches.
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
[touch]
; Enable/Disable WinTouch emulation
enable=0
; -----------------------------------------------------------------------------
; 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 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

12
dist/cm/start.bat vendored Normal file
View File

@ -0,0 +1,12 @@
@echo off
pushd %~dp0
start /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

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

@ -0,0 +1,89 @@
[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
[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.
portNo=17
; -----------------------------------------------------------------------------
; 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

9
dist/idac/config_hook.json vendored Normal file
View File

@ -0,0 +1,9 @@
{
"network" :
{
"property" :
{
"dhcp" : true
}
}
}

176
dist/idac/segatools.ini vendored Normal file
View File

@ -0,0 +1,176 @@
[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=
[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
[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.100.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
[gpio]
; ALLS DIP switches.
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
[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 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
; 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]
; 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

51
dist/idac/start.bat vendored Normal file
View File

@ -0,0 +1,51 @@
@echo off
pushd %~dp0
REM set the APP_DIR to the Y drive
set APP_DIR=Y:\SDGT
REM create the APP_DIR if it doesn't exist and redirect it to the TEMP folder
if not exist "%APP_DIR%" (
subst Y: %TEMP%
REM timeout /t 1
if not exist "%APP_DIR%" (
mkdir "%APP_DIR%"
)
)
echo Mounted the Y:\ drive to the %TEMP%\SDGT folder
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 /min inject -d -k idachook.dll amdaemon.exe -c %AMDAEMON_CFG%
inject -d -k idachook.dll ..\WindowsNoEditor\GameProject.exe -culture=en launch=Cabinet ABSLOG="..\..\..\..\..\Userdata\GameProject.log" -Master -UserDir="..\..\..\Userdata" -NotInstalled -UNATTENDED
taskkill /f /im amdaemon.exe > nul 2>&1
REM unmount the APP_DIR
subst Y: /d > nul 2>&1
echo.
echo Game processes have terminated
pause

103
dist/idz/segatools.ini vendored
View File

@ -8,6 +8,14 @@ option=
; NOTE: This has nothing to do with Windows %APPDATA%.
appdata=
[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
[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.
@ -19,7 +27,7 @@ default=127.0.0.1
; 4: Export (some UI elements in English)
;
; NOTE: Changing this setting causes a factory reset.
region=1
region=4
[netenv]
; Simulate an ideal LAN environment. This may interfere with head-to-head play.
@ -27,11 +35,28 @@ region=1
; 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
[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.158.0
[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
[gpio]
; Emulated Nu DIP switch for Distribution Server setting.
@ -50,16 +75,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 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
; 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 +111,24 @@ singleStickSteering=0
; in the Control Panel.
restrict=97
[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=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,30 +139,36 @@ 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

8
dist/idz/start.bat vendored
View File

@ -2,8 +2,12 @@
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
inject -k idzhook.dll InitialD0_DX11_Nu.exe
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 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
taskkill /im ServerBoxD8_Nu_x64.exe > nul 2>&1
echo.
echo Game processes have terminated

84
dist/mai2/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, Bxxx 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]
; Enable aime reader emulation.
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 its 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
[gpio]
; ALLS DIP switches.
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
; -----------------------------------------------------------------------------
; 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 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
; 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]
;1p_btn1=0x53
;1p_btn2=0x53
;1p_btn3=0x53
; ... etc ...
;2p_btn1=0x53
;2p_btn2=0x53
;2p_btn3=0x53
; ... etc ...

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

@ -0,0 +1,11 @@
@echo off
pushd %~dp0
start /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
taskkill /f /im amdaemon.exe > nul 2>&1
echo.
echo Game processes have terminated
pause

View File

@ -12,14 +12,6 @@ option=option
; 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
@ -35,12 +27,42 @@ subnet=192.168.174.0
[gfx]
enable=1
[gpio]
; ALLS DIP switches.
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
; -----------------------------------------------------------------------------
; 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=0x2D
service=0x2E
coin=0x24
; 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
; Volume up virtual-key code. Default is the "UP" key.
volup=0x26
; Volume down virtual-key code. Default is the "DOWN" key.
voldown=0x28
; Hooks related to the touch boards

View File

@ -1,25 +1,23 @@
[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=
[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
[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
@ -32,29 +30,61 @@ enable=1
; that subnet must start with 192.168.
subnet=192.168.162.0
[gpio]
; ALLS DIP switches.
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
[gfx]
enable=1
[led15093]
; 837-15093-06 LED board emulation setting.
enable=1
; -----------------------------------------------------------------------------
; 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 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
[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
; Set "1" to enable mouse lever emulation, "0" to use XInput
mouse=1
; Keyboard input bindings
left1=0x41 ; A
left2=0x53 ; S
left3=0x44 ; D
leftSide=0x01 ; Mouse Left
rightSide=0x02 ; Mouse Right
right1=0x4A ; J
right1=0x4B ; K
right3=0x4C ; L
leftMenu=0x55 ; U
rightMenu=0x4F ; O

12
dist/mu3/start.bat vendored
View File

@ -1,11 +1,11 @@
@echo off
pushd %~dp0
start /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
start inject -d -k mu3hook.dll amdaemon.exe -f -c config_client.json config_common.json config_server.json
inject -d -k mu3hook.dll mu3.exe
taskkill /f /im amdaemon.exe > nul 2>&1
echo Game processes have terminated
echo.
echo Game processes have terminated
pause

135
dist/swdc/segatools.ini vendored Normal file
View File

@ -0,0 +1,135 @@
[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=
[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
[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.100.0
[aimeio]
; To use a custom card reader IO DLL enter its path here.
; Leave empty if you want to use Segatools built-in keyboard input.
path=
[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=
[gpio]
; ALLS DIP switches.
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
; -----------------------------------------------------------------------------
; 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 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
; 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]
; 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

17
dist/swdc/start.bat vendored Normal file
View File

@ -0,0 +1,17 @@
@echo off
pushd %~dp0
rem Matching Server
start /min ..\..\..\Tools\tdrserver.exe
REM start /min inject -d -k swdchook.dll amdaemon.exe -c config.json config_LanClient.json config_MiniCabinet.json config_hook.json
start /min inject -d -k swdchook.dll amdaemon.exe -c config.json config_LanServer.json config_MiniCabinet.json
REM Valid -launch parameters are "PC", "Cabinet" and "MiniCabinet
inject -d -k swdchook.dll ..\Todoroki\Binaries\Win64\Todoroki-Win64-Shipping.exe -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

View File

@ -62,7 +62,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;

124
doc/15093-06.txt Normal file
View File

@ -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

View File

@ -31,12 +31,36 @@ Default: `1`
Enable Aime card reader assembly emulation. Disable to use a real SEGA Aime
reader (COM port number varies by game).
### `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 +70,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.
@ -270,6 +294,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.

View File

@ -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 ` `

View File

@ -18,7 +18,7 @@ if ERRORLEVEL 1 (
goto failure
)
docker image rm -f %IMAGE_NAME%
:: docker image rm -f %IMAGE_NAME%
goto success

125
fgohook/config.c Normal file
View File

@ -0,0 +1,125 @@
#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);
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 = GetPrivateProfileIntW(L"led15093", L"portNo", 0, filename);
cfg->high_baudrate = GetPrivateProfileIntW(L"led15093", L"highBaudrate", 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);
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);
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 "board/led15093.h"
#include "hooklib/dvd.h"
#include "hooklib/touch.h"
#include "hooklib/printer.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 touch_screen_config touch;
struct printer_config printer;
struct deck_config deck;
struct ftdi_config ftdi;
struct led15093_config led15093;
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);

513
fgohook/deck.c Normal file
View File

@ -0,0 +1,513 @@
/*
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, 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;
}

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, unsigned int port_no);

142
fgohook/dllmain.c Normal file
View File

@ -0,0 +1,142 @@
#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, 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, 17);
if (FAILED(hr)) {
goto fail;
}
hr = led15093_hook_init(&fgo_hook_cfg.led15093, 17, 1, 1, 2);
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(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

227
fgohook/ftdi.c Normal file
View File

@ -0,0 +1,227 @@
/*
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 <windows.h>
#include <setupapi.h>
#include <stdint.h>
#include <assert.h>
#include <stdio.h>
#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,
}
};
#define 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);
}

21
fgohook/ftdi.h Normal file
View File

@ -0,0 +1,21 @@
#pragma once
#include <windows.h>
#include <initguid.h>
#include <stdbool.h>
#include <stddef.h>
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);

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);

34
fgohook/meson.build Normal file
View File

@ -0,0 +1,34 @@
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',
],
)

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);

Some files were not shown because too many files have changed in this diff Show More