ekt: Make y3io expandable with I/O dlls

This commit is contained in:
2025-09-02 15:37:40 +02:00
parent da0fc56500
commit 873ae0a30a
16 changed files with 448 additions and 151 deletions

100
common/y3io/config.c Normal file
View File

@ -0,0 +1,100 @@
#include "y3io/config.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
void y3_config_load(
struct y3_config *cfg,
const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
wchar_t tmpstr[5];
memset(cfg->firm_name_field, ' ', sizeof(cfg->firm_name_field) - 1);
cfg->firm_name_field[sizeof(cfg->firm_name_field) - 1] = '\0';
memset(cfg->firm_name_printer, ' ', sizeof(cfg->firm_name_printer) - 1);
cfg->firm_name_printer[sizeof(cfg->firm_name_printer) - 1] = '\0';
memset(cfg->target_code_field, ' ', sizeof(cfg->target_code_field) - 1);
cfg->target_code_field[sizeof(cfg->target_code_field) - 1] = '\0';
memset(cfg->target_code_printer, ' ', sizeof(cfg->target_code_printer) - 1);
cfg->target_code_printer[sizeof(cfg->target_code_printer) - 1] = '\0';
cfg->enable = GetPrivateProfileIntW(L"flatPanelReader", L"enable", 1, filename);
cfg->port_field = GetPrivateProfileIntW(L"flatPanelReader", L"port_field", 10, filename);
cfg->port_printer = GetPrivateProfileIntW(L"flatPanelReader", L"port_printer", 11, filename);
cfg->dll_version = (float)GetPrivateProfileIntW(
L"flatPanelReader",
L"dllVersion",
1,
filename);
cfg->firm_version = (float)GetPrivateProfileIntW(
L"flatPanelReader",
L"firmVersion",
1,
filename);
GetPrivateProfileStringW(
L"flatPanelReader",
L"firmNameField",
L"SFPR",
tmpstr,
_countof(tmpstr),
filename);
wcstombs(cfg->firm_name_field, tmpstr, sizeof(cfg->firm_name_field) - 1);
GetPrivateProfileStringW(
L"flatPanelReader",
L"firmNamePrinter",
L"SPRT",
tmpstr,
_countof(tmpstr),
filename);
wcstombs(cfg->firm_name_printer, tmpstr, sizeof(cfg->firm_name_printer) - 1);
GetPrivateProfileStringW(
L"flatPanelReader",
L"targetCodeField",
L"SFR0",
tmpstr,
_countof(tmpstr),
filename);
wcstombs(cfg->target_code_field, tmpstr, sizeof(cfg->target_code_field) - 1);
GetPrivateProfileStringW(
L"flatPanelReader",
L"targetCodePrinter",
L"SPT0",
tmpstr,
_countof(tmpstr),
filename);
wcstombs(cfg->target_code_printer, tmpstr, sizeof(cfg->target_code_printer) - 1);
}
void y3_dll_config_load(
struct y3_dll_config *cfg,
const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
GetPrivateProfileStringW(
L"y3io",
L"path",
L"",
cfg->path,
_countof(cfg->path),
filename);
}

29
common/y3io/config.h Normal file
View File

@ -0,0 +1,29 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <windows.h>
struct y3_config {
bool enable;
float dll_version;
float firm_version;
char firm_name_field[5];
char firm_name_printer[5];
char target_code_field[5];
char target_code_printer[5];
uint8_t port_field;
uint8_t port_printer;
};
struct y3_dll_config {
wchar_t path[MAX_PATH];
};
void y3_config_load(
struct y3_config *cfg,
const wchar_t *filename);
void y3_dll_config_load(
struct y3_dll_config *cfg,
const wchar_t *filename);

View File

@ -0,0 +1,63 @@
#include <stdint.h>
#include <windows.h>
#include "util/dprintf.h"
#include "y3io/y3.h"
uint16_t y3_io_get_api_version() {
return 0x0100;
}
HRESULT y3_io_init() {
dprintf("Y3 Dummy Cards: initialized\n");
return S_OK;
}
HRESULT y3_io_get_cards(struct CardInfo* pCardInfo, int* numCards) {
memset(pCardInfo, 0, sizeof(struct CardInfo) * *numCards);
const float paddingX = 86.0f;
const float paddingY = 54.0f;
// Dimensions of the flat panel
const float panelHeight = 1232.0f;
const float panelWidth = 1160.0f;
// Number of cards in each row and column
const int numRows = 4;
const int numCols = 4;
int activeCards = numRows * numCols;
if (*numCards < activeCards) {
activeCards = *numCards;
}
*numCards = activeCards;
// Calculate spacing between cards
const float cardWidth = (panelWidth - paddingY * 2) / (numCols - 1);
const float cardHeight = (panelHeight - paddingX * 2) / (numRows - 1);
// Create example card info
for (int i = 0; i < activeCards; i++) {
int row = i / numCols;
int col = i % numCols;
pCardInfo[i].fX = paddingX + (col * cardHeight);
pCardInfo[i].fY = paddingY + (row * cardWidth);
pCardInfo[i].fAngle = 0.0f;
pCardInfo[i].eCardType = TYPE0;
pCardInfo[i].eCardStatus = VALID;
pCardInfo[i].uID = 1000 + i;
pCardInfo[i].nNumChars = 0;
pCardInfo[i].ubChar0.Data = 0;
pCardInfo[i].ubChar1.Data = 0;
pCardInfo[i].ubChar2.Data = 0;
pCardInfo[i].ubChar3.Data = 0;
pCardInfo[i].ubChar4.Data = 0;
pCardInfo[i].ubChar5.Data = 0;
}
return S_OK;
}

View File

@ -0,0 +1,31 @@
#pragma once
#include <stdint.h>
#include <windows.h>
#include "y3io/y3.h"
/* Get the version of the Y3 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 y3_io_get_api_version();
/* Initialize the Y3 board. This function will be called before any other
y3_io_*() function calls. Errors returned from this function will
manifest as a disconnected Y3 board.
This method may be called multiple times.
Minimum API version: 0x0100 */
HRESULT y3_io_init();
/* Fills the given buffer with cards that are detected on the play field.
For the values inside CardInfo, see y3.h.
The input value of numCards is the size of the pCardInfo array.
The output value of numCards is how many cards (>=0) have been set in pCardInfo.
Minimum API version: 0x0100 */
HRESULT y3_io_get_cards(struct CardInfo* pCardInfo, int* numCards);

23
common/y3io/meson.build Normal file
View File

@ -0,0 +1,23 @@
y3io_lib = static_library(
'y3io',
name_prefix : '',
include_directories: inc,
implicit_include_directories : false,
dependencies : [
capnhook.get_variable('hook_dep'),
capnhook.get_variable('hooklib_dep')
],
link_with : [
util_lib
],
sources : [
'config.c',
'config.h',
'y3-dll.c',
'y3-dll.h',
'y3.c',
'y3.h',
'impl/dummy/y3io.c',
'impl/dummy/y3io.h',
],
)

104
common/y3io/y3-dll.c Normal file
View File

@ -0,0 +1,104 @@
#include <windows.h>
#include <assert.h>
#include <stdlib.h>
#include "config.h"
#include "y3.h"
#include "y3-dll.h"
#include "util/dll-bind.h"
#include "util/dprintf.h"
const struct dll_bind_sym y3_dll_syms[] = {
{
.sym = "y3_io_init",
.off = offsetof(struct y3_dll, init),
}, {
.sym = "y3_io_get_cards",
.off = offsetof(struct y3_dll, get_cards),
}
};
struct y3_dll y3_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 y3_dll_init(const struct y3_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("Y3: Failed to load IO DLL: %lx: %S\n",
hr,
cfg->path);
goto end;
}
dprintf("Y3: Using custom IO DLL: %S\n", cfg->path);
src = owned;
} else {
owned = NULL;
src = self;
}
get_api_version = (void *) GetProcAddress(src, "y3_io_get_api_version");
if (get_api_version != NULL) {
y3_dll.api_version = get_api_version();
} else {
y3_dll.api_version = 0x0100;
dprintf("Custom IO DLL does not expose y3_io_get_api_version, "
"assuming API version 1.0.\n"
"Please ask the developer to update their DLL.\n");
}
if (y3_dll.api_version >= 0x0200) {
hr = E_NOTIMPL;
dprintf("Y3: Custom IO DLL implements an unsupported "
"API version (%#04x). Please update Segatools.\n",
y3_dll.api_version);
goto end;
}
sym = y3_dll_syms;
hr = dll_bind(&y3_dll, src, &sym, _countof(y3_dll_syms));
if (FAILED(hr)) {
if (src != self) {
dprintf("Y3: 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;
}

18
common/y3io/y3-dll.h Normal file
View File

@ -0,0 +1,18 @@
#pragma once
#include <windows.h>
#include <stdbool.h>
#include <stdint.h>
#include "config.h"
#include "y3.h"
struct y3_dll {
uint16_t api_version;
HRESULT (*init)(void);
HRESULT (*get_cards)(struct CardInfo* cards, int* len);
};
extern struct y3_dll y3_dll;
HRESULT y3_dll_init(const struct y3_dll_config *cfg, HINSTANCE self);

View File

@ -1,3 +1,4 @@
// ReSharper disable CppParameterNeverUsed
#include <windows.h>
#include <stdint.h>
@ -5,7 +6,8 @@
#include <stdlib.h>
#include <assert.h>
#include "ekthook/y3.h"
#include "y3.h"
#include "y3-dll.h"
#include "hook/table.h"
#include "hook/procaddr.h"
@ -264,11 +266,12 @@ static struct CardInfo card_data[MAX_CARD_SIZE];
static int* Y3_COM_FIELD = (int*)10;
static int* Y3_COM_PRINT = (int*)11;
void y3_hook_init(const struct y3_config* cfg, HINSTANCE self) {
HRESULT y3_hook_init(const struct y3_config* cfg, HINSTANCE self, const wchar_t* config_filename) {
HRESULT hr;
assert(cfg != NULL);
if (!cfg->enable) {
return;
return S_FALSE;
}
memcpy(&y3_config, cfg, sizeof(*cfg));
@ -279,7 +282,17 @@ void y3_hook_init(const struct y3_config* cfg, HINSTANCE self) {
memset(card_data, 0, sizeof(card_data));
struct y3_dll_config config;
y3_dll_config_load(&config, config_filename);
hr = y3_dll_init(&config, self);
if (FAILED(hr)) {
return hr;
}
dprintf("Y3: hook enabled (field port = %d, printer port = %d)\n", cfg->port_field, cfg->port_printer);
return hr;
}
void y3_insert_hooks(HMODULE target) {
@ -322,15 +335,23 @@ CALL uint32_t API_GetErrorMessage(uint32_t errNo, char* szMessage,
}
CALL int* API_Connect(char* szPortName) {
HRESULT hr;
dprintf("Y3: %s(%s)\n", __func__, szPortName);
if (!y3_config.enable) {
return NULL;
} else {
char number[2];
strncpy(number, szPortName + 3, 2);
return (int*)(uintptr_t)atoi(number);
}
hr = y3_dll.init();
if (FAILED(hr)) {
dprintf("Y3: Hook DLL initialization failed: %lx\n", hr);
return NULL;
}
char number[2];
strncpy(number, szPortName + 3, 2);
return (int*)(uintptr_t)atoi(number);
}
CALL int API_Close(int* hDevice) {
@ -413,49 +434,15 @@ CALL int API_GetCardInfo(int* hDevice, int numCards, struct CardInfo* pCardInfo)
// numCards = max cards
if (hDevice == Y3_COM_FIELD) {
memset(pCardInfo, 0, sizeof(struct CardInfo) * numCards);
const float paddingX = 86.0f;
const float paddingY = 54.0f;
// Dimensions of the flat panel
const float panelHeight = 1232.0f;
const float panelWidth = 1160.0f;
// Number of cards in each row and column
const int numRows = 4;
const int numCols = 4;
int activeCards = numRows * numCols;
if (numCards < activeCards) {
activeCards = numCards;
int cards = numCards;
HRESULT hr = y3_dll.get_cards(pCardInfo, &cards);
if (FAILED(hr)) {
dprintf("Y3: DLL returned error when retrieving cards: %lx\n", hr);
return 0;
}
// Calculate spacing between cards
const float cardWidth = (panelWidth - paddingY * 2) / (numCols - 1);
const float cardHeight = (panelHeight - paddingX * 2) / (numRows - 1);
// Create example card info
for (int i = 0; i < activeCards; i++) {
int row = i / numCols;
int col = i % numCols;
pCardInfo[i].fX = paddingX + (col * cardHeight);
pCardInfo[i].fY = paddingY + (row * cardWidth);
pCardInfo[i].fAngle = 0.0f;
pCardInfo[i].eCardType = TYPE0;
pCardInfo[i].eCardStatus = VALID;
pCardInfo[i].uID = 1000 + i;
pCardInfo[i].nNumChars = 0;
pCardInfo[0].ubChar0.Data = 0;
pCardInfo[0].ubChar1.Data = 0;
pCardInfo[0].ubChar2.Data = 0;
pCardInfo[0].ubChar3.Data = 0;
pCardInfo[0].ubChar4.Data = 0;
pCardInfo[0].ubChar5.Data = 0;
}
return activeCards;
return cards;
} else if (hDevice == Y3_COM_PRINT) {
pCardInfo[0].fX = 0;
pCardInfo[0].fY = 0;
@ -606,7 +593,7 @@ uint32_t convert_string_to_uint(const char* firmName) {
// Iterate over each character in the string and construct the uint32_t
for (int i = 0; i < 4; i++) {
result |= ((uint32_t)firmName[i] << (i * 8));
result |= (uint32_t)firmName[i] << (i * 8);
}
return result;

View File

@ -1,19 +1,18 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "config.h"
#pragma pack(push, 1)
enum Y3WebsocketProtocol {
Y3_WS_PROTO_ERROR = 0,
Y3_WS_PROTO_CARD_DATA = 2,
Y3_WS_PROTO_PING = 1,
Y3_WS_PROTO_GAME_TITLE = 3,
};
// Value held on a card.
struct CardByteData {
unsigned int Data;
};
// Unused
enum CardType {
TYPE0 = 0,
TYPE1,
@ -26,43 +25,47 @@ enum CardType {
};
enum CardStatus {
// Unset entry
INVALID = 0,
// Valid card
VALID = 1,
// Not a card but rather infrared interference. Only relevant in test mode.
INFERENCE = 2,
// This is only used by the printer camera.
MARKER = 3
};
struct CardInfo {
// X position of the card.
float fX; // 0x00|0
// Y position of the card.
float fY; // 0x04|4
// Rotation of the card in degrees >=0.0 && <360.0
float fAngle; // 0x08|8
// Unused
enum CardType eCardType; // 0x0C|12
// see enum CardStatus
enum CardStatus eCardStatus; // 0x10|16
// card's BaseCode. used for a reference to the card being tracked as well as part of the IvCode.
unsigned int uID; // 0x14|20
// Unused
int nNumChars; // 0x18|24
// Title Code. Is 8589934592 for EKT.
struct CardByteData ubChar0; // 0x1C|28
// Must be 0x4000 for the printer camera.
struct CardByteData ubChar1; // 0x20|32
// Unused
struct CardByteData ubChar2; // 0x24|36
// Must be 0x0 for the printer camera.
struct CardByteData ubChar3; // 0x28|40
// Unused
struct CardByteData ubChar4; // 0x2C|44
// Unused
struct CardByteData ubChar5; // 0x30|48
};
#pragma pack(pop)
struct y3_config {
bool enable;
float dll_version;
float firm_version;
char firm_name_field[5];
char firm_name_printer[5];
char target_code_field[5];
char target_code_printer[5];
uint8_t port_field;
uint8_t port_printer;
};
void y3_hook_init(const struct y3_config *cfg, HINSTANCE self);
HRESULT y3_hook_init(const struct y3_config *cfg, HINSTANCE self, const wchar_t* config_path);
void y3_insert_hooks(HMODULE target);

View File

@ -108,6 +108,11 @@ path=
; Leave empty if you want to use Segatools built-in keyboard/gamepad input.
path=
[y3io]
; To use a custom Y3 IO DLL enter its path here.
; Leave empty if you want to use ... TBA ...
path=
; -----------------------------------------------------------------------------
; Input settings
; -----------------------------------------------------------------------------

View File

@ -8,6 +8,8 @@
#include "platform/config.h"
#include "ekthook/config.h"
#include "y3io/config.h"
#include "ekthook/ekt-dll.h"
void led15093_config_load(struct led15093_config *cfg, const wchar_t *filename)
@ -87,83 +89,6 @@ void ekt_dll_config_load(
filename);
}
void y3_config_load(
struct y3_config *cfg,
const wchar_t *filename)
{
assert(cfg != NULL);
assert(filename != NULL);
wchar_t tmpstr[5];
memset(cfg->firm_name_field, ' ', sizeof(cfg->firm_name_field) - 1);
cfg->firm_name_field[sizeof(cfg->firm_name_field) - 1] = '\0';
memset(cfg->firm_name_printer, ' ', sizeof(cfg->firm_name_printer) - 1);
cfg->firm_name_printer[sizeof(cfg->firm_name_printer) - 1] = '\0';
memset(cfg->target_code_field, ' ', sizeof(cfg->target_code_field) - 1);
cfg->target_code_field[sizeof(cfg->target_code_field) - 1] = '\0';
memset(cfg->target_code_printer, ' ', sizeof(cfg->target_code_printer) - 1);
cfg->target_code_printer[sizeof(cfg->target_code_printer) - 1] = '\0';
cfg->enable = GetPrivateProfileIntW(L"flatPanelReader", L"enable", 1, filename);
cfg->port_field = GetPrivateProfileIntW(L"flatPanelReader", L"port_field", 10, filename);
cfg->port_printer = GetPrivateProfileIntW(L"flatPanelReader", L"port_printer", 11, filename);
cfg->dll_version = (float)GetPrivateProfileIntW(
L"flatPanelReader",
L"dllVersion",
1,
filename);
cfg->firm_version = (float)GetPrivateProfileIntW(
L"flatPanelReader",
L"firmVersion",
1,
filename);
GetPrivateProfileStringW(
L"flatPanelReader",
L"firmNameField",
L"SFPR",
tmpstr,
_countof(tmpstr),
filename);
wcstombs(cfg->firm_name_field, tmpstr, sizeof(cfg->firm_name_field) - 1);
GetPrivateProfileStringW(
L"flatPanelReader",
L"firmNamePrinter",
L"SPRT",
tmpstr,
_countof(tmpstr),
filename);
wcstombs(cfg->firm_name_printer, tmpstr, sizeof(cfg->firm_name_printer) - 1);
GetPrivateProfileStringW(
L"flatPanelReader",
L"targetCodeField",
L"SFR0",
tmpstr,
_countof(tmpstr),
filename);
wcstombs(cfg->target_code_field, tmpstr, sizeof(cfg->target_code_field) - 1);
GetPrivateProfileStringW(
L"flatPanelReader",
L"targetCodePrinter",
L"SPT0",
tmpstr,
_countof(tmpstr),
filename);
wcstombs(cfg->target_code_printer, tmpstr, sizeof(cfg->target_code_printer) - 1);
}
void ekt_hook_config_load(
struct ekt_hook_config *cfg,

View File

@ -13,7 +13,7 @@
#include "unityhook/config.h"
#include "ekthook/ekt-dll.h"
#include "ekthook/y3.h"
#include "y3io/config.h"
struct ekt_hook_config {
struct platform_config platform;

View File

@ -41,6 +41,8 @@
#include "util/dprintf.h"
#include "util/env.h"
#include "y3io/y3-dll.h"
#include "y3io/y3.h"
static HMODULE ekt_hook_mod;
static process_entry_t ekt_startup;
@ -63,8 +65,12 @@ static DWORD CALLBACK ekt_pre_startup(void)
serial_hook_init();
/* Hook external DLL APIs */
y3_hook_init(&ekt_hook_cfg.y3, ekt_hook_mod);
hr = y3_hook_init(&ekt_hook_cfg.y3, ekt_hook_mod, get_config_path());
if (FAILED(hr)) {
goto fail;
}
/* Initialize emulation hooks */

View File

@ -15,6 +15,9 @@ EXPORTS
ekt_io_poll
ekt_io_led_init
ekt_io_led_set_colors
y3_io_get_api_version
y3_io_init
y3_io_get_cards
API_DLLVersion @1
API_GetLastError @2
API_GetErrorMessage @3

View File

@ -17,6 +17,7 @@ shared_library(
platform_lib,
unityhook_lib,
util_lib,
y3io_lib,
],
sources : [
'config.c',
@ -26,7 +27,5 @@ shared_library(
'ekt-dll.h',
'io4.c',
'io4.h',
'y3.c',
'y3.h'
],
)

View File

@ -106,6 +106,7 @@ subdir('common/jvs')
subdir('common/platform')
subdir('common/util')
subdir('common/aimeio')
subdir('common/y3io')
subdir('common/gfxhook')
subdir('common/unityhook')