iidx, sdvx: bi2x hook fix (#496)

## Link to GitHub Issue, if one exists
n/a

## Description of change
Both games take the address returned by `aioNMgrIob2_Create` and reads
values offset from that address as part of the I/O routine. This means
that the I/O routines of these games have been accessing random
variables in data/bss segments for years, they just happened to get a
value that doesn't lead to a crash.

Stumbled upon this while trying to add a new feature that required
adding more globals, but suddenly the game stopped polling I/O when I
changed a value of my global. Scary.

## Testing
Tested both games I/O
This commit is contained in:
bicarus
2026-01-03 19:27:53 -08:00
committed by GitHub
parent 7e89037db3
commit bf7666939a
2 changed files with 35 additions and 12 deletions
+18 -6
View File
@@ -30,6 +30,14 @@ namespace games::iidx {
uint8_t dummy1[0x9A0]; uint8_t dummy1[0x9A0];
}; };
// add padding before and after as the game will try to deref an offset
// from the address returned by aioNMgrIob2_Create
struct AIO_NMGR_IOB2_PTR {
uint8_t dummy0[0x100];
AIO_NMGR_IOB2 *iob2;
uint8_t dummy1[0x400 - sizeof(AIO_NMGR_IOB2 *)];
};
struct AIO_IOB2_BI2X_TDJ { struct AIO_IOB2_BI2X_TDJ {
// who knows // who knows
uint8_t data[0x13F8]; uint8_t data[0x13F8];
@@ -112,7 +120,7 @@ namespace games::iidx {
AIO_IOB2_BI2X_TDJ *custom_node = nullptr; AIO_IOB2_BI2X_TDJ *custom_node = nullptr;
AC_HNDLIF *acHndlif = nullptr; AC_HNDLIF *acHndlif = nullptr;
AIO_NMGR_IOB2 *aioNmgrIob2 = nullptr; AIO_NMGR_IOB2_PTR *aioNmgrIob2 = nullptr;
/* /*
* implementations * implementations
@@ -458,12 +466,16 @@ namespace games::iidx {
static AIO_NMGR_IOB2** __fastcall aioNMgrIob2_Create(AC_HNDLIF *a1, unsigned int a2) { static AIO_NMGR_IOB2** __fastcall aioNMgrIob2_Create(AC_HNDLIF *a1, unsigned int a2) {
if (aioNmgrIob2 == nullptr) { if (aioNmgrIob2 == nullptr) {
aioNmgrIob2 = new AIO_NMGR_IOB2; aioNmgrIob2 = new AIO_NMGR_IOB2_PTR{};
memset(aioNmgrIob2, 0x0, sizeof(AIO_NMGR_IOB2)); aioNmgrIob2->iob2 = new AIO_NMGR_IOB2{};
aioNmgrIob2->pAIO_NMGR_IOB_BeginManage = AIO_NMGR_IOB_BeginManageStub; aioNmgrIob2->iob2->pAIO_NMGR_IOB_BeginManage = AIO_NMGR_IOB_BeginManageStub;
} }
log_info("bi2x_hook", "aioNMgrIob2_Create");
return &aioNmgrIob2; log_info("bi2x_hook", "aioNMgrIob2_Create returned {}, padded size=0x{:x}, IOB2 @ {}, size=0x{:x}",
fmt::ptr(&aioNmgrIob2->iob2), sizeof(*aioNmgrIob2),
fmt::ptr(aioNmgrIob2->iob2), sizeof(*aioNmgrIob2->iob2));
return &aioNmgrIob2->iob2;
} }
static int64_t __fastcall aioIob2Bi2x_WriteFirmGetState(int64_t a1) { static int64_t __fastcall aioIob2Bi2x_WriteFirmGetState(int64_t a1) {
+17 -6
View File
@@ -32,6 +32,14 @@ namespace games::sdvx {
uint8_t dummy1[0x9A0]; uint8_t dummy1[0x9A0];
}; };
// add padding before and after as the game will try to deref an offset
// from the address returned by aioNMgrIob2_Create
struct AIO_NMGR_IOB2_PTR {
uint8_t dummy0[0x100];
AIO_NMGR_IOB2 *iob2;
uint8_t dummy1[0x400 - sizeof(AIO_NMGR_IOB2 *)];
};
struct AIO_IOB2_BI2X_UFC { struct AIO_IOB2_BI2X_UFC {
// who knows // who knows
uint8_t data[0x13F8]; uint8_t data[0x13F8];
@@ -94,7 +102,7 @@ namespace games::sdvx {
AIO_IOB2_BI2X_UFC *custom_node = nullptr; AIO_IOB2_BI2X_UFC *custom_node = nullptr;
AC_HNDLIF *acHndlif = nullptr; AC_HNDLIF *acHndlif = nullptr;
AIO_NMGR_IOB2 *aioNmgrIob2 = nullptr; AIO_NMGR_IOB2_PTR* aioNmgrIob2 = nullptr;
// state // state
static uint8_t count = 0; static uint8_t count = 0;
static uint16_t VOL_L = 0; static uint16_t VOL_L = 0;
@@ -347,11 +355,14 @@ namespace games::sdvx {
static AIO_NMGR_IOB2** __fastcall aioNMgrIob2_Create(AC_HNDLIF *a1, unsigned int a2) { static AIO_NMGR_IOB2** __fastcall aioNMgrIob2_Create(AC_HNDLIF *a1, unsigned int a2) {
if (aioNmgrIob2 == nullptr) { if (aioNmgrIob2 == nullptr) {
aioNmgrIob2 = new AIO_NMGR_IOB2; aioNmgrIob2 = new AIO_NMGR_IOB2_PTR{};
memset(aioNmgrIob2, 0x0, sizeof(AIO_NMGR_IOB2)); aioNmgrIob2->iob2 = new AIO_NMGR_IOB2{};
aioNmgrIob2->pAIO_NMGR_IOB_BeginManage = AIO_NMGR_IOB_BeginManageStub; aioNmgrIob2->iob2->pAIO_NMGR_IOB_BeginManage = AIO_NMGR_IOB_BeginManageStub;
} }
log_info("bi2x_hook", "aioNMgrIob2_Create"); log_info("bi2x_hook", "aioNMgrIob2_Create returned {}, padded size=0x{:x}, IOB2 @ {}, size=0x{:x}",
fmt::ptr(&aioNmgrIob2->iob2), sizeof(*aioNmgrIob2),
fmt::ptr(aioNmgrIob2->iob2), sizeof(*aioNmgrIob2->iob2));
BI2X_INITIALIZED = true; BI2X_INITIALIZED = true;
// enable hack to make PIN pad work for KFC in BI2X mode // enable hack to make PIN pad work for KFC in BI2X mode
@@ -359,7 +370,7 @@ namespace games::sdvx {
// (as opposed to just doing a check for "isValkyrieCabMode?") // (as opposed to just doing a check for "isValkyrieCabMode?")
// because there are hex edits that allow you to use legacy (KFC/BIO2) IO while in Valk mode // because there are hex edits that allow you to use legacy (KFC/BIO2) IO while in Valk mode
acioemu::ICCA_DEVICE_HACK = true; acioemu::ICCA_DEVICE_HACK = true;
return &aioNmgrIob2; return &aioNmgrIob2->iob2;
} }
static int64_t __fastcall aioIob2Bi2x_WriteFirmGetState(int64_t a1) { static int64_t __fastcall aioIob2Bi2x_WriteFirmGetState(int64_t a1) {