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
+15 -1
View File
@@ -1,5 +1,7 @@
#include "bi2x_hook.h"
#if SPICE64
#include <cstdint>
#include "util/detour.h"
#include "util/logging.h"
@@ -16,15 +18,19 @@ namespace games::ccj {
*/
struct AIO_SCI_COMM {
uint8_t data[0xf8];
};
struct AIO_NMGR_IOB2 {
uint8_t data[0xe30];
};
struct AIO_IOB2_BI2X_TBS {
uint8_t data[0x4680];
};
struct AIO_IOB2_BI2X_WRFIRM {
uint8_t data[0x20f48];
};
struct AIO_NMGR_IOB__NODEINFO {
@@ -72,6 +78,12 @@ namespace games::ccj {
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);
/*
@@ -621,4 +633,6 @@ namespace games::ccj {
detour::trampoline_try(libaioDll, "aioNodeCtl_IsError",
aioNodeCtl_IsError, &aioNodeCtl_IsError_orig);
}
}
}
#endif
+5 -1
View File
@@ -1,5 +1,9 @@
#pragma once
#if SPICE64
namespace games::ccj {
void bi2x_hook_init();
}
}
#endif
+4
View File
@@ -68,9 +68,13 @@ namespace games::ccj {
detour::trampoline_try("execexe.dll", MAKEINTRESOURCE(11),
(void*)execexe_CreateFileW_hook,(void**)&execexe_CreateFileW_orig);
#if SPICE64
// insert BI2X hooks
bi2x_hook_init();
#endif
// insert trackball hooks
trackball_hook_init();
+19 -1
View File
@@ -1,5 +1,7 @@
#include "bi2x_hook.h"
#if SPICE64
#include <optional>
#include <cstdint>
#include "util/detour.h"
@@ -18,24 +20,29 @@ namespace games::gitadora {
// */
struct AIO_NMGR_IOB2 {
uint8_t data[0xe18];
};
struct AIO_IOB2_BI2X_AC1__SETTING {
};
struct AIO_NMGR_IOB5 {
uint8_t data[0xb10];
};
struct AIO_SCI_COMM {
uint8_t data[0x100];
};
struct AIO_IOB5_Y32D {
uint8_t data[0x2e8];
};
// struct AIO_IOB5_Y33I {
// };
struct AIO_IOB2_BI2X_AC1 {
uint8_t data[0x4570];
};
struct AIO_NMGR_IOB__NODEINFO {
@@ -43,6 +50,7 @@ namespace games::gitadora {
};
struct AIO_IOB2_BI2X_WRFIRM {
uint8_t data[0x20f48];
};
struct AIO_SCI_COMM_T {
@@ -159,6 +167,14 @@ namespace games::gitadora {
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
*/
@@ -701,4 +717,6 @@ namespace games::gitadora {
detour::trampoline_try(libaioDll, "aioNodeCtl_UpdateDevicesStatus",
aioNodeCtl_UpdateDevicesStatus, &aioNodeCtl_UpdateDevicesStatus_orig);
}
}
}
#endif
+5 -1
View File
@@ -1,5 +1,9 @@
#pragma once
#if SPICE64
namespace games::gitadora {
void bi2x_hook_init();
}
}
#endif
+38 -11
View File
@@ -1,5 +1,7 @@
#include "bi2x_hook.h"
#if SPICE64
#include "util/execexe.h"
#include "util/logging.h"
#include "rawinput/rawinput.h"
@@ -9,9 +11,10 @@
#include "util/tapeled.h"
#include "pc.h"
#include "util/utils.h"
#include "util/time.h"
#include "util/socd_cleaner.h"
namespace games::pc {
bool PC_KNOB_MODE = false;
constexpr float RELATIVE_DECAY = 0.02f;
struct FADER_RELATIVE {
@@ -75,18 +78,22 @@ namespace games::pc {
*/
struct AIO_SCI_COMM {
uint8_t data[0xf8];
};
struct AIO_NMGR_IOB2 {
uint8_t data[0xe30];
};
struct AIO_IOB2_BI2X_AC1 {
uint8_t data[0x4538];
};
struct AIO_IOB2_BI2X_AC1__SETTING {
};
struct AIO_IOB2_BI2X_WRFIRM {
uint8_t data[0x20f48];
};
struct AIO_IOB2_BI2X_AC1__INPUT
@@ -191,6 +198,12 @@ namespace games::pc {
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
*/
@@ -338,14 +351,21 @@ namespace games::pc {
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])) {
val = 0.f;
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Left])) {
const auto now = get_performance_milliseconds();
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;
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Right])) {
} else if (fader_l_socd == socd::SocdCW) {
val = +1.0f;
}
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_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;
}
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Left]) &&
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Right])) {
val = 0.f;
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Left])) {
const auto fader_r_socd = socd::socd_clean(
1, // Fader-R
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;
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Right])) {
} else if (fader_r_socd == socd::SocdCW) {
val = +1.0f;
}
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.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
#if SPICE64
namespace games::pc {
void bi2x_hook_init();
}
#endif
+12
View File
@@ -12,10 +12,12 @@
#include "misc/wintouchemu.h"
#include "hooks/graphics/graphics.h"
#include "rawinput/rawinput.h"
#include "util/socd_cleaner.h"
namespace games::pc {
std::string PC_INJECT_ARGS = "";
bool PC_NO_IO = false;
bool PC_KNOB_MODE = false;
static acioemu::ACIOHandle *acioHandle = nullptr;
static std::wstring portName = L"COM1";
@@ -61,6 +63,11 @@ namespace games::pc {
return ERROR_NOT_SUPPORTED;
}
void PCGame::pre_attach() {
// SOCD
socd::ALGORITHM = socd::SocdAlgorithm::PreferRecent;
}
void PCGame::attach() {
Game::attach();
@@ -78,9 +85,14 @@ namespace games::pc {
execexe::load_library("libaio-iob2_video.dll");
execexe::load_library("win10actlog.dll");
#if SPICE64
if (!PC_NO_IO) {
bi2x_hook_init();
}
#endif
});
const auto user32Dll = "user32.dll";
+1
View File
@@ -11,6 +11,7 @@ namespace games::pc {
public:
PCGame() : Game("Polaris Chord") {}
virtual void pre_attach() override;
virtual void attach() override;
virtual void detach() override;
};
+15 -1
View File
@@ -1,5 +1,7 @@
#include "bi2x_hook.h"
#if SPICE64
#include "util/detour.h"
#include "util/logging.h"
#include "rawinput/rawinput.h"
@@ -15,15 +17,19 @@ namespace games::qks {
*/
struct AIO_SCI_COMM {
uint8_t data[0xf8];
};
struct AIO_NMGR_IOB {
uint8_t data[0xa00];
};
struct AIO_IOB2_BI2X_AC1 {
uint8_t data[0x3908];
};
struct AIO_IOB2_BI2X_WRFIRM {
uint8_t data[0x20f48];
};
struct AIO_NMGR_IOB__NODEINFO {
@@ -174,6 +180,12 @@ namespace games::qks {
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
*/
@@ -602,4 +614,6 @@ namespace games::qks {
detour::trampoline_try(libaioDll, "aioNodeCtl_IsError",
aioNodeCtl_IsError, &aioNodeCtl_IsError_orig);
}
}
}
#endif
+5 -1
View File
@@ -1,7 +1,11 @@
#pragma once
#if SPICE64
#include <stdint.h>
namespace games::qks {
void bi2x_hook_init();
}
}
#endif
+4
View File
@@ -86,9 +86,13 @@ namespace games::qks {
detour::trampoline_try("execexe.dll", MAKEINTRESOURCE(9),
execexe_CreateFileA_hook,&execexe_CreateFileA_orig);
#if SPICE64
// insert BI2X hooks
bi2x_hook_init();
#endif
// add card reader
acioHandle = new acioemu::ACIOHandle(portName.c_str());
devicehook_init_trampoline();