io: fix bi2x_hook implementation in various games (#522)

## Link to GitHub Issue, if one exists
similar issue as #499

## Description of change
Fix broken bi2x_hook implementations that did not properly allocate I/O
buffers with correct sizes.

People keep copy-pasting other implementation that allocate 0 bytes of
memory, but we run the risk of memory corruption or/or game crashes if
we keep doing this.

We've seen some mystery crashes with CCJ for example, maybe this will
fix that.

While I'm here, implement SOCD cleaner for Polaris Chord.

## Testing
Tested I/O menu with CCJ, play through tutorial (didn't test matches)
Tested GWDelta (test menu and a few songs in guitar)
Tested PC (played a credit)
Tested QKS (still can't get past the title screen)
This commit is contained in:
bicarus
2026-01-18 18:06:15 -08:00
committed by GitHub
parent 1d6be8fe4b
commit 7684028f99
12 changed files with 127 additions and 17 deletions
+14
View File
@@ -1,5 +1,7 @@
#include "bi2x_hook.h" #include "bi2x_hook.h"
#if SPICE64
#include <cstdint> #include <cstdint>
#include "util/detour.h" #include "util/detour.h"
#include "util/logging.h" #include "util/logging.h"
@@ -16,15 +18,19 @@ namespace games::ccj {
*/ */
struct AIO_SCI_COMM { struct AIO_SCI_COMM {
uint8_t data[0xf8];
}; };
struct AIO_NMGR_IOB2 { struct AIO_NMGR_IOB2 {
uint8_t data[0xe30];
}; };
struct AIO_IOB2_BI2X_TBS { struct AIO_IOB2_BI2X_TBS {
uint8_t data[0x4680];
}; };
struct AIO_IOB2_BI2X_WRFIRM { struct AIO_IOB2_BI2X_WRFIRM {
uint8_t data[0x20f48];
}; };
struct AIO_NMGR_IOB__NODEINFO { struct AIO_NMGR_IOB__NODEINFO {
@@ -72,6 +78,12 @@ namespace games::ccj {
AIO_IOB2_BI2X_AC1__OUTPUTDATA OutputData; AIO_IOB2_BI2X_AC1__OUTPUTDATA OutputData;
}; };
// checked with UJK-001-2024060400
static_assert(sizeof(AIO_SCI_COMM) == 0xf8);
static_assert(sizeof(AIO_NMGR_IOB2) == 0xe30);
static_assert(sizeof(AIO_IOB2_BI2X_TBS) == 0x4680);
static_assert(sizeof(AIO_IOB2_BI2X_WRFIRM) == 0x20f48);
static void write_iccr_led(Lights::ccj_lights_t light, uint8_t value); static void write_iccr_led(Lights::ccj_lights_t light, uint8_t value);
/* /*
@@ -622,3 +634,5 @@ namespace games::ccj {
aioNodeCtl_IsError, &aioNodeCtl_IsError_orig); aioNodeCtl_IsError, &aioNodeCtl_IsError_orig);
} }
} }
#endif
+4
View File
@@ -1,5 +1,9 @@
#pragma once #pragma once
#if SPICE64
namespace games::ccj { namespace games::ccj {
void bi2x_hook_init(); void bi2x_hook_init();
} }
#endif
+4
View File
@@ -68,9 +68,13 @@ namespace games::ccj {
detour::trampoline_try("execexe.dll", MAKEINTRESOURCE(11), detour::trampoline_try("execexe.dll", MAKEINTRESOURCE(11),
(void*)execexe_CreateFileW_hook,(void**)&execexe_CreateFileW_orig); (void*)execexe_CreateFileW_hook,(void**)&execexe_CreateFileW_orig);
#if SPICE64
// insert BI2X hooks // insert BI2X hooks
bi2x_hook_init(); bi2x_hook_init();
#endif
// insert trackball hooks // insert trackball hooks
trackball_hook_init(); trackball_hook_init();
+18
View File
@@ -1,5 +1,7 @@
#include "bi2x_hook.h" #include "bi2x_hook.h"
#if SPICE64
#include <optional> #include <optional>
#include <cstdint> #include <cstdint>
#include "util/detour.h" #include "util/detour.h"
@@ -18,24 +20,29 @@ namespace games::gitadora {
// */ // */
struct AIO_NMGR_IOB2 { struct AIO_NMGR_IOB2 {
uint8_t data[0xe18];
}; };
struct AIO_IOB2_BI2X_AC1__SETTING { struct AIO_IOB2_BI2X_AC1__SETTING {
}; };
struct AIO_NMGR_IOB5 { struct AIO_NMGR_IOB5 {
uint8_t data[0xb10];
}; };
struct AIO_SCI_COMM { struct AIO_SCI_COMM {
uint8_t data[0x100];
}; };
struct AIO_IOB5_Y32D { struct AIO_IOB5_Y32D {
uint8_t data[0x2e8];
}; };
// struct AIO_IOB5_Y33I { // struct AIO_IOB5_Y33I {
// }; // };
struct AIO_IOB2_BI2X_AC1 { struct AIO_IOB2_BI2X_AC1 {
uint8_t data[0x4570];
}; };
struct AIO_NMGR_IOB__NODEINFO { struct AIO_NMGR_IOB__NODEINFO {
@@ -43,6 +50,7 @@ namespace games::gitadora {
}; };
struct AIO_IOB2_BI2X_WRFIRM { struct AIO_IOB2_BI2X_WRFIRM {
uint8_t data[0x20f48];
}; };
struct AIO_SCI_COMM_T { struct AIO_SCI_COMM_T {
@@ -159,6 +167,14 @@ namespace games::gitadora {
uint8_t unk[0xB1]; uint8_t unk[0xB1];
}; };
// verified with M32-007-2025092400
static_assert(sizeof(AIO_NMGR_IOB2) == 0xe18);
static_assert(sizeof(AIO_NMGR_IOB5) == 0xb10);
static_assert(sizeof(AIO_SCI_COMM) == 0x100);
static_assert(sizeof(AIO_IOB5_Y32D) == 0x2e8);
static_assert(sizeof(AIO_IOB2_BI2X_AC1) == 0x4570);
static_assert(sizeof(AIO_IOB2_BI2X_WRFIRM) == 0x20f48);
/* /*
* typedefs * typedefs
*/ */
@@ -702,3 +718,5 @@ namespace games::gitadora {
aioNodeCtl_UpdateDevicesStatus, &aioNodeCtl_UpdateDevicesStatus_orig); aioNodeCtl_UpdateDevicesStatus, &aioNodeCtl_UpdateDevicesStatus_orig);
} }
} }
#endif
+4
View File
@@ -1,5 +1,9 @@
#pragma once #pragma once
#if SPICE64
namespace games::gitadora { namespace games::gitadora {
void bi2x_hook_init(); void bi2x_hook_init();
} }
#endif
+38 -11
View File
@@ -1,5 +1,7 @@
#include "bi2x_hook.h" #include "bi2x_hook.h"
#if SPICE64
#include "util/execexe.h" #include "util/execexe.h"
#include "util/logging.h" #include "util/logging.h"
#include "rawinput/rawinput.h" #include "rawinput/rawinput.h"
@@ -9,9 +11,10 @@
#include "util/tapeled.h" #include "util/tapeled.h"
#include "pc.h" #include "pc.h"
#include "util/utils.h" #include "util/utils.h"
#include "util/time.h"
#include "util/socd_cleaner.h"
namespace games::pc { namespace games::pc {
bool PC_KNOB_MODE = false;
constexpr float RELATIVE_DECAY = 0.02f; constexpr float RELATIVE_DECAY = 0.02f;
struct FADER_RELATIVE { struct FADER_RELATIVE {
@@ -75,18 +78,22 @@ namespace games::pc {
*/ */
struct AIO_SCI_COMM { struct AIO_SCI_COMM {
uint8_t data[0xf8];
}; };
struct AIO_NMGR_IOB2 { struct AIO_NMGR_IOB2 {
uint8_t data[0xe30];
}; };
struct AIO_IOB2_BI2X_AC1 { struct AIO_IOB2_BI2X_AC1 {
uint8_t data[0x4538];
}; };
struct AIO_IOB2_BI2X_AC1__SETTING { struct AIO_IOB2_BI2X_AC1__SETTING {
}; };
struct AIO_IOB2_BI2X_WRFIRM { struct AIO_IOB2_BI2X_WRFIRM {
uint8_t data[0x20f48];
}; };
struct AIO_IOB2_BI2X_AC1__INPUT struct AIO_IOB2_BI2X_AC1__INPUT
@@ -191,6 +198,12 @@ namespace games::pc {
AIO_IOB2_BI2X_AC1__ICNPIN ICnPinHist[20]; AIO_IOB2_BI2X_AC1__ICNPIN ICnPinHist[20];
}; };
// verified with XIF-2024122300
static_assert(sizeof(AIO_SCI_COMM) == 0xf8);
static_assert(sizeof(AIO_NMGR_IOB2) == 0xe30);
static_assert(sizeof(AIO_IOB2_BI2X_AC1) == 0x4538);
static_assert(sizeof(AIO_IOB2_BI2X_WRFIRM) == 0x20f48);
/* /*
* typedefs * typedefs
*/ */
@@ -338,14 +351,21 @@ namespace games::pc {
val = (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::FaderL]) - 0.5f) * 2; val = (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::FaderL]) - 0.5f) * 2;
} }
} }
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Left]) &&
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Right])) { const auto now = get_performance_milliseconds();
val = 0.f;
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Left])) { const auto fader_l_socd = socd::socd_clean(
0, // Fader-L
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Left]),
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Right]),
now);
if (fader_l_socd == socd::SocdCCW) {
val = -1.0f; val = -1.0f;
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Right])) { } else if (fader_l_socd == socd::SocdCW) {
val = +1.0f; val = +1.0f;
} }
o_DevStatus->Input.CN15_7 = (val < 0.2f) ? 0xFF : 0; o_DevStatus->Input.CN15_7 = (val < 0.2f) ? 0xFF : 0;
o_DevStatus->Input.CN15_6 = (val > -0.85f && val < 0.35f) ? 0xFF : 0; o_DevStatus->Input.CN15_6 = (val > -0.85f && val < 0.35f) ? 0xFF : 0;
o_DevStatus->Input.CN15_5 = (val > -0.6f && val < 0.6f) ? 0xFF : 0; o_DevStatus->Input.CN15_5 = (val > -0.6f && val < 0.6f) ? 0xFF : 0;
@@ -363,14 +383,19 @@ namespace games::pc {
val = (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::FaderR]) - 0.5f) * 2; val = (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::FaderR]) - 0.5f) * 2;
} }
} }
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Left]) &&
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Right])) { const auto fader_r_socd = socd::socd_clean(
val = 0.f; 1, // Fader-R
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Left])) { GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Left]),
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Right]),
now);
if (fader_r_socd == socd::SocdCCW) {
val = -1.0f; val = -1.0f;
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Right])) { } else if (fader_r_socd == socd::SocdCW) {
val = +1.0f; val = +1.0f;
} }
o_DevStatus->Input.CN11_20 = (val < 0.2f) ? 0xFF : 0; o_DevStatus->Input.CN11_20 = (val < 0.2f) ? 0xFF : 0;
o_DevStatus->Input.CN11_19 = (val > -0.85f && val < 0.35f) ? 0xFF : 0; o_DevStatus->Input.CN11_19 = (val > -0.85f && val < 0.35f) ? 0xFF : 0;
o_DevStatus->Input.CN9_10 = (val > -0.6f && val < 0.6f) ? 0xFF : 0; o_DevStatus->Input.CN9_10 = (val > -0.6f && val < 0.6f) ? 0xFF : 0;
@@ -737,3 +762,5 @@ namespace games::pc {
} }
} }
#endif
+4
View File
@@ -1,5 +1,9 @@
#pragma once #pragma once
#if SPICE64
namespace games::pc { namespace games::pc {
void bi2x_hook_init(); void bi2x_hook_init();
} }
#endif
+12
View File
@@ -12,10 +12,12 @@
#include "misc/wintouchemu.h" #include "misc/wintouchemu.h"
#include "hooks/graphics/graphics.h" #include "hooks/graphics/graphics.h"
#include "rawinput/rawinput.h" #include "rawinput/rawinput.h"
#include "util/socd_cleaner.h"
namespace games::pc { namespace games::pc {
std::string PC_INJECT_ARGS = ""; std::string PC_INJECT_ARGS = "";
bool PC_NO_IO = false; bool PC_NO_IO = false;
bool PC_KNOB_MODE = false;
static acioemu::ACIOHandle *acioHandle = nullptr; static acioemu::ACIOHandle *acioHandle = nullptr;
static std::wstring portName = L"COM1"; static std::wstring portName = L"COM1";
@@ -61,6 +63,11 @@ namespace games::pc {
return ERROR_NOT_SUPPORTED; return ERROR_NOT_SUPPORTED;
} }
void PCGame::pre_attach() {
// SOCD
socd::ALGORITHM = socd::SocdAlgorithm::PreferRecent;
}
void PCGame::attach() { void PCGame::attach() {
Game::attach(); Game::attach();
@@ -78,9 +85,14 @@ namespace games::pc {
execexe::load_library("libaio-iob2_video.dll"); execexe::load_library("libaio-iob2_video.dll");
execexe::load_library("win10actlog.dll"); execexe::load_library("win10actlog.dll");
#if SPICE64
if (!PC_NO_IO) { if (!PC_NO_IO) {
bi2x_hook_init(); bi2x_hook_init();
} }
#endif
}); });
const auto user32Dll = "user32.dll"; const auto user32Dll = "user32.dll";
+1
View File
@@ -11,6 +11,7 @@ namespace games::pc {
public: public:
PCGame() : Game("Polaris Chord") {} PCGame() : Game("Polaris Chord") {}
virtual void pre_attach() override;
virtual void attach() override; virtual void attach() override;
virtual void detach() override; virtual void detach() override;
}; };
+14
View File
@@ -1,5 +1,7 @@
#include "bi2x_hook.h" #include "bi2x_hook.h"
#if SPICE64
#include "util/detour.h" #include "util/detour.h"
#include "util/logging.h" #include "util/logging.h"
#include "rawinput/rawinput.h" #include "rawinput/rawinput.h"
@@ -15,15 +17,19 @@ namespace games::qks {
*/ */
struct AIO_SCI_COMM { struct AIO_SCI_COMM {
uint8_t data[0xf8];
}; };
struct AIO_NMGR_IOB { struct AIO_NMGR_IOB {
uint8_t data[0xa00];
}; };
struct AIO_IOB2_BI2X_AC1 { struct AIO_IOB2_BI2X_AC1 {
uint8_t data[0x3908];
}; };
struct AIO_IOB2_BI2X_WRFIRM { struct AIO_IOB2_BI2X_WRFIRM {
uint8_t data[0x20f48];
}; };
struct AIO_NMGR_IOB__NODEINFO { struct AIO_NMGR_IOB__NODEINFO {
@@ -174,6 +180,12 @@ namespace games::qks {
AIO_IOB2_BI2X_AC1__SETTING Setting; AIO_IOB2_BI2X_AC1__SETTING Setting;
}; };
// verified with UKS-001-2024030600
static_assert(sizeof(AIO_IOB2_BI2X_AC1) == 0x3908);
static_assert(sizeof(AIO_SCI_COMM) == 0xf8);
static_assert(sizeof(AIO_NMGR_IOB) == 0xa00);
static_assert(sizeof(AIO_IOB2_BI2X_WRFIRM) == 0x20f48);
/* /*
* typedefs * typedefs
*/ */
@@ -603,3 +615,5 @@ namespace games::qks {
aioNodeCtl_IsError, &aioNodeCtl_IsError_orig); aioNodeCtl_IsError, &aioNodeCtl_IsError_orig);
} }
} }
#endif
+4
View File
@@ -1,7 +1,11 @@
#pragma once #pragma once
#if SPICE64
#include <stdint.h> #include <stdint.h>
namespace games::qks { namespace games::qks {
void bi2x_hook_init(); void bi2x_hook_init();
} }
#endif
+4
View File
@@ -86,9 +86,13 @@ namespace games::qks {
detour::trampoline_try("execexe.dll", MAKEINTRESOURCE(9), detour::trampoline_try("execexe.dll", MAKEINTRESOURCE(9),
execexe_CreateFileA_hook,&execexe_CreateFileA_orig); execexe_CreateFileA_hook,&execexe_CreateFileA_orig);
#if SPICE64
// insert BI2X hooks // insert BI2X hooks
bi2x_hook_init(); bi2x_hook_init();
#endif
// add card reader // add card reader
acioHandle = new acioemu::ACIOHandle(portName.c_str()); acioHandle = new acioemu::ACIOHandle(portName.c_str());
devicehook_init_trampoline(); devicehook_init_trampoline();