Custom HID mai2io DLL and 1KHz FPS

This commit is contained in:
whowechina 2023-10-05 23:11:24 +08:00
parent aef8ce1931
commit 84d808b89d
7 changed files with 531 additions and 2 deletions

View File

@ -0,0 +1,49 @@
#include <windows.h>
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include "mai2io/config.h"
/*
Maimai DX Default key binding
1P: self-explanatory
2P: (Numpad) 8, 9, 6, 3, 2, 1, 4, 7, *
*/
static const int mai2_io_1p_default[] = {'W', 'E', 'D', 'C', 'X', 'Z', 'A', 'Q', '3'};
static const int mai2_io_2p_default[] = {0x68, 0x69, 0x66, 0x63, 0x62, 0x61, 0x64, 0x67, 0x54};
void mai2_io_config_load(
struct mai2_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);
for (i = 0 ; i < 9 ; i++) {
swprintf_s(key, _countof(key), L"1p_btn%i", i + 1);
cfg->vk_1p_btn[i] = GetPrivateProfileIntW(
L"button",
key,
mai2_io_1p_default[i],
filename);
}
for (i = 0 ; i < 9 ; i++) {
swprintf_s(key, _countof(key), L"2p_btn%i", i + 1);
cfg->vk_2p_btn[i] = GetPrivateProfileIntW(
L"button",
key,
mai2_io_2p_default[i],
filename);
}
}

View File

@ -0,0 +1,18 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
struct mai2_io_config {
uint8_t vk_test;
uint8_t vk_service;
uint8_t vk_coin;
uint8_t vk_1p_btn[9];
uint8_t vk_2p_btn[9];
};
void mai2_io_config_load(
struct mai2_io_config *cfg,
const wchar_t *filename);

View File

@ -0,0 +1,324 @@
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <limits.h>
#include <stdint.h>
#include <hidsdi.h>
#include <setupapi.h>
#include "mai2io/mai2io.h"
#include "mai2io/config.h"
static GUID hidclass_guid = {0x745a17a0, 0x74d3, 0x11d0, {0xb6, 0xfe, 0x00, 0xa0, 0xc9, 0x0f, 0x57, 0xda}};
static BOOLEAN get_device_path(char *lPath, uint16_t vid, uint16_t pid, int8_t mi)
{
const GUID *guid = &hidclass_guid;
HidD_GetHidGuid(&hidclass_guid);
// Get device interface info set handle
// for all devices attached to system
HDEVINFO hDevInfo = SetupDiGetClassDevs(guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); // Function class devices.
if(hDevInfo == INVALID_HANDLE_VALUE)
return FALSE;
// Retrieve a context structure for a device interface of a device information set.
BYTE buf[1024];
PSP_DEVICE_INTERFACE_DETAIL_DATA pspdidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)buf;
SP_DEVICE_INTERFACE_DATA spdid;
SP_DEVINFO_DATA spdd;
DWORD dwSize;
char vidstr[64];
char mistr[64];
(void) pid; /* not used for now */
//sprintf(vidpidstr, "vid_%04x&pid_%04x&mi_%02x", vid, pid, mi);
sprintf(vidstr, "vid_%04x&", vid);
if (mi != -1) sprintf(mistr, "&mi_%02x", mi);
#if DEBUG == 1
printf("looking for substring %s in device path\r\n", vidstr);
#endif
spdid.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
// Iterate through all the interfaces and try to match one based on
// the device number.
for(DWORD i = 0; SetupDiEnumDeviceInterfaces(hDevInfo, NULL,guid, i, &spdid); i++)
{
// Get the device path.
dwSize = 0;
SetupDiGetDeviceInterfaceDetail(hDevInfo, &spdid, NULL, 0, &dwSize, NULL);
if(dwSize == 0 || dwSize > sizeof(buf))
continue;
pspdidd->cbSize = sizeof(*pspdidd);
ZeroMemory((PVOID)&spdd, sizeof(spdd));
spdd.cbSize = sizeof(spdd);
if(!SetupDiGetDeviceInterfaceDetail(hDevInfo, &spdid, pspdidd,
dwSize, &dwSize, &spdd))
continue;
#if DEBUG == 1
printf("checking path %s... ", pspdidd->DevicePath);
#endif
/* check if the device contains our wanted vid/pid */
// if ( strstr( pspdidd->DevicePath, vidpidstr ) == NULL )
if ( strstr( pspdidd->DevicePath, vidstr ) == NULL || ((mi!= -1) && strstr( pspdidd->DevicePath, mistr ) == NULL) )
{
#if DEBUG == 1
printf("that's not it.\r\n");
#endif
continue;
}
#if DEBUG == 1
printf("\r\nDevice found at %s\r\n", pspdidd->DevicePath);
#endif
//copy devpath into lPath
strcpy(lPath, pspdidd->DevicePath);
SetupDiDestroyDeviceInfoList(hDevInfo);
return TRUE;
}
SetupDiDestroyDeviceInfoList(hDevInfo);
return FALSE;
}
int hid_open_device(HANDLE *device_handle, uint16_t vid, uint16_t pid, uint8_t mi){
static uint8_t err_count = 0;
char path[256];
if (!get_device_path(path, vid, pid, mi))
{
#if DEBUG == 1
printf("\r\nDevice not detected (vid %04x pid %04x mi %02x).\r\n",vid,pid,mi);
#endif
err_count++;
if (err_count > 2){
printf("Could not init device after multiple attempts. Exiting.\r\n");
exit(1);
}
return -1;
}
#if DEBUG == 1
printf("\r\nDevice found (vid %04x pid %04x mi %02x).\r\n",vid,pid,mi);
#endif
*device_handle = CreateFile(path, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
if ( *device_handle == INVALID_HANDLE_VALUE )
{
printf("Could not open detected device (err = %lx).\r\n", GetLastError());
return -1;
}
return 0;
}
int hid_get_report(HANDLE device_handle, uint8_t *buf, uint8_t report_id, uint8_t nb_bytes)
{
DWORD bytesRead = 0;
static uint8_t tmp_buf[128];
if (buf == NULL) return -1;
tmp_buf[0] = report_id;
ReadFile(device_handle, tmp_buf, nb_bytes*2, &bytesRead, NULL);
// bytesRead should either be nb_bytes*2 (if it successfully read 2 reports) or nb_bytes (only one)
if ( bytesRead != nb_bytes*2 && bytesRead != nb_bytes )
{
#ifdef DEBUG
printf("HID read error (expected %u (or twice that), but got %lu bytes)\n",nb_bytes, bytesRead);
#endif
return -1;
}
/* HID read ok, copy latest report bytes */
memcpy(buf, tmp_buf + bytesRead - nb_bytes, nb_bytes);
return 0;
}
static uint8_t mai2_opbtn;
static uint16_t mai2_player1_btn;
static uint16_t mai2_player2_btn;
static struct mai2_io_config mai2_io_cfg;
static bool mai2_io_coin;
uint16_t mai2_io_get_api_version(void)
{
return 0x0100;
}
static HANDLE joy_handle;
HRESULT mai2_io_init(void)
{
mai2_io_config_load(&mai2_io_cfg, L".\\segatools.ini");
if (hid_open_device(&joy_handle, 0x0f0d, 0x0092, 0) != 0) {
return S_OK;
}
HidD_SetNumInputBuffers(joy_handle, 2);
return S_OK;
}
#pragma pack(1)
typedef struct joy_report_s {
uint8_t report_id;
uint16_t buttons; // 16 buttons; see JoystickButtons_t for bit mapping
uint8_t HAT; // HAT switch; one nibble w/ unused nibble
uint32_t axis;
uint8_t VendorSpec;
} joy_report_t;
joy_report_t joy_data;
HRESULT mai2_io_poll_(void)
{
mai2_opbtn = 0;
mai2_player1_btn = 0;
mai2_player2_btn = 0;
if (GetAsyncKeyState(mai2_io_cfg.vk_test) & 0x8000) {
mai2_opbtn |= MAI2_IO_OPBTN_TEST;
}
if (GetAsyncKeyState(mai2_io_cfg.vk_service) & 0x8000) {
mai2_opbtn |= MAI2_IO_OPBTN_SERVICE;
}
if (GetAsyncKeyState(mai2_io_cfg.vk_coin) & 0x8000) {
if (!mai2_io_coin) {
mai2_io_coin = true;
mai2_opbtn |= MAI2_IO_OPBTN_COIN;
}
} else {
mai2_io_coin = false;
}
//Player 1
if (GetAsyncKeyState(mai2_io_cfg.vk_1p_btn[0])) {
mai2_player1_btn |= MAI2_IO_GAMEBTN_1;
}
if (GetAsyncKeyState(mai2_io_cfg.vk_1p_btn[1])) {
mai2_player1_btn |= MAI2_IO_GAMEBTN_2;
}
if (GetAsyncKeyState(mai2_io_cfg.vk_1p_btn[2])) {
mai2_player1_btn |= MAI2_IO_GAMEBTN_3;
}
if (GetAsyncKeyState(mai2_io_cfg.vk_1p_btn[3])) {
mai2_player1_btn |= MAI2_IO_GAMEBTN_4;
}
if (GetAsyncKeyState(mai2_io_cfg.vk_1p_btn[4])) {
mai2_player1_btn |= MAI2_IO_GAMEBTN_5;
}
if (GetAsyncKeyState(mai2_io_cfg.vk_1p_btn[5])) {
mai2_player1_btn |= MAI2_IO_GAMEBTN_6;
}
if (GetAsyncKeyState(mai2_io_cfg.vk_1p_btn[6])) {
mai2_player1_btn |= MAI2_IO_GAMEBTN_7;
}
if (GetAsyncKeyState(mai2_io_cfg.vk_1p_btn[7])) {
mai2_player1_btn |= MAI2_IO_GAMEBTN_8;
}
if (GetAsyncKeyState(mai2_io_cfg.vk_1p_btn[8])) {
mai2_player1_btn |= MAI2_IO_GAMEBTN_SELECT;
}
//Player 2
if (GetAsyncKeyState(mai2_io_cfg.vk_2p_btn[0])) {
mai2_player2_btn |= MAI2_IO_GAMEBTN_1;
}
if (GetAsyncKeyState(mai2_io_cfg.vk_2p_btn[1])) {
mai2_player2_btn |= MAI2_IO_GAMEBTN_2;
}
if (GetAsyncKeyState(mai2_io_cfg.vk_2p_btn[2])) {
mai2_player2_btn |= MAI2_IO_GAMEBTN_3;
}
if (GetAsyncKeyState(mai2_io_cfg.vk_2p_btn[3])) {
mai2_player2_btn |= MAI2_IO_GAMEBTN_4;
}
if (GetAsyncKeyState(mai2_io_cfg.vk_2p_btn[4])) {
mai2_player2_btn |= MAI2_IO_GAMEBTN_5;
}
if (GetAsyncKeyState(mai2_io_cfg.vk_2p_btn[5])) {
mai2_player2_btn |= MAI2_IO_GAMEBTN_6;
}
if (GetAsyncKeyState(mai2_io_cfg.vk_2p_btn[6])) {
mai2_player2_btn |= MAI2_IO_GAMEBTN_7;
}
if (GetAsyncKeyState(mai2_io_cfg.vk_2p_btn[7])) {
mai2_player2_btn |= MAI2_IO_GAMEBTN_8;
}
if (GetAsyncKeyState(mai2_io_cfg.vk_2p_btn[8])) {
mai2_player2_btn |= MAI2_IO_GAMEBTN_SELECT;
}
return S_OK;
}
HRESULT mai2_io_poll(void)
{
mai2_opbtn = 0;
mai2_player1_btn = 0;
mai2_player2_btn = 0;
if (GetAsyncKeyState(mai2_io_cfg.vk_test) & 0x8000) {
mai2_opbtn |= MAI2_IO_OPBTN_TEST;
}
if (GetAsyncKeyState(mai2_io_cfg.vk_service) & 0x8000) {
mai2_opbtn |= MAI2_IO_OPBTN_SERVICE;
}
if (GetAsyncKeyState(mai2_io_cfg.vk_coin) & 0x8000) {
if (!mai2_io_coin) {
mai2_io_coin = true;
mai2_opbtn |= MAI2_IO_OPBTN_COIN;
}
} else {
mai2_io_coin = false;
}
hid_get_report(joy_handle, (uint8_t *)&joy_data, 0x01, sizeof(joy_data));
mai2_player1_btn = joy_data.buttons;
return S_OK;
}
void mai2_io_get_opbtns(uint8_t *opbtn)
{
if (opbtn != NULL) {
*opbtn = mai2_opbtn;
}
}
void mai2_io_get_gamebtns(uint16_t *player1, uint16_t *player2)
{
if (player1 != NULL) {
*player1 = mai2_player1_btn;
}
if (player2 != NULL ){
*player2 = mai2_player2_btn;
}
}

View File

@ -0,0 +1,8 @@
LIBRARY mai2io
EXPORTS
mai2_io_get_api_version
mai2_io_init
mai2_io_poll
mai2_io_get_opbtns
mai2_io_get_gamebtns

View File

@ -0,0 +1,68 @@
#pragma once
#include <windows.h>
#include <stdint.h>
enum {
MAI2_IO_OPBTN_TEST = 0x01,
MAI2_IO_OPBTN_SERVICE = 0x02,
MAI2_IO_OPBTN_COIN = 0x04,
};
enum {
MAI2_IO_GAMEBTN_1 = 0x01,
MAI2_IO_GAMEBTN_2 = 0x02,
MAI2_IO_GAMEBTN_3 = 0x04,
MAI2_IO_GAMEBTN_4 = 0x08,
MAI2_IO_GAMEBTN_5 = 0x10,
MAI2_IO_GAMEBTN_6 = 0x20,
MAI2_IO_GAMEBTN_7 = 0x40,
MAI2_IO_GAMEBTN_8 = 0x80,
MAI2_IO_GAMEBTN_SELECT = 0x100,
};
/* Get the version of the Maimai 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 mai2_io_get_api_version(void);
/* Initialize the IO DLL. This is the second function that will be called on
your DLL, after mai2_io_get_api_version.
All subsequent calls to this API may originate from arbitrary threads.
Minimum API version: 0x0100 */
HRESULT mai2_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 mai2_io_poll(void);
/* Get the state of the cabinet's operator buttons as of the last poll. See
MAI2_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 mai2_io_get_opbtns(uint8_t *opbtn);
/* Get the state of the cabinet's gameplay buttons as of the last poll. See
MAI2_IO_GAMEBTN enum above for bit mask definitions. Inputs are split into
a left hand side set of inputs and a right hand side set of inputs: the bit
mappings are the same in both cases.
All buttons are active-high, even though some buttons' electrical signals
on a real cabinet are active-low.
Minimum API version: 0x0100 */
void mai2_io_get_gamebtns(uint16_t *player1, uint16_t *player2);

View File

@ -0,0 +1,53 @@
project('mai2io_dll', 'c', version: '0.1.0')
add_project_arguments(
'-DCOBJMACROS',
'-DDIRECTINPUT_VERSION=0x0800',
'-DWIN32_LEAN_AND_MEAN',
'-D_WIN32_WINNT=_WIN32_WINNT_WIN7',
'-DMINGW_HAS_SECURE_API=1',
language: 'c',
)
# Use get_argument_syntax() instead once Meson 0.49.0 releases
if meson.get_compiler('c').get_id() != 'msvc'
add_project_arguments(
'-ffunction-sections',
'-fdata-sections',
language: 'c',
)
add_project_link_arguments(
'-Wl,--enable-stdcall-fixup',
'-Wl,--exclude-all-symbols',
'-Wl,--gc-sections',
'-static-libgcc',
language: 'c',
)
endif
cc = meson.get_compiler('c')
hid_lib = cc.find_library('hid')
setupapi_lib = cc.find_library('setupapi')
inc = include_directories('.')
mai2io_lib = shared_library(
'mai2io',
name_prefix : '',
include_directories : [inc, '../'],
vs_module_defs : 'mai2io.def',
implicit_include_directories : false,
dependencies : [
hid_lib,
setupapi_lib,
],
sources : [
'mai2io.c',
'mai2io.h',
'config.c',
'config.h',
],
)

View File

@ -50,7 +50,11 @@ void report_usb_hid()
if (tud_hid_ready()) {
hid_joy.HAT = 0;
hid_joy.VendorSpec = 0;
if (mai_cfg->hid.nkro &&
if (mai_cfg->hid.joy) {
hid_joy.buttons = button_read();
tud_hid_n_report(0x00, REPORT_ID_JOYSTICK, &hid_joy, sizeof(hid_joy));
}
if (mai_cfg->hid.nkro &&
(memcmp(&hid_nkro, &sent_hid_nkro, sizeof(hid_nkro)) != 0)) {
sent_hid_nkro = hid_nkro;
tud_hid_n_report(0x02, 0, &sent_hid_nkro, sizeof(sent_hid_nkro));
@ -84,7 +88,7 @@ static void run_lights()
return;
}
if (now - io_last_io_time() < 5000000) {
if (now - io_last_io_time() < 60000000) {
return;
}
@ -118,6 +122,8 @@ static void core1_loop()
static void core0_loop()
{
static uint64_t next_frame = 0;
while(1) {
tud_task();
io_update();
@ -126,6 +132,9 @@ static void core0_loop()
save_loop();
cli_fps_count(0);
sleep_until(next_frame);
next_frame = time_us_64() + 1000; // 1KHz
touch_update();
button_update();