Add setting to only allow game input in foreground (#83)

This adds a setting to the global configuration to allow only input when the game is focused. This prevents issues, such as triggering game buttons or service buttons while being tabbed out. Turned off by default.

The implementation is not the best, but in the context how these games are structured, the most acceptable.
It will keep scanning if the foreground window title fully matches (some games partially match, like FGO because due to the revision number in the title), and if found, will grab the HWND of that window and compare that from then on, to not tank performance due to constant string operations.

Tested with FGO, chusan and ongeki.

Reviewed-on: https://gitea.tendokyu.moe/TeamTofuShop/segatools/pulls/83
Reviewed-by: Dniel97 <dniel97@noreply.gitea.tendokyu.moe>
Co-authored-by: kyoubate-haruka <46010460+kyoubate-haruka@users.noreply.github.com>
Co-committed-by: kyoubate-haruka <46010460+kyoubate-haruka@users.noreply.github.com>
This commit is contained in:
kyoubate-haruka
2025-11-12 22:33:32 +00:00
committed by Dniel97
parent af8d0bab7d
commit b86049034f
28 changed files with 276 additions and 128 deletions

View File

@@ -73,6 +73,7 @@ void jvs_config_load(struct jvs_config *cfg, const wchar_t *filename)
assert(filename != NULL);
cfg->enable = GetPrivateProfileIntW(L"jvs", L"enable", 1, filename);
cfg->foreground = GetPrivateProfileIntW(L"jvs", L"foreground", 0, filename);
}
void sram_config_load(struct sram_config *cfg, const wchar_t *filename)

View File

@@ -15,6 +15,7 @@ DEFINE_GUID(
struct jvs_config {
bool enable;
bool foreground;
};
typedef HRESULT (*jvs_provider_t)(struct jvs_node **root);

View File

@@ -92,6 +92,7 @@ void io4_config_load(struct io4_config *cfg, const wchar_t *filename)
assert(filename != NULL);
cfg->enable = GetPrivateProfileIntW(L"io4", L"enable", 1, filename);
cfg->foreground_only = GetPrivateProfileIntW(L"io4", L"foreground", 0, filename);
}
void vfd_config_load(struct vfd_config *cfg, const wchar_t *filename)

View File

@@ -26,6 +26,7 @@
#include "util/dprintf.h"
#include "util/dump.h"
#include "util/fg-detect.h"
static void io3_transact(
struct jvs_node *node,
@@ -146,6 +147,8 @@ static uint8_t io3_features[] = {
0x00,
};
static struct io3_switch_state prev_state = {0};
void io3_init(
struct io3 *io3,
struct jvs_node *next,
@@ -423,7 +426,13 @@ static HRESULT io3_cmd_read_switches(
memset(&state, 0, sizeof(state));
if (io3->ops != NULL) {
io3->ops->read_switches(io3->ops_ctx, &state);
fgdet_poll();
if (fgdet_in_foreground()) { // returns true if fgdet is not enabled
io3->ops->read_switches(io3->ops_ctx, &state);
memcpy(&prev_state, &state, sizeof(state));
} else {
state = prev_state;
}
}
hr = iobuf_write_8(resp_buf, state.system); /* Test, Tilt lines */

View File

@@ -20,6 +20,7 @@
#include "util/async.h"
#include "util/dprintf.h"
#include "util/fg-detect.h"
#pragma pack(push, 1)
@@ -99,12 +100,14 @@ static struct async io4_async;
static uint8_t io4_system_status;
static const struct io4_ops *io4_ops;
static void *io4_ops_ctx;
static struct io4_state prev_state;
HRESULT io4_hook_init(
const struct io4_config *cfg,
const struct io4_ops *ops,
void *ctx)
{
const struct io4_config *cfg,
const struct io4_ops *ops,
void *ctx,
const wchar_t* window_name,
const bool window_name_is_partial_match) {
HRESULT hr;
assert(cfg != NULL);
@@ -122,9 +125,14 @@ HRESULT io4_hook_init(
return hr;
}
if (window_name != NULL && cfg->foreground_only) {
fgdet_init(window_name, window_name_is_partial_match);
}
io4_ops = ops;
io4_ops_ctx = ctx;
io4_system_status = 0x02; /* idk */
memset(&prev_state, 0, sizeof(prev_state));
iohook_push_handler(io4_handle_irp);
hr = setupapi_add_phantom_dev(&hid_guid, io4_path);
@@ -325,11 +333,18 @@ static HRESULT io4_async_poll(void *ctx, struct irp *irp)
/* Call into ops to poll the underlying inputs */
memset(&state, 0, sizeof(state));
hr = io4_ops->poll(io4_ops_ctx, &state);
fgdet_poll();
if (fgdet_in_foreground()) { // returns true if fgdet is not enabled
memset(&state, 0, sizeof(state));
hr = io4_ops->poll(io4_ops_ctx, &state);
if (FAILED(hr)) {
return hr;
if (FAILED(hr)) {
return hr;
}
memcpy(&prev_state, &state, sizeof(state));
} else {
state = prev_state; // if we're unfocused, freeze the current input
}
/* Construct IN report. Values are all little-endian, unlike JVS. */

View File

@@ -10,12 +10,13 @@
enum {
/* System buttons in button[0] */
IO4_BUTTON_TEST = 1 << 9,
IO4_BUTTON_SERVICE = 1 << 6,
IO4_BUTTON_TEST = 1 << 9,
IO4_BUTTON_SERVICE = 1 << 6,
};
struct io4_config {
bool enable;
bool foreground_only;
};
struct io4_state {
@@ -26,11 +27,14 @@ struct io4_state {
};
struct io4_ops {
HRESULT (*poll)(void *ctx, struct io4_state *state);
HRESULT (*poll)(void* ctx, struct io4_state* state);
HRESULT (*write_gpio)(uint8_t* payload, size_t len);
};
HRESULT io4_hook_init(
const struct io4_config *cfg,
const struct io4_ops *ops,
void *ctx);
const struct io4_config* cfg,
const struct io4_ops* ops,
void* ctx,
const wchar_t* window_name,
const bool window_name_is_partial_match);

66
common/util/fg-detect.c Normal file
View File

@@ -0,0 +1,66 @@
#include <stdbool.h>
#include <windows.h>
#include "util/dprintf.h"
#include "util/fg-detect.h"
#include <assert.h>
static HWND window_handle;
static const wchar_t* window_title = NULL;
static bool foreground_state = true;
static bool partial_match;
static wchar_t scanned_title[256];
static HWND get_window(){
if (window_handle != NULL){
return window_handle;
}
// try detecting the window
HWND hwnd = GetForegroundWindow();
if (GetWindowTextW(hwnd, scanned_title, sizeof(scanned_title)) == 0) {
return NULL;
}
if (partial_match) {
if (wcsstr(scanned_title, window_title) == NULL) {
return NULL;
}
} else {
if (wcscmp(scanned_title, window_title) != 0) {
return NULL;
}
}
dprintf("FG-Detect: Program window detected\n");
window_handle = hwnd;
return window_handle;
}
void fgdet_init(const wchar_t* wnd_title, const bool wnd_partial_match) {
assert(wnd_title != NULL);
window_handle = NULL;
window_title = wnd_title;
partial_match = wnd_partial_match;
dprintf("FG-Detect: Searching for \"%ls\"\n", window_title);
}
bool fgdet_in_foreground(){
return foreground_state;
}
void fgdet_poll(){
if (window_title == NULL){
return;
} else if (GetForegroundWindow() == get_window()){
if (!foreground_state){
dprintf("FG-Detect: Got focus\n");
foreground_state = true;
}
} else {
if (foreground_state){
dprintf("FG-Detect: Lost focus\n");
foreground_state = false;
}
}
}

8
common/util/fg-detect.h Normal file
View File

@@ -0,0 +1,8 @@
#pragma once
#include <stdbool.h>
void fgdet_init(const wchar_t* wnd_title, const bool wnd_partial_match);
bool fgdet_in_foreground();
void fgdet_poll();

View File

@@ -17,6 +17,8 @@ util_lib = static_library(
'dprintf.h',
'dump.c',
'dump.h',
'fg-detect.c',
'fg-detect.h',
'get_function_ordinal.c',
'get_function_ordinal.h',
'lib.c',

View File

@@ -409,6 +409,12 @@ Default `1`
Enable JVS port emulation. Disable to use the JVS port on a real AMEX.
### `foreground`
Default `0`
Only enables input when the game's main window is focused. This does not work for all games.
## `[io4]`
Configure emulation of the IO4 board. Same settings also apply to `[io3]`.
@@ -419,6 +425,13 @@ Default `1`
Enable IO4 port emulation. Disable to use the IO4 port on a real ALLS.
### `foreground`
Default `0`
Only enables input when the game's main window is focused. This does not work for all games, notably APMv3, due to the game switching involved.
### `test`
Default `0x31`

View File

@@ -23,7 +23,7 @@ HRESULT apm3_io4_hook_init(const struct io4_config *cfg)
assert(apm3_dll.init != NULL);
hr = io4_hook_init(cfg, &apm3_io4_ops, NULL);
hr = io4_hook_init(cfg, &apm3_io4_ops, NULL, NULL, false); // can't use fgdet here because the main window closes
if (FAILED(hr)) {
return hr;

View File

@@ -37,6 +37,7 @@
#include "util/dprintf.h"
#include "util/env.h"
#include "util/fg-detect.h"
static HMODULE chuni_hook_mod;
static process_entry_t chuni_startup;
@@ -129,6 +130,10 @@ static DWORD CALLBACK chuni_pre_startup(void)
goto fail;
}
if (chuni_hook_cfg.amex.jvs.enable && chuni_hook_cfg.amex.jvs.foreground) {
fgdet_init(L"teaGfx DirectX Release", false);
}
/* Initialize debug helpers */
spike_hook_init(get_config_path());

View File

@@ -16,32 +16,32 @@ struct chunithm_jvs_ir_mask {
// 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 },
{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 },
{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 chusan_io4_hook_init(const struct io4_config* cfg) {
HRESULT hr;
assert(chuni_dll.jvs_init != NULL);
@@ -50,19 +50,18 @@ HRESULT chusan_io4_hook_init(const struct io4_config* cfg)
hr = chuni_dll.jvs_init();
if (FAILED(hr)) {
dprintf("USB I/O: Backend error, I/O disconnected: %x\n", (int)hr);
dprintf("USB I/O: Backend error, I/O disconnected: %x\n", (int) hr);
return hr;
}
io4_hook_init(cfg, &chusan_io4_ops, NULL);
io4_hook_init(cfg, &chusan_io4_ops, NULL, L"teaGfx DirectX Release", false);
return S_OK;
}
static HRESULT chusan_io4_poll(void* ctx, struct io4_state* state)
{
const struct chunithm_jvs_ir_mask *masks;
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;
@@ -103,4 +102,4 @@ static HRESULT chusan_io4_poll(void* ctx, struct io4_state* state)
}
return S_OK;
}
}

View File

@@ -10,20 +10,20 @@
#include "util/dprintf.h"
static HRESULT cm_io4_poll(void *ctx, struct io4_state *state);
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 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);
hr = io4_hook_init(cfg, &cm_io4_ops, NULL, L"MU3CardViewer", false);
if (FAILED(hr)) {
return hr;
@@ -32,8 +32,7 @@ HRESULT cm_io4_hook_init(const struct io4_config *cfg)
return cm_dll.init();
}
static HRESULT cm_io4_poll(void *ctx, struct io4_state *state)
{
static HRESULT cm_io4_poll(void* ctx, struct io4_state* state) {
uint8_t opbtn;
HRESULT hr;
@@ -66,4 +65,4 @@ static HRESULT cm_io4_poll(void *ctx, struct io4_state *state)
state->chutes[0] = coins << 8;
return S_OK;
}
}

View File

@@ -42,6 +42,7 @@ void revio_config_load(struct revio_config *cfg, const wchar_t *filename)
assert(filename != NULL);
cfg->enable = GetPrivateProfileIntW(L"revio", L"enable", 1, filename);
cfg->foreground = GetPrivateProfileIntW(L"revio", L"foreground", 0, filename);
}
void led_config_load(struct led_config *cfg, const wchar_t *filename)

View File

@@ -24,6 +24,7 @@
#include "util/dprintf.h"
#include "util/env.h"
#include "util/fg-detect.h"
static HMODULE cxb_hook_mod;
static process_entry_t cxb_startup;
@@ -103,6 +104,10 @@ static DWORD CALLBACK cxb_pre_startup(void)
goto fail;
}
if (cxb_hook_cfg.revio.enable && cxb_hook_cfg.revio.foreground) {
fgdet_init(L"MT Framework", false);
}
hr = led_hook_init(&cxb_hook_cfg.led);
if (FAILED(hr)) {

View File

@@ -14,6 +14,7 @@
#include "hook/table.h"
#include "util/dprintf.h"
#include "util/fg-detect.h"
static int my_cCommIo_Open(char *port);
static int my_cCommIo_Close();
@@ -31,6 +32,7 @@ static int my_cCommIo_SetAmpMute(int amp_id, int a2);
int amp_volume[] = {20, 20, 20};
int last_triggers = 0;
int last_is_mouse_down = false;
int prev_out = 0;
static struct hook_symbol revio_syms[] = {
{
@@ -139,26 +141,32 @@ static int my_cCommIo_GetTrigger()
uint16_t btns = 0;
int out = 0;
cxb_dll.revio_poll(&btns);
fgdet_poll();
if (fgdet_in_foreground()) {
cxb_dll.revio_poll(&btns);
if (btns & 0x01) {
out |= 1 << 4; // test
}
if (btns & 0x01) {
out |= 1 << 4; // test
}
if (btns & 0x02) {
out |= 1 << 5; // service?
}
if (btns & 0x02) {
out |= 1 << 5; // service?
}
if (btns & 0x04) {
out |= 1 << 1; // up
}
if (btns & 0x04) {
out |= 1 << 1; // up
}
if (btns & 0x08) {
out |= 1 << 3; // down
}
if (btns & 0x08) {
out |= 1 << 3; // down
}
if (btns & 0x0F) {
out |= 1 << 2; // cancel
if (btns & 0x0F) {
out |= 1 << 2; // cancel
}
prev_out = out;
} else {
out = prev_out;
}
out &= ~last_triggers;

View File

@@ -6,6 +6,7 @@
struct revio_config {
bool enable;
bool foreground;
uint8_t test;
uint8_t service;
uint8_t coin;

View File

@@ -34,6 +34,7 @@
#include "util/dprintf.h"
#include "util/env.h"
#include "util/fg-detect.h"
static HMODULE diva_hook_mod;
static process_entry_t diva_startup;
@@ -104,6 +105,10 @@ static DWORD CALLBACK diva_pre_startup(void)
goto fail;
}
if (diva_hook_cfg.amex.jvs.enable && diva_hook_cfg.amex.jvs.foreground) {
fgdet_init(L"Hatsune Miku Project DIVA Arcade Future Tone", false);
}
/* Initialize debug helpers */
spike_hook_init(get_config_path());

View File

@@ -10,20 +10,20 @@
#include "util/dprintf.h"
static HRESULT fgo_io4_poll(void *ctx, struct io4_state *state);
static HRESULT fgo_io4_poll(void* ctx, struct io4_state* state);
static uint16_t coins;
static const struct io4_ops fgo_io4_ops = {
.poll = fgo_io4_poll,
};
HRESULT fgo_io4_hook_init(const struct io4_config *cfg)
{
HRESULT fgo_io4_hook_init(const struct io4_config* cfg) {
HRESULT hr;
assert(fgo_dll.init != NULL);
hr = io4_hook_init(cfg, &fgo_io4_ops, NULL);
hr = io4_hook_init(cfg, &fgo_io4_ops, NULL, L"Fate/Grand Order Arcade r", true);
if (FAILED(hr)) {
return hr;
@@ -32,8 +32,7 @@ HRESULT fgo_io4_hook_init(const struct io4_config *cfg)
return fgo_dll.init();
}
static HRESULT fgo_io4_poll(void *ctx, struct io4_state *state)
{
static HRESULT fgo_io4_poll(void* ctx, struct io4_state* state) {
uint8_t opbtn;
uint8_t gamebtn;
int16_t stick_x;
@@ -101,4 +100,4 @@ static HRESULT fgo_io4_poll(void *ctx, struct io4_state *state)
state->adcs[4] = 0x8000 + stick_y;
return S_OK;
}
}

View File

@@ -10,12 +10,14 @@
#include "util/dprintf.h"
static HRESULT idac_io4_poll(void *ctx, struct io4_state *state);
static HRESULT idac_io4_poll(void* ctx, struct io4_state* state);
static HRESULT idac_io4_write_gpio(uint8_t* payload, size_t len);
static uint16_t coins;
static const struct io4_ops idac_io4_ops = {
.poll = idac_io4_poll,
.poll = idac_io4_poll,
.write_gpio = idac_io4_write_gpio
};
@@ -36,13 +38,12 @@ static const uint16_t idac_gear_signals[] = {
0x0014,
};
HRESULT idac_io4_hook_init(const struct io4_config *cfg)
{
HRESULT idac_io4_hook_init(const struct io4_config* cfg) {
HRESULT hr;
assert(idac_dll.init != NULL);
hr = io4_hook_init(cfg, &idac_io4_ops, NULL);
hr = io4_hook_init(cfg, &idac_io4_ops, NULL, L"GameProject", false);
if (FAILED(hr)) {
return hr;
@@ -51,8 +52,7 @@ HRESULT idac_io4_hook_init(const struct io4_config *cfg)
return idac_dll.init();
}
static HRESULT idac_io4_poll(void *ctx, struct io4_state *state)
{
static HRESULT idac_io4_poll(void* ctx, struct io4_state* state) {
uint8_t opbtn;
uint8_t gamebtn;
uint8_t gear;
@@ -131,21 +131,20 @@ static HRESULT idac_io4_poll(void *ctx, struct io4_state *state)
return S_OK;
}
static HRESULT idac_io4_write_gpio(uint8_t* payload, size_t len)
{
static HRESULT idac_io4_write_gpio(uint8_t* payload, size_t len) {
assert(idac_dll.led_set_leds != NULL);
// Just fast fail if there aren't enough bytes in the payload
if (len < 3)
if (len < 3)
return S_OK;
// This command is used for lights in IDAC, but it only contains button lights,
// and only in the first 3 bytes of the payload; everything else is padding to
// make the payload 62 bytes. The rest of the cabinet lights and the side button
// lights are handled separately, by the 15070 lights controller.
uint32_t lights_data = (uint32_t) ((uint8_t)(payload[0]) << 24 |
(uint8_t)(payload[1]) << 16 |
(uint8_t)(payload[2]) << 8);
uint32_t lights_data = (uint32_t)((uint8_t)(payload[0]) << 24 |
(uint8_t)(payload[1]) << 16 |
(uint8_t)(payload[2]) << 8);
// Since Sega uses an odd ordering for the first part of the bitfield,
// let's normalize the data and just send over bytes for the receiver
@@ -162,4 +161,4 @@ static HRESULT idac_io4_write_gpio(uint8_t* payload, size_t len)
idac_dll.led_set_leds(0, rgb_out);
return S_OK;
}
}

View File

@@ -41,6 +41,7 @@
#include "util/dprintf.h"
#include "util/lib.h"
#include "util/env.h"
#include "util/fg-detect.h"
static HMODULE idz_hook_mod;
static process_entry_t idz_startup;
@@ -136,6 +137,10 @@ static DWORD CALLBACK idz_pre_startup(void)
goto fail;
}
if (idz_hook_cfg.amex.jvs.enable && idz_hook_cfg.amex.jvs.foreground) {
fgdet_init(L"Direct3D Window", false);
}
/* Initialize debug helpers */
spike_hook_init(get_config_path());

View File

@@ -18,6 +18,7 @@
#include "unityhook/hook.h"
#include "util/dprintf.h"
#include "util/env.h"
#include "util/fg-detect.h"
static HMODULE kemono_hook_mod;
static process_entry_t kemono_startup;
@@ -103,6 +104,10 @@ static DWORD CALLBACK kemono_pre_startup(void) {
goto fail;
}
if (kemono_hook_cfg.amex.jvs.enable && kemono_hook_cfg.amex.jvs.foreground) {
fgdet_init(L"Parade", false);
}
kemono_extra_hooks_init();
/* Initialize Unity native plugin DLL hooks

View File

@@ -10,20 +10,20 @@
#include "util/dprintf.h"
static HRESULT mai2_io4_poll(void *ctx, struct io4_state *state);
static HRESULT mai2_io4_poll(void* ctx, struct io4_state* state);
static uint16_t coins;
static const struct io4_ops mai2_io4_ops = {
.poll = mai2_io4_poll,
};
HRESULT mai2_io4_hook_init(const struct io4_config *cfg)
{
HRESULT mai2_io4_hook_init(const struct io4_config* cfg) {
HRESULT hr;
assert(mai2_dll.init != NULL);
hr = io4_hook_init(cfg, &mai2_io4_ops, NULL);
hr = io4_hook_init(cfg, &mai2_io4_ops, NULL, L"Sinmai", false);
if (FAILED(hr)) {
return hr;
@@ -32,8 +32,7 @@ HRESULT mai2_io4_hook_init(const struct io4_config *cfg)
return mai2_dll.init();
}
static HRESULT mai2_io4_poll(void *ctx, struct io4_state *state)
{
static HRESULT mai2_io4_poll(void* ctx, struct io4_state* state) {
uint8_t opbtn;
uint16_t player1;
uint16_t player2;
@@ -150,4 +149,4 @@ static HRESULT mai2_io4_poll(void *ctx, struct io4_state *state)
}
return S_OK;
}
}

View File

@@ -14,19 +14,18 @@
bool mercury_io_coin = false;
uint16_t mercury_io_coins = 0;
static HRESULT mercury_io4_poll(void *ctx, struct io4_state *state);
static HRESULT mercury_io4_poll(void* ctx, struct io4_state* state);
static const struct io4_ops mercury_io4_ops = {
.poll = mercury_io4_poll,
};
HRESULT mercury_io4_hook_init(const struct io4_config *cfg)
{
HRESULT mercury_io4_hook_init(const struct io4_config* cfg) {
HRESULT hr;
assert(mercury_dll.init != NULL);
hr = io4_hook_init(cfg, &mercury_io4_ops, NULL);
hr = io4_hook_init(cfg, &mercury_io4_ops, NULL, L"Mercury", false);
if (FAILED(hr)) {
return hr;
@@ -35,8 +34,7 @@ HRESULT mercury_io4_hook_init(const struct io4_config *cfg)
return mercury_dll.init();
}
static HRESULT mercury_io4_poll(void *ctx, struct io4_state *state)
{
static HRESULT mercury_io4_poll(void* ctx, struct io4_state* state) {
uint8_t opbtn;
uint8_t gamebtn;
HRESULT hr;
@@ -72,8 +70,7 @@ static HRESULT mercury_io4_poll(void *ctx, struct io4_state *state)
mercury_io_coin = true;
mercury_io_coins++;
}
}
else {
} else {
mercury_io_coin = false;
}
@@ -88,4 +85,4 @@ static HRESULT mercury_io4_poll(void *ctx, struct io4_state *state)
}
return S_OK;
}
}

View File

@@ -10,7 +10,8 @@
#include "util/dprintf.h"
static HRESULT mu3_io4_poll(void *ctx, struct io4_state *state);
static HRESULT mu3_io4_poll(void* ctx, struct io4_state* state);
static HRESULT mu3_io4_write_gpio(uint8_t* payload, size_t len);
static uint16_t coins;
@@ -20,13 +21,12 @@ static const struct io4_ops mu3_io4_ops = {
.write_gpio = mu3_io4_write_gpio,
};
HRESULT mu3_io4_hook_init(const struct io4_config *cfg)
{
HRESULT mu3_io4_hook_init(const struct io4_config* cfg) {
HRESULT hr;
assert(mu3_dll.init != NULL);
hr = io4_hook_init(cfg, &mu3_io4_ops, NULL);
hr = io4_hook_init(cfg, &mu3_io4_ops, NULL, L"Otoge", false);
if (FAILED(hr)) {
return hr;
@@ -35,8 +35,7 @@ HRESULT mu3_io4_hook_init(const struct io4_config *cfg)
return mu3_dll.init();
}
static HRESULT mu3_io4_poll(void *ctx, struct io4_state *state)
{
static HRESULT mu3_io4_poll(void* ctx, struct io4_state* state) {
uint8_t opbtn;
uint8_t left;
uint8_t right;
@@ -111,11 +110,11 @@ static HRESULT mu3_io4_poll(void *ctx, struct io4_state *state)
}
if (!(left & MU3_IO_GAMEBTN_SIDE)) {
state->buttons[1] |= 1 << 15; /* L-Side, active-low */
state->buttons[1] |= 1 << 15; /* L-Side, active-low */
}
if (!(right & MU3_IO_GAMEBTN_SIDE)) {
state->buttons[0] |= 1 << 14; /* R-Side, active-low */
state->buttons[0] |= 1 << 14; /* R-Side, active-low */
}
/* Lever increases right-to-left, not left-to-right.
@@ -128,19 +127,18 @@ static HRESULT mu3_io4_poll(void *ctx, struct io4_state *state)
return S_OK;
}
static HRESULT mu3_io4_write_gpio(uint8_t* payload, size_t len)
{
static HRESULT mu3_io4_write_gpio(uint8_t* payload, size_t len) {
// Just fast fail if there aren't enough bytes in the payload
if (len < 3)
if (len < 3)
return S_OK;
// This command is used for lights in Ongeki, but it only contains button lights,
// and only in the first 3 bytes of the payload; everything else is padding to
// make the payload 62 bytes. The rest of the cabinet lights and the side button
// lights are handled separately, by the 15093 lights controller.
uint32_t lights_data = (uint32_t) ((uint8_t)(payload[0]) << 24 |
(uint8_t)(payload[1]) << 16 |
(uint8_t)(payload[2]) << 8);
uint32_t lights_data = (uint32_t)((uint8_t)(payload[0]) << 24 |
(uint8_t)(payload[1]) << 16 |
(uint8_t)(payload[2]) << 8);
// Since Sega uses an odd ordering for the first part of the bitfield,
// let's normalize the data and just send over bytes for the receiver
@@ -169,4 +167,4 @@ static HRESULT mu3_io4_write_gpio(uint8_t* payload, size_t len)
mu3_dll.led_set_leds(1, rgb_out);
return S_OK;
}
}

View File

@@ -11,24 +11,28 @@
#include "swdchook/swdc-dll.h"
static HANDLE mmf;
static HRESULT init_mmf(void);
static void swdc_set_gamebtns(uint16_t value);
static HRESULT swdc_io4_poll(void *ctx, struct io4_state *state);
static HRESULT swdc_io4_poll(void* ctx, struct io4_state* state);
static HRESULT swdc_io4_write_gpio(uint8_t* payload, size_t len);
static uint16_t coins;
static const struct io4_ops swdc_io4_ops = {
.poll = swdc_io4_poll,
.poll = swdc_io4_poll,
.write_gpio = swdc_io4_write_gpio
};
HRESULT swdc_io4_hook_init(const struct io4_config *cfg) {
HRESULT swdc_io4_hook_init(const struct io4_config* cfg) {
HRESULT hr;
assert(swdc_dll.init != NULL);
hr = io4_hook_init(cfg, &swdc_io4_ops, NULL);
hr = io4_hook_init(cfg, &swdc_io4_ops, NULL, L"Todoroki", false);
if (FAILED(hr)) {
return hr;
@@ -61,7 +65,7 @@ void swdc_set_gamebtns(uint16_t value) {
// Update the memory-mapped file
LPVOID mmf_view = MapViewOfFile(mmf, FILE_MAP_ALL_ACCESS, 0, 0, 2);
if (mmf_view != NULL) {
uint16_t* ptr = (uint16_t*)mmf_view;
uint16_t* ptr = (uint16_t *) mmf_view;
*ptr = value;
UnmapViewOfFile(mmf_view);
@@ -70,7 +74,7 @@ void swdc_set_gamebtns(uint16_t value) {
// ReleaseMutex(mutex);
}
static HRESULT swdc_io4_poll(void *ctx, struct io4_state *state) {
static HRESULT swdc_io4_poll(void* ctx, struct io4_state* state) {
uint8_t opbtn;
uint16_t gamebtn;
struct swdc_io_analog_state analog_state;
@@ -128,7 +132,7 @@ static HRESULT swdc_io4_poll(void *ctx, struct io4_state *state) {
state->buttons[0] |= 1 << 2;
}
/*
/*
Update steering wheel buttons
Those are connected to the SEGA 838-15415 INDICATOR BD MAIN
@@ -175,21 +179,20 @@ static HRESULT swdc_io4_poll(void *ctx, struct io4_state *state) {
return S_OK;
}
static HRESULT swdc_io4_write_gpio(uint8_t* payload, size_t len)
{
static HRESULT swdc_io4_write_gpio(uint8_t* payload, size_t len) {
assert(swdc_dll.led_set_leds != NULL);
// Just fast fail if there aren't enough bytes in the payload
if (len < 3)
if (len < 3)
return S_OK;
// This command is used for lights in SWDC, but it only contains button lights,
// and only in the first 3 bytes of the payload; everything else is padding to
// make the payload 62 bytes. The rest of the cabinet lights and the side button
// lights are handled separately, by the 15070 lights controller.
uint32_t lights_data = (uint32_t) ((uint8_t)(payload[0]) << 24 |
(uint8_t)(payload[1]) << 16 |
(uint8_t)(payload[2]) << 8);
uint32_t lights_data = (uint32_t)((uint8_t)(payload[0]) << 24 |
(uint8_t)(payload[1]) << 16 |
(uint8_t)(payload[2]) << 8);
// Since Sega uses an odd ordering for the first part of the bitfield,
// let's normalize the data and just send over bytes for the receiver
@@ -206,4 +209,4 @@ static HRESULT swdc_io4_write_gpio(uint8_t* payload, size_t len)
swdc_dll.led_set_leds(0, rgb_out);
return S_OK;
}
}

View File

@@ -26,7 +26,7 @@ HRESULT tokyo_io4_hook_init(const struct io4_config *cfg)
assert(tokyo_dll.init != NULL);
hr = io4_hook_init(cfg, &tokyo_io4_ops, NULL);
hr = io4_hook_init(cfg, &tokyo_io4_ops, NULL, L"STAR", false);
if (FAILED(hr)) {
return hr;