Compare commits

...

64 Commits

Author SHA1 Message Date
Dniel97 63320f8456 Merge pull request 'fix chunihook' (#2) from CrazyRedMachine/segatools:chuniio into chuniio
Reviewed-on: Dniel97/segatools#2
2023-12-27 15:14:07 +00:00
CrazyRedMachine 926493290b fix chunihook 2023-12-26 06:47:19 -05:00
Dniel97 f4a3a5f78d
optimized meson.build to decrease filesize 2023-12-25 15:55:43 +01:00
Dniel97 f5f275c8e9
chuniio: fixed individual IR notes 2023-12-25 15:12:25 +01:00
Dniel97 b38dea23ac
chu2to3: fixed docker compilation, bugfixes 2023-12-25 15:03:44 +01:00
Dniel97 16bbd87c73 Merge pull request 'Include chu2to3 engine' (#1) from CrazyRedMachine/segatools:chuniio into chuniio
Reviewed-on: Dniel97/segatools#1
2023-12-25 14:02:33 +00:00
CrazyRedMachine ee414d122b Include chu2to3 engine 2023-12-24 08:33:30 -05:00
Dniel97 d4372fa5c2
chuniio: use HRESULT instead of int for chuni_io_led_init() 2023-12-21 00:45:41 +01:00
Dniel97 ac9b889d71
chusan/chuni: tower LEDs added 2023-12-19 14:40:02 +01:00
Dniel97 3bf223c04e
chusan: fixed SP/CVT switching 2023-12-19 13:45:06 +01:00
Dniel97 8ebdf67d6e
chuni/chusan: added LED output to DLLs (will break most DLLs)
Credits: somewhatlurker, skogaby
https://dev.s-ul.net/skogaby/segatools/-/blob/ongeki-15093/
2023-12-19 12:48:33 +01:00
Dniel97 ed042176d7
chusan: ledport changed to COM5, fixes VFD 2023-12-17 20:17:26 +01:00
Dniel97 ad154a83e5
chusan: fixed dipsw settings, added aimeio DLL loading 2023-12-17 19:43:00 +01:00
Dniel97 1cbc33d97d
chuasn: fixed coin insert for custom chuniio 2023-12-17 09:26:30 +01:00
Dniel97 72db08ac93
idz, idac, swdc: added FFB centering spring feature 2023-12-11 19:18:25 +01:00
Dniel97 4ffcf25555
added freeplay setting to ALLS games 2023-12-03 22:22:17 +01:00
Dniel97 3dd6054a1e
chusan, fgo, mu3: fixed LED 15093 board 2023-12-03 21:34:38 +01:00
Dniel97 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
Dniel97 8c12853051
cm: disabled coin selector AS6DB 2023-11-30 00:02:00 +01:00
Dniel97 a3fd2fb926
Merge branch 'master' into develop 2023-11-29 23:07:24 +01:00
Dniel97 8b1d0cfefa
LED board improvements and cleanup 2023-11-25 23:00:05 +01:00
Dniel97 d86baab852
fgo: working FTDI hook by @OLEG
from: 88a0026f0b
2023-11-22 21:43:42 +01:00
Dniel97 b9fd59fd70
idz: added fullscreen patch by @Felix (again) 2023-11-20 18:04:45 +01:00
Dniel97 146fac9287
dns: block more All.NET requests 2023-11-12 21:16:02 +01:00
Dniel97 3cf5cbb793
added LED 15093-06 credits and docs 2023-11-12 19:14:42 +01:00
Dniel97 a2db39c58c
fgo: fgohook finally added
Credits:
- Coburn
- domeori
- Mitsuhide
- OLEG
- rakisaionji
2023-11-11 23:05:11 +01:00
Dniel97 946ea7ef3b
chusan: fixed LED configs 2023-11-11 22:41:04 +01:00
Dniel97 25562e37f9
nusec: fixes keychip not found error 0949 2023-11-02 23:32:13 +01:00
Dniel97 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
Dniel97 6c45d0995b
fix cursor being annoying by @OLEG
95273ccecb
2023-10-05 00:37:25 +02:00
Dniel97 a4bd570cfc
chusan, mai2, mu3, swdc: removed emoney config 2023-10-05 00:35:55 +02:00
Dniel97 37793fc051
Merge branch 'master' into develop 2023-10-05 00:30:24 +02:00
Dniel97 f5a7e5b821
epay: added config because git is dumb 2023-09-15 19:58:46 +02:00
Dniel97 5ef0cf6181
Merge branch 'master' into develop 2023-09-15 19:56:48 +02:00
Dniel97 31203daa09
idz, idac, swdc: fixed `restrict` config setting 2023-09-05 00:24:43 +02:00
Dniel97 80718104f6
swdc: updated config, fixed start.bat 2023-09-05 00:23:17 +02:00
Dniel97 eb2eef927a
idac: added left and right button mapping 2023-09-04 23:31:52 +02:00
Dniel97 9bef9e49f8
swdc: added aime firm to config_hook.json 2023-09-04 02:05:37 +02:00
Dniel97 d5a551482b
idac: mapped left and right thumbstick to dpad left and right 2023-09-04 02:04:42 +02:00
Dniel97 49f729c501
idz, idac, swdc: switched to `[xinput]` config instead of [io] 2023-09-04 02:03:31 +02:00
Dniel97 91f69beae6
idac: fixed config error 2023-08-30 02:24:02 +02:00
Dniel97 51b11469d0
added `aimeGen` as default instead of `feliciaGen` 2023-08-29 22:57:04 +02:00
Dniel97 f0dc51d01e
idac: added config_hook.json 2023-08-29 20:03:33 +02:00
Dniel97 ca4a8bd84d
idz, idac, swdc: Added deadzone config 2023-08-29 02:22:05 +02:00
Dniel97 f6e961d4f4
Removed `-f` from start.bat and fixed idz 2023-08-29 00:49:58 +02:00
Dniel97 a69a9b5917
swdc: disabled built in XInput 2023-08-20 16:21:55 +02:00
Dniel97 97234f26d7
cm, chusan, mai2, mu3, swdc: added emoney config 2023-08-15 22:02:38 +02:00
Dniel97 2277bf7526
all: fix accounting issue (my bad) 2023-08-15 20:23:28 +02:00
Dniel97 80d5fc4bb2
mu3: Added basic keyboard and mouse lever emulation support 2023-08-15 17:40:32 +02:00
Dniel97 608c9ac1a6
chusan, cm, mai2, mu3: Added dipSw support and highBaudrate AiMe 2023-08-15 17:28:19 +02:00
Dniel97 3dc2ec6e69
idac, idz, swdc: Fixed DInput brake/accel, added cubic steering 2023-08-15 17:20:27 +02:00
Dniel97 28ef2d719a
dinput: fixed POV bug 2023-07-19 12:31:57 +02:00
Dniel97 600f795104
disable dipsw by default 2023-07-14 01:07:22 +02:00
Dniel97 e5d17b82b2
updated Package.mk for mai2, cm 2023-07-14 01:03:36 +02:00
Dniel97 0ee081ca1b
mai2: segatools added 2023-07-14 01:00:28 +02:00
Dniel97 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
Dniel97 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
Dniel97 90a6f1be7c
mu3: coin input added 2023-07-14 00:54:30 +02:00
Dniel97 ec072667b3
swdc: first segatools added 2023-07-14 00:52:50 +02:00
Dniel97 89195ed60b
Merge branch 'idac_test' into develop 2023-07-14 00:43:38 +02:00
Dniel97 c27ef9674d
idac: added dipswitch support (beta) 2023-07-14 00:41:23 +02:00
Dniel97 da97d23b51
idac: first segatools support 2023-06-29 11:27:52 +02:00
Dniel97 ee6675dd73
Merge branch 'master' into idac_test 2023-06-27 21:11:45 +02:00
Dniel97 555784258a
idac: test 2023-04-23 16:13:51 +02:00
221 changed files with 17228 additions and 206 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

@ -18,6 +18,7 @@ struct aime_dll {
struct aime_dll_config {
wchar_t path[MAX_PATH];
bool path64;
};
extern struct aime_dll aime_dll;

View File

@ -9,18 +9,59 @@
#include "board/config.h"
#include "board/sg-reader.h"
#include "util/dprintf.h"
// Check windows
#if _WIN32 || _WIN64
#if _WIN64
#define ENV64BIT
#else
#define ENV32BIT
#endif
#endif
// Check GCC
#if __GNUC__
#if __x86_64__ || __ppc64__
#define ENV64BIT
#else
#define ENV32BIT
#endif
#endif
static void aime_dll_config_load(struct aime_dll_config *cfg, const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
GetPrivateProfileStringW(
// Workaround for x64/x86 external IO dlls
// path32 for 32bit, path64 for 64bit
// for else.. is that possible? idk
if (cfg->path64) {
#if defined(ENV32BIT)
// Always empty, due to amdaemon being 64 bit in 32 bit mode
memset(cfg->path, 0, sizeof(cfg->path));
#elif defined(ENV64BIT)
GetPrivateProfileStringW(
L"aimeio",
L"path",
L"",
cfg->path,
_countof(cfg->path),
filename);
#else
#error "Unknown environment"
#endif
} else {
GetPrivateProfileStringW(
L"aimeio",
L"path",
L"",
cfg->path,
_countof(cfg->path),
filename);
}
}
void aime_config_load(struct aime_config *cfg, const wchar_t *filename)
@ -30,6 +71,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);

1149
board/led15093.c Normal file

File diff suppressed because it is too large Load Diff

24
board/led15093.h Normal file
View File

@ -0,0 +1,24 @@
#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;
};
typedef HRESULT (*io_led_init_t)(void);
typedef void (*io_led_set_leds_t)(uint8_t board, uint8_t *rgb);
HRESULT led15093_hook_init(const struct led15093_config *cfg, io_led_init_t _led_init,
io_led_set_leds_t _set_leds, unsigned int 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

@ -30,9 +30,28 @@ const struct dll_bind_sym chuni_dll_syms[] = {
}, {
.sym = "chuni_io_slider_set_leds",
.off = offsetof(struct chuni_dll, slider_set_leds),
}, {
.sym = "chuni_io_led_init",
.off = offsetof(struct chuni_dll, led_init),
}, {
.sym = "chuni_io_led_set_colors",
.off = offsetof(struct chuni_dll, led_set_leds),
}
};
/* Helper function to determine upon dll_bind failure whether the required functions were found
NOTE: relies on symbols order declared above */
static HRESULT has_enough_symbols(uint16_t version, uint8_t count)
{
if ( version <= 0x0101 && count == 7 )
return S_OK;
if ( version >= 0x0102 && count == 9 )
return S_OK;
return E_FAIL;
}
struct chuni_dll chuni_dll;
// Copypasta DLL binding and diagnostic message boilerplate.
@ -92,16 +111,24 @@ HRESULT chuni_dll_init(const struct chuni_dll_config *cfg, HINSTANCE self)
}
sym = chuni_dll_syms;
const struct dll_bind_sym *init_sym = &sym[0];
hr = dll_bind(&chuni_dll, src, &sym, _countof(chuni_dll_syms));
if (FAILED(hr)) {
if (src != self) {
dprintf("Chunithm IO: Custom IO DLL does not provide function "
"\"%s\". Please contact your IO DLL's developer for "
"further assistance.\n",
sym->sym);
// Might still be ok depending on external dll API version
int bind_count = sym - init_sym;
if ( has_enough_symbols(chuni_dll.api_version, bind_count) == S_OK )
{
hr = S_OK;
} else {
dprintf("Chunithm IO: Custom IO DLL does not provide function "
"\"%s\". Please contact your IO DLL's developer for "
"further assistance.\n",
sym->sym);
goto end;
goto end;
}
} else {
dprintf("Internal error: could not reflect \"%s\"\n", sym->sym);
}

View File

@ -13,6 +13,8 @@ struct chuni_dll {
void (*slider_start)(chuni_io_slider_callback_t callback);
void (*slider_stop)(void);
void (*slider_set_leds)(const uint8_t *rgb);
HRESULT (*led_init)(void);
void (*led_set_leds)(uint8_t board, uint8_t *rgb);
};
struct chuni_dll_config {

View File

@ -20,3 +20,5 @@ EXPORTS
chuni_io_slider_set_leds
chuni_io_slider_start
chuni_io_slider_stop
chuni_io_led_init
chuni_io_led_set_colors

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

@ -4,6 +4,7 @@
#include "amex/amex.h"
#include "board/led15093.h"
#include "board/sg-reader.h"
#include "chunihook/config.h"
@ -96,7 +97,19 @@ static DWORD CALLBACK chuni_pre_startup(void)
goto fail;
}
hr = sg_reader_hook_init(&chuni_hook_cfg.aime, 12, chuni_hook_mod);
if ( chuni_dll.led_init == NULL || chuni_dll.led_set_leds == NULL )
{
dprintf("IO DLL doesn't support led_init/led_set_leds, cannot start LED15093 hook\n");
} else {
hr = led15093_hook_init(&chuni_hook_cfg.led15093,
chuni_dll.led_init, chuni_dll.led_set_leds, 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;
}

358
chuniio/chu2to3.c Normal file
View File

@ -0,0 +1,358 @@
#include <windows.h>
#include <process.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include "chuniio/chu2to3.h"
#include "util/dprintf.h"
// Check windows
#if _WIN32 || _WIN64
#if _WIN64
#define ENV64BIT
#else
#define ENV32BIT
#endif
#endif
// Check GCC
#if __GNUC__
#if __x86_64__ || __ppc64__
#define ENV64BIT
#else
#define ENV32BIT
#endif
#endif
/* chuniio.dll dynamic loading */
HMODULE hinstLib;
typedef uint16_t (*chuni_io_get_api_version_t)(void);
typedef HRESULT (*chuni_io_jvs_init_t)(void);
typedef void (*chuni_io_jvs_poll_t)(uint8_t*, uint8_t*);
typedef void (*chuni_io_jvs_read_coin_counter_t)(uint16_t *);
typedef HRESULT (*chuni_io_slider_init_t)(void);
typedef void (*chuni_io_slider_set_leds_t)(const uint8_t *);
typedef void (*chuni_io_slider_start_t)(chuni_io_slider_callback_t);
typedef void (*chuni_io_slider_stop_t)(void);
typedef HRESULT (*chuni_io_led_init_t)(void);
typedef void (*chuni_io_led_set_colors_t)(uint8_t, uint8_t *);
chuni_io_get_api_version_t _chuni_io_get_api_version;
chuni_io_jvs_init_t _chuni_io_jvs_init;
chuni_io_jvs_poll_t _chuni_io_jvs_poll;
chuni_io_jvs_read_coin_counter_t _chuni_io_jvs_read_coin_counter;
chuni_io_slider_init_t _chuni_io_slider_init;
chuni_io_slider_set_leds_t _chuni_io_slider_set_leds;
chuni_io_slider_start_t _chuni_io_slider_start;
chuni_io_slider_stop_t _chuni_io_slider_stop;
chuni_io_led_init_t _chuni_io_led_init;
chuni_io_led_set_colors_t _chuni_io_led_set_colors;
/* SHMEM Handling */
#define BUF_SIZE 1024
#define SHMEM_WRITE(buf, size) CopyMemory((PVOID)g_pBuf, buf, size)
#define SHMEM_READ(buf, size) CopyMemory(buf,(PVOID)g_pBuf, size)
TCHAR g_shmem_name[]=TEXT("Local\\Chu2to3Shmem");
HANDLE g_hMapFile;
LPVOID g_pBuf;
#pragma pack(1)
typedef struct shared_data_s {
uint16_t coin_counter;
uint8_t opbtn;
uint8_t beams;
uint16_t version;
} shared_data_t;
shared_data_t g_shared_data;
bool shmem_create()
{
g_hMapFile = CreateFileMapping(
INVALID_HANDLE_VALUE, // use paging file
NULL, // default security
PAGE_READWRITE, // read/write access
0, // maximum object size (high-order DWORD)
BUF_SIZE, // maximum object size (low-order DWORD)
g_shmem_name); // name of mapping object
if (g_hMapFile == NULL)
{
dprintf("shmem_create : Could not create file mapping object (%ld).\n",
GetLastError());
return 0;
}
g_pBuf = MapViewOfFile(g_hMapFile, // handle to map object
FILE_MAP_ALL_ACCESS, // read/write permission
0,
0,
BUF_SIZE);
if (g_pBuf == NULL)
{
dprintf("shmem_create : Could not map view of file (%ld).\n",
GetLastError());
CloseHandle(g_hMapFile);
return 0;
}
return 1;
}
bool shmem_load()
{
g_hMapFile = OpenFileMapping(
FILE_MAP_ALL_ACCESS, // read/write access
FALSE, // do not inherit the name
g_shmem_name); // name of mapping object
if (g_hMapFile == NULL)
{
dprintf("shmem_load : Could not open file mapping object (%ld).\n", GetLastError());
return 0;
}
g_pBuf = MapViewOfFile(g_hMapFile, // handle to map object
FILE_MAP_ALL_ACCESS, // read/write permission
0,
0,
BUF_SIZE);
if (g_pBuf == NULL)
{
dprintf("shmem_load : Could not map view of file (%ld).\n", GetLastError());
CloseHandle(g_hMapFile);
return 0;
}
dprintf("shmem_load : shmem loaded succesfully.\n");
return 1;
}
void shmem_free()
{
UnmapViewOfFile(g_pBuf);
CloseHandle(g_hMapFile);
}
/* jvs polling thread (to forward info to x64 dll) */
static HANDLE jvs_poll_thread;
static bool jvs_poll_stop_flag;
static unsigned int __stdcall jvs_poll_thread_proc(void *ctx)
{
while (1) {
_chuni_io_jvs_read_coin_counter(&g_shared_data.coin_counter);
g_shared_data.opbtn = 0;
g_shared_data.beams = 0;
_chuni_io_jvs_poll(&g_shared_data.opbtn, &g_shared_data.beams);
SHMEM_WRITE(&g_shared_data, sizeof(shared_data_t));
Sleep(1);
}
return 0;
}
uint16_t chu2to3_load_dll(const wchar_t *dllname)
{
#if defined(ENV64BIT)
/* x64 must just open the shmem and do nothing else */
int errcount = 0;
while (!shmem_load())
{
if (errcount >= 10)
return -1;
Sleep(5000);
errcount++;
}
Sleep(1000);
return S_OK;
#endif
/* this is the first function called so let's setup the chuniio forwarding */
hinstLib = LoadLibraryW(dllname);
if (hinstLib == NULL) {
dprintf("ERROR: unable to load %S (error %ld)\n",dllname, GetLastError());
return -1;
}
_chuni_io_get_api_version = (chuni_io_get_api_version_t)GetProcAddress(hinstLib, "chuni_io_get_api_version");
_chuni_io_jvs_init = (chuni_io_jvs_init_t)GetProcAddress(hinstLib, "chuni_io_jvs_init");
_chuni_io_jvs_poll = (chuni_io_jvs_poll_t)GetProcAddress(hinstLib, "chuni_io_jvs_poll");
_chuni_io_jvs_read_coin_counter = (chuni_io_jvs_read_coin_counter_t)GetProcAddress(hinstLib, "chuni_io_jvs_read_coin_counter");
_chuni_io_slider_init = (chuni_io_slider_init_t)GetProcAddress(hinstLib, "chuni_io_slider_init");
_chuni_io_slider_set_leds = (chuni_io_slider_set_leds_t)GetProcAddress(hinstLib, "chuni_io_slider_set_leds");
_chuni_io_slider_start = (chuni_io_slider_start_t)GetProcAddress(hinstLib, "chuni_io_slider_start");
_chuni_io_slider_stop = (chuni_io_slider_stop_t)GetProcAddress(hinstLib, "chuni_io_slider_stop");
_chuni_io_led_init = (chuni_io_led_init_t)GetProcAddress(hinstLib, "chuni_io_led_init");
_chuni_io_led_set_colors = (chuni_io_led_set_colors_t)GetProcAddress(hinstLib, "chuni_io_led_set_colors");
/* x86 has to create the shmem */
if (!shmem_create())
{
return -1;
}
return 0;
}
/* chuniio exports */
uint16_t chu2to3_io_get_api_version(void)
{
#if defined(ENV64BIT)
/* This might be called too soon so let's make sure x86 has time to write to the shmem */
SHMEM_READ(&g_shared_data, sizeof(shared_data_t));
int errcount = 0;
while (g_shared_data.version == 0)
{
if (errcount >= 3)
{
dprintf("CHU2TO3 X64: Couldn't retrieve api version from shmem, assuming 0x0100\n");
return 0x0100;
}
Sleep(5000);
errcount++;
SHMEM_READ(&g_shared_data, sizeof(shared_data_t));
}
dprintf("CHU2TO3 X64: api version is %04X\n", g_shared_data.version);
return g_shared_data.version;
#endif
if ( _chuni_io_get_api_version == NULL )
{
g_shared_data.version = 0x0100;
}
else
{
g_shared_data.version = _chuni_io_get_api_version();
}
dprintf("CHU2TO3: api version is %04X\n", g_shared_data.version);
SHMEM_WRITE(&g_shared_data, sizeof(shared_data_t));
return g_shared_data.version;
}
HRESULT chu2to3_io_jvs_init(void)
{
#if defined(ENV64BIT)
/* x86 only */
return S_OK;
#endif
_chuni_io_jvs_init();
/* start jvs poll thread now that jvs_init is done */
if (jvs_poll_thread != NULL) {
return S_OK;
}
jvs_poll_thread = (HANDLE) _beginthreadex(NULL,
0,
jvs_poll_thread_proc,
NULL,
0,
NULL);
return S_OK;
}
void chu2to3_io_jvs_read_coin_counter(uint16_t *out)
{
#if defined(ENV32BIT)
/* x86 can perform the call and update shmem (although this call never happens) */
_chuni_io_jvs_read_coin_counter(&g_shared_data.coin_counter);
SHMEM_WRITE(&g_shared_data, sizeof(shared_data_t));
return;
#endif
/* x64 must read value from shmem and update arg */
SHMEM_READ(&g_shared_data, sizeof(shared_data_t));
if (out == NULL) {
return;
}
*out = g_shared_data.coin_counter;
}
void chu2to3_io_jvs_poll(uint8_t *opbtn, uint8_t *beams)
{
#if defined(ENV32BIT)
/* x86 can perform the call and update shmem (although this call never happens) */
_chuni_io_jvs_poll(&g_shared_data.opbtn, &g_shared_data.beams);
SHMEM_WRITE(&g_shared_data, sizeof(shared_data_t));
return;
#endif
/* x64 must read value from shmem and update args */
SHMEM_READ(&g_shared_data, sizeof(shared_data_t));
*opbtn = g_shared_data.opbtn;
*beams = g_shared_data.beams;
}
HRESULT chu2to3_io_slider_init(void)
{
#if defined(ENV64BIT)
/* x86 only */
return S_OK;
#endif
return _chuni_io_slider_init();
}
void chu2to3_io_slider_start(chuni_io_slider_callback_t callback)
{
#if defined(ENV64BIT)
/* x86 only */
return;
#endif
_chuni_io_slider_start(callback);
}
void chu2to3_io_slider_stop(void)
{
#if defined(ENV64BIT)
/* x86 only */
return;
#endif
_chuni_io_slider_stop();
}
void chu2to3_io_slider_set_leds(const uint8_t *rgb)
{
#if defined(ENV64BIT)
/* x86 only */
return;
#endif
_chuni_io_slider_set_leds(rgb);
}
HRESULT chu2to3_io_led_init(void)
{
#if defined(ENV64BIT)
/* x86 only */
return S_OK;
#endif
if (_chuni_io_led_init != NULL)
return _chuni_io_led_init();
return S_OK;
}
void chu2to3_io_led_set_colors(uint8_t board, uint8_t *rgb)
{
#if defined(ENV64BIT)
/* x86 only */
return;
#endif
if (_chuni_io_led_set_colors != NULL)
{
_chuni_io_led_set_colors(board, rgb);
}
}

26
chuniio/chu2to3.h Normal file
View File

@ -0,0 +1,26 @@
#pragma once
/*
CHU2TO3 CUSTOM IO API
This dll just mirrors chuniio dll binds but with a dynamic library loading and
a SHMEM system to let a single 32bit dll talk with x86 and x64 processes at once
*/
#include <windows.h>
#include <stdbool.h>
#include <stdint.h>
uint16_t chu2to3_io_get_api_version(void);
HRESULT chu2to3_io_jvs_init(void);
void chu2to3_io_jvs_poll(uint8_t *opbtn, uint8_t *beams);
void chu2to3_io_jvs_read_coin_counter(uint16_t *total);
HRESULT chu2to3_io_slider_init(void);
typedef void (*chuni_io_slider_callback_t)(const uint8_t *state);
void chu2to3_io_slider_start(chuni_io_slider_callback_t callback);
void chu2to3_io_slider_stop(void);
void chu2to3_io_slider_set_leds(const uint8_t *rgb);
HRESULT chu2to3_io_led_init(void);
void chu2to3_io_led_set_colors(uint8_t board, uint8_t *rgb);
uint16_t chu2to3_load_dll(const wchar_t *dllname);

View File

@ -3,9 +3,13 @@
#include <process.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include "chuniio/chuniio.h"
#include "chuniio/config.h"
#include "chuniio/ledoutput.h"
#include "util/dprintf.h"
static unsigned int __stdcall chuni_io_slider_thread_proc(void *ctx);
@ -18,13 +22,23 @@ static struct chuni_io_config chuni_io_cfg;
uint16_t chuni_io_get_api_version(void)
{
return 0x0101;
return 0x0102;
}
HRESULT chuni_io_jvs_init(void)
{
chuni_io_config_load(&chuni_io_cfg, L".\\segatools.ini");
led_init_mutex = CreateMutex(
NULL, // default security attributes
FALSE, // initially not owned
NULL); // unnamed mutex
if (led_init_mutex == NULL)
{
return E_FAIL;
}
return S_OK;
}
@ -34,7 +48,7 @@ void chuni_io_jvs_read_coin_counter(uint16_t *out)
return;
}
if (GetAsyncKeyState(chuni_io_cfg.vk_coin)) {
if (GetAsyncKeyState(chuni_io_cfg.vk_coin) & 0x8000) {
if (!chuni_io_coin) {
chuni_io_coin = true;
chuni_io_coins++;
@ -50,38 +64,52 @@ 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 (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 {
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);
// Use actual AIR
for (i = 0; i < 6; i++) {
if(GetAsyncKeyState(chuni_io_cfg.vk_ir[i]) & 0x8000) {
*beams |= (1 << i);
} else {
*beams &= ~(1 << i);
}
}
}
}
HRESULT chuni_io_slider_init(void)
{
return S_OK;
return led_output_init(&chuni_io_cfg); // because of slider LEDs
}
void chuni_io_slider_start(chuni_io_slider_callback_t callback)
{
BOOL status;
if (chuni_io_slider_thread != NULL) {
return;
}
@ -111,6 +139,7 @@ void chuni_io_slider_stop(void)
void chuni_io_slider_set_leds(const uint8_t *rgb)
{
led_output_update(2, rgb);
}
static unsigned int __stdcall chuni_io_slider_thread_proc(void *ctx)
@ -136,3 +165,13 @@ static unsigned int __stdcall chuni_io_slider_thread_proc(void *ctx)
return 0;
}
HRESULT chuni_io_led_init(void)
{
return led_output_init(&chuni_io_cfg);
}
void chuni_io_led_set_colors(uint8_t board, uint8_t *rgb)
{
led_output_update(board, rgb);
}

View File

@ -8,6 +8,7 @@
- 0x0100: Initial API version (assumed if chuni_io_get_api_version is not
exported)
- 0x0101: Fix IR beam mappings
- 0x0102: Add air tower led and billboard support
*/
#include <windows.h>
@ -15,6 +16,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
@ -139,3 +146,30 @@ void chuni_io_slider_stop(void);
Minimum API version: 0x0100 */
void chuni_io_slider_set_leds(const uint8_t *rgb);
/* Initialize LED emulation. This function will be called before any
other chuni_io_led_*() function calls.
All subsequent calls may originate from arbitrary threads and some may
overlap with each other. Ensuring synchronization inside your IO DLL is
your responsibility.
Minimum API version: 0x0102 */
HRESULT chuni_io_led_init(void);
/* Update the RGB LEDs. rgb is a pointer to an array of up to 63 * 3 = 189 bytes.
Chunithm uses two chains/boards with WS2811 protocol (each logical led corresponds to 3 physical leds).
board 0 is on the left side and board 1 on the right side of the cab
left side has 5*10 rgb values for the billboard, followed by 3 rgb values for the air tower
right side has 6*10 rgb values for the billboard, followed by 3 rgb values for the air tower
Each rgb value is comprised of 3 bytes in R,G,B order
NOTE: billboard strips have alternating direction (bottom to top, top to bottom, ...)
Minimum API version: 0x0102 */
void chuni_io_led_set_colors(uint8_t board, uint8_t *rgb);

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,25 @@ void chuni_io_config_load(
chuni_io_default_cells[i],
filename);
}
cfg->led_output_pipe = GetPrivateProfileIntW(L"led", L"cabLedOutputPipe", 1, filename);
cfg->led_output_serial = GetPrivateProfileIntW(L"led", L"cabLedOutputSerial", 0, filename);
cfg->slider_led_output_pipe = GetPrivateProfileIntW(L"led", L"controllerLedOutputPipe", 1, filename);
cfg->slider_led_output_serial = GetPrivateProfileIntW(L"led", L"controllerLedOutputSerial", 0, filename);
cfg->led_serial_baud = GetPrivateProfileIntW(L"led", L"serialBaud", 921600, filename);
GetPrivateProfileStringW(
L"led",
L"serialPort",
L"COM5",
port_input,
6,
filename);
// Sanitize the output path. If it's a serial COM port, it needs to be prefixed
// with `\\.\`.
wcsncpy(cfg->led_serial_port, L"\\\\.\\", 4);
wcsncat_s(cfg->led_serial_port, 11, port_input, 6);
}

View File

@ -7,8 +7,21 @@ 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];
// Which ways to output LED information are enabled
bool led_output_pipe;
bool led_output_serial;
bool slider_led_output_pipe;
bool slider_led_output_serial;
// The name of a COM port to output LED data on, in serial mode
wchar_t led_serial_port[12];
int32_t led_serial_baud;
};
void chuni_io_config_load(

22
chuniio/leddata.h Normal file
View File

@ -0,0 +1,22 @@
#pragma once
#include <windows.h>
#include <stdint.h>
#define LED_PACKET_FRAMING 0xE0
#define LED_PACKET_ESCAPE 0xD0
#define LED_NUM_MAX 66
#define LED_BOARDS_TOTAL 3
#define LED_OUTPUT_HEADER_SIZE 2
#define LED_OUTPUT_DATA_SIZE_MAX LED_NUM_MAX * 3 * 2 // max if every byte's escaped
#define LED_OUTPUT_TOTAL_SIZE_MAX LED_OUTPUT_HEADER_SIZE + LED_OUTPUT_DATA_SIZE_MAX
// This struct is used to send data related to the slider and billboard LEDs
struct _chuni_led_data_buf_t {
byte framing; // Sync byte
uint8_t board; // LED output the data is for (0-1: billboard, 2: slider)
byte data[LED_OUTPUT_DATA_SIZE_MAX]; // Buffer for LEDs
byte data_len; // How many bytes to output from the buffer
};
static byte chuni_led_board_data_lens[LED_BOARDS_TOTAL] = {53*3, 63*3, 31*3};

133
chuniio/ledoutput.c Normal file
View File

@ -0,0 +1,133 @@
#include <windows.h>
#include <process.h>
#include <stdbool.h>
#include <stdint.h>
#include "chuniio/config.h"
#include "chuniio/leddata.h"
#include "chuniio/ledoutput.h"
#include "chuniio/pipeimpl.h"
#include "chuniio/serialimpl.h"
static struct _chuni_led_data_buf_t led_unescaped_buf[LED_BOARDS_TOTAL];
static struct _chuni_led_data_buf_t led_escaped_buf[LED_BOARDS_TOTAL];
static bool led_output_is_init = false;
static struct chuni_io_config* config;
static bool any_outputs_enabled;
HANDLE led_init_mutex;
HRESULT led_output_init(struct chuni_io_config* const cfg)
{
DWORD dwWaitResult = WaitForSingleObject(led_init_mutex, INFINITE);
if (dwWaitResult == WAIT_FAILED)
{
return HRESULT_FROM_WIN32(GetLastError());
// return 1;
}
else if (dwWaitResult != WAIT_OBJECT_0)
{
return E_FAIL;
// return 1;
}
if (!led_output_is_init)
{
config = cfg;
// Setup the framing bytes for the packets
for (int i = 0; i < LED_BOARDS_TOTAL; i++) {
led_unescaped_buf[i].framing = LED_PACKET_FRAMING;
led_unescaped_buf[i].board = i;
led_unescaped_buf[i].data_len = chuni_led_board_data_lens[i];
led_escaped_buf[i].framing = LED_PACKET_FRAMING;
led_escaped_buf[i].board = i;
led_escaped_buf[i].data_len = chuni_led_board_data_lens[i];
}
any_outputs_enabled = config->led_output_pipe || config->slider_led_output_pipe
|| config->led_output_serial || config->slider_led_output_serial;
if (config->led_output_pipe || config->slider_led_output_pipe)
{
led_pipe_init(); // don't really care about errors here tbh
}
if (config->led_output_serial || config->slider_led_output_serial)
{
led_serial_init(config->led_serial_port, config->led_serial_baud);
}
}
led_output_is_init = true;
ReleaseMutex(led_init_mutex);
return S_OK;
// return 0;
}
struct _chuni_led_data_buf_t* escape_led_data(struct _chuni_led_data_buf_t* unescaped)
{
struct _chuni_led_data_buf_t* out_struct = &led_escaped_buf[unescaped->board];
byte* in_buf = unescaped->data;
byte* out_buf = out_struct->data;
int i = 0;
int o = 0;
while (i < unescaped->data_len)
{
byte b = in_buf[i++];
if (b == LED_PACKET_FRAMING || b == LED_PACKET_ESCAPE)
{
out_buf[o++] = LED_PACKET_ESCAPE;
b--;
}
out_buf[o++] = b;
}
out_struct->data_len = o;
return out_struct;
}
void led_output_update(uint8_t board, const byte* rgb)
{
if (board < 0 || board > 2 || !any_outputs_enabled)
{
return;
}
memcpy(led_unescaped_buf[board].data, rgb, led_unescaped_buf[board].data_len);
struct _chuni_led_data_buf_t* escaped_data = escape_led_data(&led_unescaped_buf[board]);
if (board < 2)
{
// billboard
if (config->led_output_pipe)
{
led_pipe_update(escaped_data);
}
if (config->led_output_serial)
{
led_serial_update(escaped_data);
}
}
else
{
// slider
if (config->slider_led_output_pipe)
{
led_pipe_update(escaped_data);
}
if (config->slider_led_output_serial)
{
led_serial_update(escaped_data);
}
}
}

19
chuniio/ledoutput.h Normal file
View File

@ -0,0 +1,19 @@
/*
LED output functions
Credits:
somewhatlurker, skogaby
*/
#pragma once
#include <windows.h>
#include <stdbool.h>
#include <stdint.h>
#include "chuniio/config.h"
extern HANDLE led_init_mutex;
HRESULT led_output_init(struct chuni_io_config* const cfg);
void led_output_update(uint8_t board, const byte* rgb);

View File

@ -5,9 +5,18 @@ chuniio_lib = static_library(
implicit_include_directories : false,
c_pch : '../precompiled.h',
sources : [
'chu2to3.c',
'chu2to3.h',
'chuniio.c',
'chuniio.h',
'config.c',
'config.h',
'leddata.h',
'ledoutput.c',
'ledoutput.h',
'pipeimpl.c',
'pipeimpl.h',
'serialimpl.c',
'serialimpl.h'
],
)

160
chuniio/pipeimpl.c Normal file
View File

@ -0,0 +1,160 @@
#include <windows.h>
#include <process.h>
#include <stdbool.h>
#include <stdint.h>
#include "chuniio/leddata.h"
#include "chuniio/pipeimpl.h"
static bool pipe_update[LED_BOARDS_TOTAL];
// incoming data is copied into these to ensure it isn't written during output
static struct _chuni_led_data_buf_t pipe_write_buf[LED_BOARDS_TOTAL];
static HANDLE pipe_write_mutex;
static HRESULT pipe_create(LPHANDLE hPipe, LPCWSTR lpszPipename, DWORD dwBufSize)
{
*hPipe = INVALID_HANDLE_VALUE;
*hPipe = CreateNamedPipeW(
lpszPipename, // pipe name
PIPE_ACCESS_OUTBOUND, // read/write access
PIPE_TYPE_BYTE | // byte type pipe
PIPE_WAIT, // blocking mode
PIPE_UNLIMITED_INSTANCES, // max. instances
dwBufSize, // output buffer size
0, // input buffer size
0, // client time-out
NULL); // default security attribute
if (*hPipe == INVALID_HANDLE_VALUE)
{
return E_FAIL;
}
return S_OK;
}
static HRESULT pipe_write(HANDLE hPipe, LPCVOID lpBuffer, DWORD dwSize)
{
DWORD cbWritten = 0;
bool fSuccess = WriteFile(
hPipe,
lpBuffer,
dwSize,
&cbWritten,
NULL);
if (!fSuccess || cbWritten != dwSize)
{
DWORD last_err = GetLastError();
return (last_err == ERROR_BROKEN_PIPE) ? E_ABORT : E_FAIL;
}
return S_OK;
}
static unsigned int __stdcall chuni_io_led_pipe_thread_proc(void *ctx)
{
HANDLE hPipe;
LPCWSTR lpszPipename = L"\\\\.\\pipe\\chuni_led";
while (true)
{
hPipe = INVALID_HANDLE_VALUE;
if (pipe_create(&hPipe, lpszPipename, LED_OUTPUT_TOTAL_SIZE_MAX) != S_OK)
{
continue;
}
// wait for a connection to the pipe
bool fConnected = ConnectNamedPipe(hPipe, NULL) ?
true : (GetLastError() == ERROR_PIPE_CONNECTED);
while (fConnected)
{
if (WaitForSingleObject(pipe_write_mutex, INFINITE) != WAIT_OBJECT_0)
{
continue;
}
for (int i = 0; i < LED_BOARDS_TOTAL; i++) {
if (pipe_update[i])
{
HRESULT result = pipe_write(
hPipe,
&pipe_write_buf[i],
LED_OUTPUT_HEADER_SIZE + pipe_write_buf[i].data_len);
if (result != S_OK)
{
//if (result == E_ABORT)
//{
fConnected = false;
//}
break;
}
pipe_update[i] = false;
}
}
ReleaseMutex(pipe_write_mutex);
}
FlushFileBuffers(hPipe);
DisconnectNamedPipe(hPipe);
CloseHandle(hPipe);
}
return 0;
}
HRESULT led_pipe_init()
{
pipe_write_mutex = CreateMutex(
NULL, // default security attributes
FALSE, // initially not owned
NULL); // unnamed mutex
if (pipe_write_mutex == NULL)
{
return E_FAIL;
}
// clear out update bools
for (int i = 0; i < LED_BOARDS_TOTAL; i++) {
pipe_update[i] = false;
}
_beginthreadex(
NULL,
0,
chuni_io_led_pipe_thread_proc,
0,
0,
NULL);
return S_OK;
}
void led_pipe_update(struct _chuni_led_data_buf_t* data)
{
if (data->board > 2)
{
return;
}
if (WaitForSingleObject(pipe_write_mutex, INFINITE) != WAIT_OBJECT_0)
{
return;
}
memcpy(&pipe_write_buf[data->board], data, sizeof(struct _chuni_led_data_buf_t));
pipe_update[data->board] = true;
ReleaseMutex(pipe_write_mutex);
}

14
chuniio/pipeimpl.h Normal file
View File

@ -0,0 +1,14 @@
/*
Pipe implementation for chuniio
Credits:
somewhatlurker, skogaby
*/
#pragma once
#include <windows.h>
#include "chuniio/leddata.h"
HRESULT led_pipe_init();
void led_pipe_update(struct _chuni_led_data_buf_t* data);

99
chuniio/serialimpl.c Normal file
View File

@ -0,0 +1,99 @@
#include <windows.h>
#include <process.h>
#include <stdbool.h>
#include <stdint.h>
#include "chuniio/leddata.h"
#include "chuniio/serialimpl.h"
#include "util/dprintf.h"
static HANDLE serial_port;
static HANDLE serial_write_mutex;
HRESULT led_serial_init(wchar_t led_com[12], DWORD baud)
{
// Setup the serial communications
BOOL status;
serial_port = CreateFileW(led_com,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
0,
NULL);
if (serial_port == INVALID_HANDLE_VALUE)
dprintf("Chunithm Serial LEDs: Failed to open COM port (Attempted on %S)\n", led_com);
else
dprintf("Chunithm Serial LEDs: COM port success!\n");
DCB dcb_serial_params = { 0 };
dcb_serial_params.DCBlength = sizeof(dcb_serial_params);
status = GetCommState(serial_port, &dcb_serial_params);
dcb_serial_params.BaudRate = baud;
dcb_serial_params.ByteSize = 8;
dcb_serial_params.StopBits = ONESTOPBIT;
dcb_serial_params.Parity = NOPARITY;
SetCommState(serial_port, &dcb_serial_params);
COMMTIMEOUTS timeouts = { 0 };
timeouts.ReadIntervalTimeout = 50;
timeouts.ReadTotalTimeoutConstant = 50;
timeouts.ReadTotalTimeoutMultiplier = 10;
timeouts.WriteTotalTimeoutConstant = 50;
timeouts.WriteTotalTimeoutMultiplier = 10;
SetCommTimeouts(serial_port, &timeouts);
if (!status)
{
return E_FAIL;
}
serial_write_mutex = CreateMutex(
NULL, // default security attributes
FALSE, // initially not owned
NULL); // unnamed mutex
if (serial_write_mutex == NULL)
{
return E_FAIL;
}
return S_OK;
}
void led_serial_update(struct _chuni_led_data_buf_t* data)
{
if (data->board > 2)
{
return;
}
if (WaitForSingleObject(serial_write_mutex, INFINITE) != WAIT_OBJECT_0)
{
return;
}
BOOL status = true;
DWORD bytes_written = 0;
if (serial_port != INVALID_HANDLE_VALUE) {
status = WriteFile(
serial_port,
data,
LED_OUTPUT_HEADER_SIZE + data->data_len,
&bytes_written,
NULL);
}
if (!status) {
DWORD last_err = GetLastError();
// dprintf("Chunithm Serial LEDs: Serial port write failed -- %d\n", last_err);
}
ReleaseMutex(serial_write_mutex);
}

15
chuniio/serialimpl.h Normal file
View File

@ -0,0 +1,15 @@
/*
Serial LED implementation for chuniio
Credits:
somewhatlurker, skogaby
*/
#pragma once
#include <windows.h>
#include "chuniio/leddata.h"
HRESULT led_serial_init(wchar_t led_com[12], DWORD baud);
void led_serial_update(struct _chuni_led_data_buf_t* data);

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

@ -0,0 +1,189 @@
#include <windows.h>
#include <assert.h>
#include <stdlib.h>
#include "chuniio/chu2to3.h"
#include "chusanhook/chuni-dll.h"
#include "util/dll-bind.h"
#include "util/dprintf.h"
const struct dll_bind_sym chuni_dll_syms[] = {
{
.sym = "chuni_io_jvs_init",
.off = offsetof(struct chuni_dll, jvs_init),
}, {
.sym = "chuni_io_jvs_poll",
.off = offsetof(struct chuni_dll, jvs_poll),
}, {
.sym = "chuni_io_jvs_read_coin_counter",
.off = offsetof(struct chuni_dll, jvs_read_coin_counter),
}, {
.sym = "chuni_io_slider_init",
.off = offsetof(struct chuni_dll, slider_init),
}, {
.sym = "chuni_io_slider_start",
.off = offsetof(struct chuni_dll, slider_start),
}, {
.sym = "chuni_io_slider_stop",
.off = offsetof(struct chuni_dll, slider_stop),
}, {
.sym = "chuni_io_slider_set_leds",
.off = offsetof(struct chuni_dll, slider_set_leds),
}, {
.sym = "chuni_io_led_init",
.off = offsetof(struct chuni_dll, led_init),
}, {
.sym = "chuni_io_led_set_colors",
.off = offsetof(struct chuni_dll, led_set_leds),
}
};
const struct dll_bind_sym chu2to3_dll_syms[] = {
{
.sym = "chu2to3_io_jvs_init",
.off = offsetof(struct chuni_dll, jvs_init),
}, {
.sym = "chu2to3_io_jvs_poll",
.off = offsetof(struct chuni_dll, jvs_poll),
}, {
.sym = "chu2to3_io_jvs_read_coin_counter",
.off = offsetof(struct chuni_dll, jvs_read_coin_counter),
}, {
.sym = "chu2to3_io_slider_init",
.off = offsetof(struct chuni_dll, slider_init),
}, {
.sym = "chu2to3_io_slider_start",
.off = offsetof(struct chuni_dll, slider_start),
}, {
.sym = "chu2to3_io_slider_stop",
.off = offsetof(struct chuni_dll, slider_stop),
}, {
.sym = "chu2to3_io_slider_set_leds",
.off = offsetof(struct chuni_dll, slider_set_leds),
}, {
.sym = "chu2to3_io_led_init",
.off = offsetof(struct chuni_dll, led_init),
}, {
.sym = "chu2to3_io_led_set_colors",
.off = offsetof(struct chuni_dll, led_set_leds),
}
};
/* Helper function to determine upon dll_bind failure whether the required functions were found
NOTE: relies on symbols order declared above */
static HRESULT has_enough_symbols(uint16_t version, uint8_t count)
{
if ( version <= 0x0101 && count == 7 )
return S_OK;
if ( version >= 0x0102 && count == 9 )
return S_OK;
return E_FAIL;
}
struct chuni_dll chuni_dll;
// Copypasta DLL binding and diagnostic message boilerplate.
// Not much of this lends itself to being easily factored out. Also there
// will be a lot of API-specific branching code here eventually as new API
// versions get defined, so even though these functions all look the same
// now this won't remain the case forever.
HRESULT chuni_dll_init(const struct chuni_dll_config *cfg, HINSTANCE self)
{
uint16_t (*get_api_version)(void);
const struct dll_bind_sym *sym;
HINSTANCE owned;
HINSTANCE src;
HRESULT hr;
assert(cfg != NULL);
assert(self != NULL);
owned = NULL;
src = self;
if (cfg->chu2to3) {
dprintf("Chunithm IO: using chu2to3 engine for IO DLL: %S\n", cfg->path);
} else if (cfg->path[0] != L'\0') {
owned = LoadLibraryW(cfg->path);
if (owned == NULL) {
hr = HRESULT_FROM_WIN32(GetLastError());
dprintf("Chunithm IO: Failed to load IO DLL: %lx: %S\n",
hr,
cfg->path);
goto end;
}
dprintf("Chunithm IO: Using custom IO DLL: %S\n", cfg->path);
src = owned;
}
if (cfg->chu2to3) {
if (chu2to3_load_dll(cfg->path) != 0)
dprintf("Could not init chu2to3 engine\n");
get_api_version = (void *) GetProcAddress(src, "chu2to3_io_get_api_version");
}
else
{
get_api_version = (void *) GetProcAddress(src, "chuni_io_get_api_version");
}
if (get_api_version != NULL) {
chuni_dll.api_version = get_api_version();
} else {
chuni_dll.api_version = 0x0100;
dprintf("Custom IO DLL does not expose chuni_io_get_api_version, "
"assuming API version 1.0.\n"
"Please ask the developer to update their DLL.\n");
}
if (chuni_dll.api_version >= 0x0200) {
hr = E_NOTIMPL;
dprintf("Chunithm IO: Custom IO DLL implements an unsupported "
"API version (%#04x). Please update Segatools.\n",
chuni_dll.api_version);
goto end;
}
sym = cfg->chu2to3 ? chu2to3_dll_syms : chuni_dll_syms;
const struct dll_bind_sym *init_sym = &sym[0];
hr = dll_bind(&chuni_dll, src, &sym, _countof(chuni_dll_syms));
if (FAILED(hr)) {
if (src != self) {
// Might still be ok depending on external dll API version
int bind_count = sym - init_sym;
if ( has_enough_symbols(chuni_dll.api_version, bind_count) == S_OK )
{
hr = S_OK;
} else {
dprintf("Chunithm IO: Custom IO DLL does not provide function "
"\"%s\". Please contact your IO DLL's developer for "
"further assistance.\n",
sym->sym);
dprintf("imported %d symbols\n",bind_count);
goto end;
}
} else {
dprintf("Internal error: could not reflect \"%s\"\n", sym->sym);
}
}
owned = NULL;
end:
if (owned != NULL) {
FreeLibrary(owned);
}
return hr;
}

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

@ -0,0 +1,27 @@
#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);
HRESULT (*led_init)(void);
void (*led_set_leds)(uint8_t board, uint8_t *rgb);
};
struct chuni_dll_config {
wchar_t path[MAX_PATH];
uint8_t chu2to3;
};
extern struct chuni_dll chuni_dll;
HRESULT chuni_dll_init(const struct chuni_dll_config *cfg, HINSTANCE self);

34
chusanhook/chusanhook.def Normal file
View File

@ -0,0 +1,34 @@
LIBRARY chusanhook
EXPORTS
Direct3DCreate9
aime_io_get_api_version
aime_io_init
aime_io_led_set_color
aime_io_nfc_get_aime_id
aime_io_nfc_get_felica_id
aime_io_nfc_poll
amDllVideoClose @2
amDllVideoGetVBiosVersion @4
amDllVideoOpen @1
amDllVideoSetResolution @3
chuni_io_get_api_version
chuni_io_jvs_init
chuni_io_jvs_poll
chuni_io_jvs_read_coin_counter
chuni_io_slider_init
chuni_io_slider_set_leds
chuni_io_slider_start
chuni_io_slider_stop
chuni_io_led_init
chuni_io_led_set_colors
chu2to3_io_get_api_version
chu2to3_io_jvs_init
chu2to3_io_jvs_poll
chu2to3_io_jvs_read_coin_counter
chu2to3_io_slider_init
chu2to3_io_slider_set_leds
chu2to3_io_slider_start
chu2to3_io_slider_stop
chu2to3_io_led_init
chu2to3_io_led_set_colors

166
chusanhook/config.c Normal file
View File

@ -0,0 +1,166 @@
#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
// path for 32bit only dlls (internal chu2to3 engine)
GetPrivateProfileStringW(
L"chuniio",
L"path",
L"",
cfg->path,
_countof(cfg->path),
filename);
if (cfg->path[0] != L'\0') {
cfg->chu2to3 = 1;
} else {
cfg->chu2to3 = 0;
#if defined(ENV32BIT)
GetPrivateProfileStringW(
L"chuniio",
L"path32",
L"",
cfg->path,
_countof(cfg->path),
filename);
#elif defined(ENV64BIT)
GetPrivateProfileStringW(
L"chuniio",
L"path64",
L"",
cfg->path,
_countof(cfg->path),
filename);
#else
#error "Unknown environment"
#endif
}
}
void slider_config_load(struct slider_config *cfg, const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
cfg->enable = GetPrivateProfileIntW(L"slider", L"enable", 1, filename);
}
void led15093_config_load(struct led15093_config *cfg, const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
wchar_t tmpstr[16];
bool cvt_port;
memset(cfg->board_number, ' ', sizeof(cfg->board_number));
memset(cfg->chip_number, ' ', sizeof(cfg->chip_number));
memset(cfg->boot_chip_number, ' ', sizeof(cfg->boot_chip_number));
cfg->enable = GetPrivateProfileIntW(L"led15093", L"enable", 1, filename);
cfg->port_no = 0;
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));
// Force load the 64bit Aime DLL instead of the 32bit one
cfg->aime.dll.path64 = true;
platform_config_load(&cfg->platform, filename);
aime_config_load(&cfg->aime, filename);
dvd_config_load(&cfg->dvd, filename);
io4_config_load(&cfg->io4, filename);
gfx_config_load(&cfg->gfx, filename);
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);

181
chusanhook/dllmain.c Normal file
View File

@ -0,0 +1,181 @@
#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];
bool is_cvt = dipsw[2];
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: Cab Type: %s\n", is_cvt ? "CVT" : "SP");
break;
}
}
unsigned int first_port = is_cvt ? 2 : 20;
if (!is_cvt) {
hr = vfd_hook_init(2);
if (FAILED(hr)) {
goto fail;
}
}
if ( chuni_dll.led_init == NULL || chuni_dll.led_set_leds == NULL )
{
dprintf("IO DLL doesn't support led_init/led_set_leds, cannot start LED15093 hook\n");
} else {
hr = led15093_hook_init(&chusan_hook_cfg.led15093,
chuni_dll.led_init, chuni_dll.led_set_leds, first_port, 2, 2, 1);
if (FAILED(hr)) {
goto fail;
}
}
hr = sg_reader_hook_init(&chusan_hook_cfg.aime, 4, is_cvt ? 2: 3, chusan_hook_mod);
if (FAILED(hr)) {
goto fail;
}
/* Initialize debug helpers */
spike_hook_init(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);
}

106
chusanhook/io4.c Normal file
View File

@ -0,0 +1,106 @@
#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);
chuni_dll.jvs_read_coin_counter(&coins);
if (chuni_dll.api_version >= 0x0101) {
// Use correct mapping
masks = chunithm_jvs_ir_masks;
} else {
// Use backwards-compatible incorrect mapping
masks = chunithm_jvs_ir_masks_v1;
}
if (opbtn & CHUNI_IO_OPBTN_TEST) {
state->buttons[0] |= IO4_BUTTON_TEST;
}
if (opbtn & CHUNI_IO_OPBTN_SERVICE) {
state->buttons[0] |= IO4_BUTTON_SERVICE;
}
// Update the coin counter with the value from jvs_read_coin_counter
state->chutes[0] = coins << 8;
for (i = 0; i < 6; i++) {
/* Beam "press" is active-low hence the ~ */
if (~beams & (1 << i)) {
state->buttons[0] |= masks[i].p1;
state->buttons[1] |= masks[i].p2;
}
}
return S_OK;
}

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

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

@ -43,6 +43,47 @@ path=
; Leave empty if you want to use Segatools built-in keyboard input.
path=
[led15093]
; Enable emulation of the 15093-06 controlled lights, which handle the air tower
; RGBs and the rear LED panel (billboard) on the cabinet.
enable=1
[led]
; Output billboard LED strip data to a named pipe called "\\.\pipe\chuni_ledstrip"
cabLedOutputPipe=1
; Output billboard LED strip data to serial
cabLedOutputSerial=0
; Output slider LED data to the named pipe
controllerLedOutputPipe=1
; Output slider LED data to the serial port
controllerLedOutputSerial=0
; Serial port to send data to if using serial output. Default is COM5.
;serialPort=COM5
; Baud rate for serial data
;serialBaud=921600
; Data output a sequence of bytes, with JVS-like framing.
; Each "packet" starts with 0xE0 as a sync. To avoid E0 appearing elsewhere,
; 0xD0 is used as an escape character -- if you receive D0 in the output, ignore
; it and use the next sent byte plus one instead.
;
; After the sync is one byte for the board number that was updated, followed by
; the red, green and blue values for each LED.
;
; Board 0 has 53 LEDs:
; [0]-[49]: snakes through left half of billboard (first column starts at top)
; [50]-[52]: left side partition LEDs
;
; Board 1 has 63 LEDs:
; [0]-[59]: right half of billboard (first column starts at bottom)
; [60]-[62]: right side partition LEDs
;
; Board 2 is the slider and has 31 LEDs:
; [0]-[31]: slider LEDs right to left BRG, alternating between keys and dividers
; -----------------------------------------------------------------------------
; Input settings
; -----------------------------------------------------------------------------
@ -62,6 +103,20 @@ test=0x31
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:
@ -73,8 +128,8 @@ coin=0x33
;
; Uncomment and complete the following sequence of settings to configure a
; custom high-precision touch strip controller if you have one.
[slider]
;cell32=0x53
;cell31=0x53
;cell30=0x53
;cell1=0x53
;cell2=0x53
; ... etc ...
;cell31=0x53
;cell32=0x53

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

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

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

@ -0,0 +1,169 @@
[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]
; Uncomment this if you have custom (x64) aime implementation.
; Leave empty if you want to use Segatools built-in keyboard input.
;path=
[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.139.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, 1 = 60FPS
dipsw2=1
; Cab type: 0 = SP, 1 = CVT. SP will enable VFD and eMoney. This setting will switch
; the LED 837-15093-06 COM port and the AiMe reder hardware generation as well.
dipsw3=1
[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
[chuniio]
; Uncomment this if you have custom chuniio implementation comprised of a single 32bit DLL.
; (will use chu2to3 engine internally)
;path=
; Uncomment both of these if you have custom chuniio implementation comprised of two DLLs.
; x86 chuniio to path32, x64 to path64. Both are necessary.
;path32=
;path64=
[led15093]
; Enable emulation of the 15093-06 controlled lights, which handle the air tower
; RGBs and the rear LED panel (billboard) on the cabinet.
enable=1
[led]
; Output billboard LED strip data to a named pipe called "\\.\pipe\chuni_led"
cabLedOutputPipe=1
; Output billboard LED strip data to serial
cabLedOutputSerial=0
; Output slider LED data to the named pipe
controllerLedOutputPipe=1
; Output slider LED data to the serial port
controllerLedOutputSerial=0
; Serial port to send data to if using serial output. Default is COM5.
;serialPort=COM5
; Baud rate for serial data
;serialBaud=921600
; Data output a sequence of bytes, with JVS-like framing.
; Each "packet" starts with 0xE0 as a sync. To avoid E0 appearing elsewhere,
; 0xD0 is used as an escape character -- if you receive D0 in the output, ignore
; it and use the next sent byte plus one instead.
;
; After the sync is one byte for the board number that was updated, followed by
; the red, green and blue values for each LED.
;
; Board 0 has 53 LEDs:
; [0]-[49]: snakes through left half of billboard (first column starts at top)
; [50]-[52]: left side partition LEDs
;
; Board 1 has 63 LEDs:
; [0]-[59]: right half of billboard (first column starts at bottom)
; [60]-[62]: right side partition LEDs
;
; Board 2 is the slider and has 31 LEDs:
; [0]-[31]: slider LEDs right to left BRG, alternating between keys and dividers
; -----------------------------------------------------------------------------
; 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
}
}
}

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

@ -0,0 +1,181 @@
[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.158.0
; Override the keychip's region code. Most games seem to pay attention to the
; DS EEPROM region code and not the keychip region code, and this seems to be
; a bit mask that controls which Nu PCB region codes this keychip is authorized
; for. So it probably only affects the system software and not the game software.
; 1: JPN: Japan, 4: EXP: Export (for Asian markets)
region=4
[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
; Force feedback settings.
; Strength of the force feedback spring effect in percent. Possible values
; are 0-100.
centerSpringStrength=30

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

108
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,31 +139,42 @@ deviceName=
;
; Can be the same device as the wheel.
;
; Example: T500
; Example: G29
shifterName=
; Name of the DirectInput pedals to use (or any subset thereof).
; Leave blank if you do not have separate pedals; aka the pedals are part of
; the wheel.
;
; The pedals will be mapped to the accelAxis and brakeAxis.
pedalsName=
; Pedal mappings. Valid axis names are:
;
; X, Y, Z, RX, RY, RZ, U, V
;
; (U and V are old names for Slider 1 and Slider 2).
; The examples below are valid for a Thrustmaster TMX.
brakeAxis=RZ
; The examples below are valid for a Logitech G29.
brakeAxis=U
accelAxis=Y
; DirectInput button numbers to map to menu inputs. Note that buttons are
; numbered from 1; some software numbers buttons from 0.
start=3
viewChg=10
start=1
viewChg=2
; Button mappings for the simulated six-speed shifter.
shiftDn=1
shiftUp=2
shiftDn=5
shiftUp=6
; Button mappings for the positional shifter, if present.
gear1=1
gear2=2
gear3=3
gear4=4
gear5=5
gear6=6
gear1=13
gear2=14
gear3=15
gear4=16
gear5=17
gear6=18
; Invert the accelerator and or brake axis
; (Needed when using DirectInput for the Dualshock 4 for example)
reverseAccelAxis=0
reverseBrakeAxis=0
; Force feedback settings.
; Strength of the force feedback spring effect in percent. Possible values
; are 0-100.
centerSpringStrength=30

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

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

@ -0,0 +1,140 @@
[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
; Force feedback settings.
; Strength of the force feedback spring effect in percent. Possible values
; are 0-100.
centerSpringStrength=30

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

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