Initial re-upload of spice2x-24-08-24
This commit is contained in:
624
games/ccj/bi2x_hook.cpp
Normal file
624
games/ccj/bi2x_hook.cpp
Normal file
@@ -0,0 +1,624 @@
|
||||
#include "bi2x_hook.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "games/io.h"
|
||||
#include "io.h"
|
||||
#include "util/tapeled.h"
|
||||
|
||||
namespace games::ccj {
|
||||
|
||||
/*
|
||||
* class definitions
|
||||
*/
|
||||
|
||||
struct AIO_SCI_COMM {
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB2 {
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_TBS {
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_WRFIRM {
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB__NODEINFO {
|
||||
uint8_t data[0xA3];
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_AC1__INPUTDATA {
|
||||
uint8_t data[247];
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_AC1__OUTPUTDATA {
|
||||
uint8_t data[48];
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_TBS__INPUT {
|
||||
uint8_t DevIoCounter;
|
||||
uint8_t bExIoAErr;
|
||||
uint8_t bExIoBErr;
|
||||
uint8_t bPcPowerOn;
|
||||
uint8_t bPcPowerCheck;
|
||||
uint8_t CoinCount;
|
||||
uint8_t bTest;
|
||||
uint8_t bService;
|
||||
uint8_t bCoinSw;
|
||||
uint8_t bCoinJam;
|
||||
uint8_t bHPDetect;
|
||||
uint16_t StickY;
|
||||
uint16_t StickX;
|
||||
uint8_t bStickBtn;
|
||||
uint8_t bTrigger1;
|
||||
uint8_t bTrigger2;
|
||||
uint8_t bButton0;
|
||||
uint8_t bButton1;
|
||||
uint8_t bButton2;
|
||||
uint8_t bButton3;
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_TBS__DEVSTATUS {
|
||||
uint8_t InputCounter;
|
||||
uint8_t OutputCounter;
|
||||
uint8_t IoResetCounter;
|
||||
uint8_t TapeLedCounter;
|
||||
AIO_IOB2_BI2X_TBS__INPUT Input;
|
||||
AIO_IOB2_BI2X_AC1__INPUTDATA InputData;
|
||||
AIO_IOB2_BI2X_AC1__OUTPUTDATA OutputData;
|
||||
};
|
||||
|
||||
static void write_iccr_led(Lights::ccj_lights_t light, uint8_t value);
|
||||
|
||||
/*
|
||||
* typedefs
|
||||
*/
|
||||
|
||||
// libaio-iob2_video.dll
|
||||
typedef AIO_IOB2_BI2X_TBS* (__fastcall *aioIob2Bi2xTBS_Create_t)(AIO_NMGR_IOB2 *i_pNodeMgr, uint32_t i_DevId);
|
||||
typedef void (__fastcall *aioIob2Bi2xTBS_GetDeviceStatus_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl,
|
||||
AIO_IOB2_BI2X_TBS__DEVSTATUS *o_DevStatus);
|
||||
typedef void (__fastcall *aioIob2Bi2xTBS_IoReset_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_bfIoReset);
|
||||
typedef void (__fastcall *aioIob2Bi2xAC1_IoReset_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_bfIoReset);
|
||||
typedef void (__fastcall *aioIob2Bi2xTBS_SetWatchDogTimer_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint8_t i_Count);
|
||||
typedef void (__fastcall *aioIob2Bi2xAC1_SetWatchDogTimer_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint8_t i_Count);
|
||||
typedef void (__fastcall *aioIob2Bi2xTBS_ControlCoinBlocker_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_Slot,
|
||||
bool i_bOpen);
|
||||
typedef void (__fastcall *aioIob2Bi2xTBS_AddCounter_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_Counter,
|
||||
uint32_t i_Count);
|
||||
typedef void (__fastcall *aioIob2Bi2xTBS_SetAmpVolume_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_Amp,
|
||||
uint32_t i_Volume);
|
||||
typedef void (__fastcall *aioIob2Bi2xTBS_EnableUsbCharger_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl, bool i_bEnable);
|
||||
typedef void (__fastcall *aioIob2Bi2xTBS_SetIrLed_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl, bool i_bOn);
|
||||
typedef void (__fastcall *aioIob2Bi2xTBS_SetButton0Lamp_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl, bool i_bOn);
|
||||
typedef void (__fastcall *aioIob2Bi2xTBS_SetIccrLed_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_RGB);
|
||||
typedef void (__fastcall *aioIob2Bi2xTBS_SetStickLed_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_RGB);
|
||||
typedef void (__fastcall *aioIob2Bi2xTBS_SetTapeLedData_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_TapeLed, uint8_t *i_pData);
|
||||
typedef AIO_SCI_COMM* (__fastcall *aioIob2Bi2x_OpenSciUsbCdc_t)(uint32_t i_SerialNumber);
|
||||
typedef AIO_IOB2_BI2X_WRFIRM* (__fastcall *aioIob2Bi2x_CreateWriteFirmContext_t)(uint32_t i_SerialNumber,
|
||||
uint32_t i_bfIob);
|
||||
typedef void (__fastcall *aioIob2Bi2x_DestroyWriteFirmContext_t)(AIO_IOB2_BI2X_WRFIRM *i_pWrFirm);
|
||||
typedef int32_t (__fastcall *aioIob2Bi2x_WriteFirmGetState_t)(AIO_IOB2_BI2X_WRFIRM *i_pWrFirm);
|
||||
typedef bool (__fastcall *aioIob2Bi2x_WriteFirmIsCompleted_t)(int32_t i_State);
|
||||
typedef bool (__fastcall *aioIob2Bi2x_WriteFirmIsError_t)(int32_t i_State);
|
||||
|
||||
// libaio-iob.dll
|
||||
typedef AIO_NMGR_IOB2* (__fastcall *aioNMgrIob2_Create_t)(AIO_SCI_COMM *i_pSci, uint32_t i_bfMode);
|
||||
typedef void (__fastcall *aioNMgrIob_BeginManage_t)(AIO_NMGR_IOB2 *i_pNodeMgr);
|
||||
typedef void (__fastcall *aioNCtlIob_GetNodeInfo_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl,
|
||||
AIO_NMGR_IOB__NODEINFO *o_NodeInfo);
|
||||
|
||||
// libaio.dll
|
||||
typedef void (__fastcall *aioNodeMgr_Destroy_t)(AIO_NMGR_IOB2 *i_pNodeMgr);
|
||||
typedef int32_t (__fastcall *aioNodeMgr_GetState_t)(AIO_NMGR_IOB2 *i_pNodeMgr);
|
||||
typedef bool (__fastcall *aioNodeMgr_IsReady_t)(AIO_NMGR_IOB2 *i_pNodeMgr, int32_t i_State);
|
||||
typedef bool (__fastcall *aioNodeMgr_IsError_t)(AIO_NMGR_IOB2 *i_pNodeMgr, int32_t i_State);
|
||||
typedef void (__fastcall *aioNodeCtl_Destroy_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl);
|
||||
typedef int32_t (__fastcall *aioNodeCtl_GetState_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl);
|
||||
typedef bool (__fastcall *aioNodeCtl_IsReady_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl, int32_t i_State);
|
||||
typedef bool (__fastcall *aioNodeCtl_IsError_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl, int32_t i_State);
|
||||
|
||||
/*
|
||||
* function pointers
|
||||
*/
|
||||
|
||||
// libaio-iob2_video.dll
|
||||
static aioIob2Bi2xTBS_Create_t aioIob2Bi2xTBS_Create_orig = nullptr;
|
||||
static aioIob2Bi2xTBS_GetDeviceStatus_t aioIob2Bi2xTBS_GetDeviceStatus_orig = nullptr;
|
||||
static aioIob2Bi2xTBS_IoReset_t aioIob2Bi2xTBS_IoReset_orig = nullptr;
|
||||
static aioIob2Bi2xAC1_IoReset_t aioIob2Bi2xAC1_IoReset_orig = nullptr;
|
||||
static aioIob2Bi2xTBS_SetWatchDogTimer_t aioIob2Bi2xTBS_SetWatchDogTimer_orig = nullptr;
|
||||
static aioIob2Bi2xAC1_SetWatchDogTimer_t aioIob2Bi2xAC1_SetWatchDogTimer_orig = nullptr;
|
||||
static aioIob2Bi2xTBS_ControlCoinBlocker_t aioIob2Bi2xTBS_ControlCoinBlocker_orig = nullptr;
|
||||
static aioIob2Bi2xTBS_AddCounter_t aioIob2Bi2xTBS_AddCounter_orig = nullptr;
|
||||
static aioIob2Bi2xTBS_SetAmpVolume_t aioIob2Bi2xTBS_SetAmpVolume_orig = nullptr;
|
||||
static aioIob2Bi2xTBS_EnableUsbCharger_t aioIob2Bi2xTBS_EnableUsbCharger_orig = nullptr;
|
||||
static aioIob2Bi2xTBS_SetIrLed_t aioIob2Bi2xTBS_SetIrLed_orig = nullptr;
|
||||
static aioIob2Bi2xTBS_SetButton0Lamp_t aioIob2Bi2xTBS_SetButton0Lamp_orig = nullptr;
|
||||
static aioIob2Bi2xTBS_SetIccrLed_t aioIob2Bi2xTBS_SetIccrLed_orig = nullptr;
|
||||
static aioIob2Bi2xTBS_SetStickLed_t aioIob2Bi2xTBS_SetStickLed_orig = nullptr;
|
||||
static aioIob2Bi2xTBS_SetTapeLedData_t aioIob2Bi2xTBS_SetTapeLedData_orig = nullptr;
|
||||
static aioIob2Bi2x_OpenSciUsbCdc_t aioIob2Bi2x_OpenSciUsbCdc_orig = nullptr;
|
||||
static aioIob2Bi2x_CreateWriteFirmContext_t aioIob2Bi2x_CreateWriteFirmContext_orig = nullptr;
|
||||
static aioIob2Bi2x_DestroyWriteFirmContext_t aioIob2Bi2x_DestroyWriteFirmContext_orig = nullptr;
|
||||
static aioIob2Bi2x_WriteFirmGetState_t aioIob2Bi2x_WriteFirmGetState_orig = nullptr;
|
||||
static aioIob2Bi2x_WriteFirmIsCompleted_t aioIob2Bi2x_WriteFirmIsCompleted_orig = nullptr;
|
||||
static aioIob2Bi2x_WriteFirmIsError_t aioIob2Bi2x_WriteFirmIsError_orig = nullptr;
|
||||
|
||||
// libaio-iob.dll
|
||||
static aioNMgrIob2_Create_t aioNMgrIob2_Create_orig = nullptr;
|
||||
static aioNMgrIob_BeginManage_t aioNMgrIob_BeginManage_orig = nullptr;
|
||||
static aioNCtlIob_GetNodeInfo_t aioNCtlIob_GetNodeInfo_orig = nullptr;
|
||||
|
||||
// libaio.dll
|
||||
static aioNodeMgr_Destroy_t aioNodeMgr_Destroy_orig = nullptr;
|
||||
static aioNodeMgr_GetState_t aioNodeMgr_GetState_orig = nullptr;
|
||||
static aioNodeMgr_IsReady_t aioNodeMgr_IsReady_orig = nullptr;
|
||||
static aioNodeMgr_IsError_t aioNodeMgr_IsError_orig = nullptr;
|
||||
static aioNodeCtl_Destroy_t aioNodeCtl_Destroy_orig = nullptr;
|
||||
static aioNodeCtl_GetState_t aioNodeCtl_GetState_orig = nullptr;
|
||||
static aioNodeCtl_IsReady_t aioNodeCtl_IsReady_orig = nullptr;
|
||||
static aioNodeCtl_IsError_t aioNodeCtl_IsError_orig = nullptr;
|
||||
|
||||
/*
|
||||
* variables
|
||||
*/
|
||||
|
||||
static AIO_SCI_COMM *aioSciComm;
|
||||
static AIO_NMGR_IOB2 *aioNmgrIob2;
|
||||
static AIO_IOB2_BI2X_TBS *aioIob2Bi2xTbs;
|
||||
static AIO_IOB2_BI2X_WRFIRM *aioIob2Bi2xWrfirm;
|
||||
|
||||
static uint8_t count = 0;
|
||||
|
||||
/*
|
||||
* implementations
|
||||
*/
|
||||
|
||||
static AIO_IOB2_BI2X_TBS* __fastcall aioIob2Bi2xTBS_Create(
|
||||
AIO_NMGR_IOB2 *i_pNodeMgr, uint32_t i_DevId) {
|
||||
|
||||
if (i_pNodeMgr == aioNmgrIob2) {
|
||||
log_info("bi2x_hook", "node created");
|
||||
aioIob2Bi2xTbs = new AIO_IOB2_BI2X_TBS;
|
||||
return aioIob2Bi2xTbs;
|
||||
} else {
|
||||
return aioIob2Bi2xTBS_Create_orig(i_pNodeMgr, i_DevId);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xTBS_GetDeviceStatus(
|
||||
AIO_IOB2_BI2X_TBS *i_pNodeCtl, AIO_IOB2_BI2X_TBS__DEVSTATUS *o_DevStatus) {
|
||||
|
||||
RI_MGR->devices_flush_output();
|
||||
|
||||
if (i_pNodeCtl != aioIob2Bi2xTbs) {
|
||||
return aioIob2Bi2xTBS_GetDeviceStatus_orig(i_pNodeCtl, o_DevStatus);
|
||||
}
|
||||
|
||||
memset(o_DevStatus, 0x00, sizeof(AIO_IOB2_BI2X_TBS__DEVSTATUS));
|
||||
|
||||
o_DevStatus->Input.DevIoCounter = count;
|
||||
count++;
|
||||
|
||||
o_DevStatus->Input.StickX = 32768;
|
||||
o_DevStatus->Input.StickY = 32768;
|
||||
|
||||
auto &analogs = get_analogs();
|
||||
if (analogs[Analogs::Joystick_X].isSet() || analogs[Analogs::Joystick_Y].isSet()) {
|
||||
o_DevStatus->Input.StickX =
|
||||
65535 - (uint16_t) (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::Joystick_X]) * 65535);
|
||||
o_DevStatus->Input.StickY =
|
||||
65535 - (uint16_t) (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::Joystick_Y]) * 65535);
|
||||
}
|
||||
|
||||
auto &buttons = get_buttons();
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test]))
|
||||
o_DevStatus->Input.bTest = 1;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service]))
|
||||
o_DevStatus->Input.bService = 1;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::CoinMech]))
|
||||
o_DevStatus->Input.bCoinSw = 1;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Joystick_Up]))
|
||||
o_DevStatus->Input.StickY = 65535;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Joystick_Down]))
|
||||
o_DevStatus->Input.StickY = 0;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Joystick_Left]))
|
||||
o_DevStatus->Input.StickX = 65535;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Joystick_Right]))
|
||||
o_DevStatus->Input.StickX = 0;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button_Dash]))
|
||||
o_DevStatus->Input.bTrigger1 = 1;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button_Special]))
|
||||
o_DevStatus->Input.bButton0 = 1;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button_Action]))
|
||||
o_DevStatus->Input.bButton1 = 1;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button_Jump]))
|
||||
o_DevStatus->Input.bButton2 = 1;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button_Slide]))
|
||||
o_DevStatus->Input.bButton3 = 1;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Headphones]))
|
||||
o_DevStatus->Input.bHPDetect = 1;
|
||||
|
||||
o_DevStatus->Input.CoinCount = eamuse_coin_get_stock();
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xAC1_IoReset(
|
||||
AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_bfIoReset) {
|
||||
|
||||
if (i_pNodeCtl == aioIob2Bi2xTbs) {
|
||||
} else {
|
||||
return aioIob2Bi2xAC1_IoReset_orig(i_pNodeCtl, i_bfIoReset);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xTBS_IoReset(
|
||||
AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_bfIoReset) {
|
||||
|
||||
if (i_pNodeCtl == aioIob2Bi2xTbs) {
|
||||
return aioIob2Bi2xAC1_IoReset(i_pNodeCtl, i_bfIoReset);
|
||||
} else {
|
||||
return aioIob2Bi2xTBS_IoReset_orig(i_pNodeCtl, i_bfIoReset);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xAC1_SetWatchDogTimer(
|
||||
AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint8_t i_Count) {
|
||||
|
||||
if (i_pNodeCtl == aioIob2Bi2xTbs) {
|
||||
} else {
|
||||
return aioIob2Bi2xAC1_SetWatchDogTimer_orig(i_pNodeCtl, i_Count);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xTBS_SetWatchDogTimer(
|
||||
AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint8_t i_Count) {
|
||||
|
||||
if (i_pNodeCtl == aioIob2Bi2xTbs) {
|
||||
return aioIob2Bi2xAC1_SetWatchDogTimer(i_pNodeCtl, i_Count);
|
||||
} else {
|
||||
return aioIob2Bi2xTBS_SetWatchDogTimer_orig(i_pNodeCtl, i_Count);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xTBS_ControlCoinBlocker(
|
||||
AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_Slot, bool i_bOpen) {
|
||||
|
||||
if (i_pNodeCtl == aioIob2Bi2xTbs) {
|
||||
eamuse_coin_set_block(!i_bOpen);
|
||||
} else {
|
||||
return aioIob2Bi2xTBS_ControlCoinBlocker_orig(i_pNodeCtl, i_Slot, i_bOpen);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xTBS_AddCounter(
|
||||
AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_Counter, uint32_t i_Count) {
|
||||
|
||||
if (i_pNodeCtl == aioIob2Bi2xTbs && i_Count == 0) {
|
||||
eamuse_coin_set_stock((uint16_t) i_Count);
|
||||
} else {
|
||||
return aioIob2Bi2xTBS_AddCounter_orig(i_pNodeCtl, i_Counter, i_Count);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xTBS_SetAmpVolume(
|
||||
AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_Amp, uint32_t i_Volume) {
|
||||
|
||||
if (i_pNodeCtl != aioIob2Bi2xTbs) {
|
||||
return aioIob2Bi2xTBS_SetAmpVolume_orig(i_pNodeCtl, i_Amp, i_Volume);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xTBS_EnableUsbCharger(
|
||||
AIO_IOB2_BI2X_TBS *i_pNodeCtl, bool i_bEnable) {
|
||||
|
||||
if (i_pNodeCtl != aioIob2Bi2xTbs) {
|
||||
return aioIob2Bi2xTBS_EnableUsbCharger_orig(i_pNodeCtl, i_bEnable);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xTBS_SetIrLed(AIO_IOB2_BI2X_TBS *i_pNodeCtl, bool i_bOn) {
|
||||
if (i_pNodeCtl != aioIob2Bi2xTbs) {
|
||||
return aioIob2Bi2xTBS_SetIrLed_orig(i_pNodeCtl, i_bOn);
|
||||
}
|
||||
|
||||
// handle ir led
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xTBS_SetButton0Lamp(AIO_IOB2_BI2X_TBS *i_pNodeCtl, bool i_bOn) {
|
||||
if (i_pNodeCtl != aioIob2Bi2xTbs) {
|
||||
return aioIob2Bi2xTBS_SetButton0Lamp_orig(i_pNodeCtl, i_bOn);
|
||||
}
|
||||
|
||||
auto &lights = get_lights();
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights.at(Lights::SpecialButton), (i_bOn ? 1.f : 0.f));
|
||||
}
|
||||
|
||||
static void write_iccr_led(Lights::ccj_lights_t light, uint8_t value) {
|
||||
auto &lights = get_lights();
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights.at(light), value / 255);
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xTBS_SetIccrLed(AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_RGB) {
|
||||
if (i_pNodeCtl != aioIob2Bi2xTbs) {
|
||||
return aioIob2Bi2xTBS_SetIccrLed_orig(i_pNodeCtl, i_RGB);
|
||||
}
|
||||
|
||||
write_iccr_led(Lights::CardReader_B, i_RGB);
|
||||
write_iccr_led(Lights::CardReader_G, i_RGB >> 8);
|
||||
write_iccr_led(Lights::CardReader_R, i_RGB >> 16);
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xTBS_SetStickLed(AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_RGB) {
|
||||
if (i_pNodeCtl != aioIob2Bi2xTbs) {
|
||||
return aioIob2Bi2xTBS_SetStickLed_orig(i_pNodeCtl, i_RGB);
|
||||
}
|
||||
|
||||
// handle stick led
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xTBS_SetTapeLedData(
|
||||
AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_TapeLed, uint8_t *i_pData) {
|
||||
|
||||
if (i_pNodeCtl != aioIob2Bi2xTbs) {
|
||||
return aioIob2Bi2xTBS_SetTapeLedData_orig(i_pNodeCtl, i_TapeLed, i_pData);
|
||||
}
|
||||
|
||||
/*
|
||||
* index mapping
|
||||
* 0 - title panel - 144 bytes - 48 colors
|
||||
* 1 - side panel - 147 bytes - 49 colors
|
||||
*
|
||||
* data is stored in RGB order, 3 bytes per color
|
||||
*
|
||||
* TODO: expose this data via API
|
||||
*/
|
||||
static struct TapeLedMapping {
|
||||
size_t data_size;
|
||||
int r, g, b;
|
||||
|
||||
TapeLedMapping(size_t data_size, int r, int g, int b)
|
||||
: data_size(data_size), r(r), g(g), b(b) {}
|
||||
|
||||
} mapping[] = {
|
||||
{ 48, Lights::TitlePanel_R, Lights::TitlePanel_G, Lights::TitlePanel_B },
|
||||
{ 49, Lights::SidePanel_R, Lights::SidePanel_G, Lights::SidePanel_B },
|
||||
};
|
||||
|
||||
if (tapeledutils::is_enabled() && i_TapeLed < std::size(mapping)) {
|
||||
auto &map = mapping[i_TapeLed];
|
||||
|
||||
// pick a color to use
|
||||
const auto rgb = tapeledutils::pick_color_from_led_tape(i_pData, map.data_size);
|
||||
|
||||
// program the lights into API
|
||||
auto &lights = get_lights();
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[map.r], rgb.r);
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[map.g], rgb.g);
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[map.b], rgb.b);
|
||||
}
|
||||
}
|
||||
|
||||
static AIO_SCI_COMM* __fastcall aioIob2Bi2x_OpenSciUsbCdc(uint32_t i_SerialNumber) {
|
||||
aioSciComm = new AIO_SCI_COMM;
|
||||
return aioSciComm;
|
||||
}
|
||||
|
||||
static AIO_IOB2_BI2X_WRFIRM* __fastcall aioIob2Bi2x_CreateWriteFirmContext(
|
||||
uint32_t i_SerialNumber, uint32_t i_bfIob) {
|
||||
|
||||
aioIob2Bi2xWrfirm = new AIO_IOB2_BI2X_WRFIRM;
|
||||
return aioIob2Bi2xWrfirm;
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2x_DestroyWriteFirmContext(AIO_IOB2_BI2X_WRFIRM *i_pWrFirm) {
|
||||
if (i_pWrFirm == aioIob2Bi2xWrfirm) {
|
||||
delete aioIob2Bi2xWrfirm;
|
||||
aioIob2Bi2xWrfirm = nullptr;
|
||||
} else {
|
||||
return aioIob2Bi2x_DestroyWriteFirmContext_orig(i_pWrFirm);
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t __fastcall aioIob2Bi2x_WriteFirmGetState(AIO_IOB2_BI2X_WRFIRM *i_pWrFirm) {
|
||||
if (i_pWrFirm == aioIob2Bi2xWrfirm) {
|
||||
return 8;
|
||||
} else {
|
||||
return aioIob2Bi2x_WriteFirmGetState_orig(i_pWrFirm);
|
||||
}
|
||||
}
|
||||
|
||||
static bool __fastcall aioIob2Bi2x_WriteFirmIsCompleted(int32_t i_State) {
|
||||
if (aioIob2Bi2xWrfirm != nullptr)
|
||||
return true;
|
||||
return aioIob2Bi2x_WriteFirmIsCompleted_orig(i_State);
|
||||
}
|
||||
|
||||
static bool __fastcall aioIob2Bi2x_WriteFirmIsError(int32_t i_State) {
|
||||
if (aioIob2Bi2xWrfirm != nullptr)
|
||||
return false;
|
||||
return aioIob2Bi2x_WriteFirmIsError_orig(i_State);
|
||||
}
|
||||
|
||||
static AIO_NMGR_IOB2* __fastcall aioNMgrIob2_Create(AIO_SCI_COMM *i_pSci, uint32_t i_bfMode) {
|
||||
if (i_pSci == aioSciComm) {
|
||||
aioNmgrIob2 = new AIO_NMGR_IOB2;
|
||||
return aioNmgrIob2;
|
||||
} else {
|
||||
return aioNMgrIob2_Create_orig(i_pSci, i_bfMode);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioNMgrIob_BeginManage(AIO_NMGR_IOB2 *i_pNodeMgr) {
|
||||
if (i_pNodeMgr == aioNmgrIob2) {
|
||||
} else {
|
||||
return aioNMgrIob_BeginManage_orig(i_pNodeMgr);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioNCtlIob_GetNodeInfo(
|
||||
AIO_IOB2_BI2X_TBS *i_pNodeCtl, AIO_NMGR_IOB__NODEINFO *o_NodeInfo) {
|
||||
|
||||
if (i_pNodeCtl == aioIob2Bi2xTbs) {
|
||||
memset(o_NodeInfo, 0, sizeof(AIO_NMGR_IOB__NODEINFO));
|
||||
} else {
|
||||
return aioNCtlIob_GetNodeInfo_orig(i_pNodeCtl, o_NodeInfo);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioNodeMgr_Destroy(AIO_NMGR_IOB2 *i_pNodeMgr) {
|
||||
if (i_pNodeMgr == aioNmgrIob2) {
|
||||
delete aioNmgrIob2;
|
||||
aioNmgrIob2 = nullptr;
|
||||
} else {
|
||||
return aioNodeMgr_Destroy_orig(i_pNodeMgr);
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t __fastcall aioNodeMgr_GetState(AIO_NMGR_IOB2 *i_pNodeMgr) {
|
||||
if (i_pNodeMgr == aioNmgrIob2) {
|
||||
return 1;
|
||||
} else {
|
||||
return aioNodeMgr_GetState_orig(i_pNodeMgr);
|
||||
}
|
||||
}
|
||||
|
||||
static bool __fastcall aioNodeMgr_IsReady(AIO_NMGR_IOB2 *i_pNodeMgr, int32_t i_State) {
|
||||
if (i_pNodeMgr == aioNmgrIob2) {
|
||||
return true;
|
||||
} else {
|
||||
return aioNodeMgr_IsReady_orig(i_pNodeMgr, i_State);
|
||||
}
|
||||
}
|
||||
|
||||
static bool __fastcall aioNodeMgr_IsError(AIO_NMGR_IOB2 *i_pNodeMgr, int32_t i_State) {
|
||||
if (i_pNodeMgr == aioNmgrIob2) {
|
||||
return false;
|
||||
} else {
|
||||
return aioNodeMgr_IsError_orig(i_pNodeMgr, i_State);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioNodeCtl_Destroy(AIO_IOB2_BI2X_TBS *i_pNodeCtl) {
|
||||
if (i_pNodeCtl == aioIob2Bi2xTbs) {
|
||||
delete aioIob2Bi2xTbs;
|
||||
aioIob2Bi2xTbs = nullptr;
|
||||
} else {
|
||||
return aioNodeCtl_Destroy_orig(i_pNodeCtl);
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t __fastcall aioNodeCtl_GetState(AIO_IOB2_BI2X_TBS *i_pNodeCtl) {
|
||||
if (i_pNodeCtl == aioIob2Bi2xTbs) {
|
||||
return 1;
|
||||
} else {
|
||||
return aioNodeCtl_GetState_orig(i_pNodeCtl);
|
||||
}
|
||||
}
|
||||
|
||||
static bool __fastcall aioNodeCtl_IsReady(AIO_IOB2_BI2X_TBS *i_pNodeCtl, int32_t i_State) {
|
||||
if (i_pNodeCtl == aioIob2Bi2xTbs) {
|
||||
return true;
|
||||
} else {
|
||||
return aioNodeCtl_IsReady_orig(i_pNodeCtl, i_State);
|
||||
}
|
||||
}
|
||||
|
||||
static bool __fastcall aioNodeCtl_IsError(AIO_IOB2_BI2X_TBS *i_pNodeCtl, int32_t i_State) {
|
||||
if (i_pNodeCtl == aioIob2Bi2xTbs) {
|
||||
return false;
|
||||
} else {
|
||||
return aioNodeCtl_IsError_orig(i_pNodeCtl, i_State);
|
||||
}
|
||||
}
|
||||
|
||||
void bi2x_hook_init() {
|
||||
// avoid double init
|
||||
static bool initialized = false;
|
||||
if (initialized) {
|
||||
return;
|
||||
} else {
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
// announce
|
||||
log_info("bi2x_hook", "init");
|
||||
|
||||
// libaio-iob2_video.dll
|
||||
const auto libaioIob2VideoDll = "libaio-iob2_video.dll";
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xTBS_Create",
|
||||
aioIob2Bi2xTBS_Create, &aioIob2Bi2xTBS_Create_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xTBS_GetDeviceStatus",
|
||||
aioIob2Bi2xTBS_GetDeviceStatus, &aioIob2Bi2xTBS_GetDeviceStatus_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xTBS_IoReset",
|
||||
aioIob2Bi2xTBS_IoReset, &aioIob2Bi2xTBS_IoReset_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_IoReset",
|
||||
aioIob2Bi2xAC1_IoReset, &aioIob2Bi2xAC1_IoReset_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xTBS_SetWatchDogTimer",
|
||||
aioIob2Bi2xTBS_SetWatchDogTimer, &aioIob2Bi2xTBS_SetWatchDogTimer_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_SetWatchDogTimer",
|
||||
aioIob2Bi2xAC1_SetWatchDogTimer, &aioIob2Bi2xAC1_SetWatchDogTimer_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xTBS_ControlCoinBlocker",
|
||||
aioIob2Bi2xTBS_ControlCoinBlocker, &aioIob2Bi2xTBS_ControlCoinBlocker_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xTBS_AddCounter",
|
||||
aioIob2Bi2xTBS_AddCounter, &aioIob2Bi2xTBS_AddCounter_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xTBS_SetAmpVolume",
|
||||
aioIob2Bi2xTBS_SetAmpVolume, &aioIob2Bi2xTBS_SetAmpVolume_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xTBS_EnableUsbCharger",
|
||||
aioIob2Bi2xTBS_EnableUsbCharger, &aioIob2Bi2xTBS_EnableUsbCharger_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xTBS_SetIrLed",
|
||||
aioIob2Bi2xTBS_SetIrLed, &aioIob2Bi2xTBS_SetIrLed_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xTBS_SetButton0Lamp",
|
||||
aioIob2Bi2xTBS_SetButton0Lamp, &aioIob2Bi2xTBS_SetButton0Lamp_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xTBS_SetIccrLed",
|
||||
aioIob2Bi2xTBS_SetIccrLed, &aioIob2Bi2xTBS_SetIccrLed_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xTBS_SetStickLed",
|
||||
aioIob2Bi2xTBS_SetStickLed, &aioIob2Bi2xTBS_SetStickLed_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xTBS_SetTapeLedData",
|
||||
aioIob2Bi2xTBS_SetTapeLedData, &aioIob2Bi2xTBS_SetTapeLedData_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2x_OpenSciUsbCdc",
|
||||
aioIob2Bi2x_OpenSciUsbCdc, &aioIob2Bi2x_OpenSciUsbCdc_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2x_CreateWriteFirmContext",
|
||||
aioIob2Bi2x_CreateWriteFirmContext, &aioIob2Bi2x_CreateWriteFirmContext_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2x_DestroyWriteFirmContext",
|
||||
aioIob2Bi2x_DestroyWriteFirmContext, &aioIob2Bi2x_DestroyWriteFirmContext_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2x_WriteFirmGetState",
|
||||
aioIob2Bi2x_WriteFirmGetState, &aioIob2Bi2x_WriteFirmGetState_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2x_WriteFirmIsCompleted",
|
||||
aioIob2Bi2x_WriteFirmIsCompleted, &aioIob2Bi2x_WriteFirmIsCompleted_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2x_WriteFirmIsError",
|
||||
aioIob2Bi2x_WriteFirmIsError, &aioIob2Bi2x_WriteFirmIsError_orig);
|
||||
|
||||
// libaio-iob.dll
|
||||
const auto libaioIobDll = "libaio-iob.dll";
|
||||
detour::trampoline_try(libaioIobDll, "aioNMgrIob2_Create",
|
||||
aioNMgrIob2_Create, &aioNMgrIob2_Create_orig);
|
||||
detour::trampoline_try(libaioIobDll, "aioNMgrIob_BeginManage",
|
||||
aioNMgrIob_BeginManage, &aioNMgrIob_BeginManage_orig);
|
||||
detour::trampoline_try(libaioIobDll, "aioNCtlIob_GetNodeInfo",
|
||||
aioNCtlIob_GetNodeInfo, &aioNCtlIob_GetNodeInfo_orig);
|
||||
|
||||
// libaio.dll
|
||||
const auto libaioDll = "libaio.dll";
|
||||
detour::trampoline_try(libaioDll, "aioNodeMgr_Destroy",
|
||||
aioNodeMgr_Destroy, &aioNodeMgr_Destroy_orig);
|
||||
detour::trampoline_try(libaioDll, "aioNodeMgr_GetState",
|
||||
aioNodeMgr_GetState, &aioNodeMgr_GetState_orig);
|
||||
detour::trampoline_try(libaioDll, "aioNodeMgr_IsReady",
|
||||
aioNodeMgr_IsReady, &aioNodeMgr_IsReady_orig);
|
||||
detour::trampoline_try(libaioDll, "aioNodeMgr_IsError",
|
||||
aioNodeMgr_IsError, &aioNodeMgr_IsError_orig);
|
||||
detour::trampoline_try(libaioDll, "aioNodeCtl_Destroy",
|
||||
aioNodeCtl_Destroy, &aioNodeCtl_Destroy_orig);
|
||||
detour::trampoline_try(libaioDll, "aioNodeCtl_GetState",
|
||||
aioNodeCtl_GetState, &aioNodeCtl_GetState_orig);
|
||||
detour::trampoline_try(libaioDll, "aioNodeCtl_IsReady",
|
||||
aioNodeCtl_IsReady, &aioNodeCtl_IsReady_orig);
|
||||
detour::trampoline_try(libaioDll, "aioNodeCtl_IsError",
|
||||
aioNodeCtl_IsError, &aioNodeCtl_IsError_orig);
|
||||
}
|
||||
}
|
||||
5
games/ccj/bi2x_hook.h
Normal file
5
games/ccj/bi2x_hook.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
namespace games::ccj {
|
||||
void bi2x_hook_init();
|
||||
}
|
||||
101
games/ccj/ccj.cpp
Normal file
101
games/ccj/ccj.cpp
Normal file
@@ -0,0 +1,101 @@
|
||||
#include "ccj.h"
|
||||
|
||||
#include <format>
|
||||
#include "util/libutils.h"
|
||||
#include "util/fileutils.h"
|
||||
#include "util/utils.h"
|
||||
#include "util/detour.h"
|
||||
#include "acioemu/handle.h"
|
||||
#include "misc/wintouchemu.h"
|
||||
#include "bi2x_hook.h"
|
||||
#include "trackball.h"
|
||||
|
||||
namespace games::ccj {
|
||||
// nomatchselect: disables debug menu that says "Select Match Mode"
|
||||
// q1: disables v-sync
|
||||
std::string CCJ_INJECT_ARGS = "-nomatchselect";
|
||||
|
||||
static acioemu::ACIOHandle *acioHandle = nullptr;
|
||||
static std::string commandLine;
|
||||
|
||||
static decltype(AddVectoredExceptionHandler) *AddVectoredExceptionHandler_orig = nullptr;
|
||||
static decltype(CreateFileW) *execexe_CreateFileW_orig = nullptr;
|
||||
static decltype(ShowCursor) *ShowCursor_orig = nullptr;
|
||||
static decltype(GetCommandLineA) *GetCommandLineA_orig = nullptr;
|
||||
|
||||
static HANDLE WINAPI execexe_CreateFileW_hook(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
|
||||
LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition,
|
||||
DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) {
|
||||
const auto fileName = ws2s(lpFileName);
|
||||
if (fileName == "COM1" && acioHandle->open(lpFileName)) {
|
||||
SetLastError(0);
|
||||
return (HANDLE) acioHandle;
|
||||
} else {
|
||||
return execexe_CreateFileW_orig(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes,
|
||||
dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
|
||||
}
|
||||
}
|
||||
|
||||
static int WINAPI ShowCursor_hook(BOOL bShow) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static PVOID WINAPI AddVectoredExceptionHandler_hook(ULONG First, PVECTORED_EXCEPTION_HANDLER Handler) {
|
||||
return INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
static LPSTR WINAPI GetCommandLineA_hook() {
|
||||
return (LPSTR)commandLine.c_str();
|
||||
}
|
||||
|
||||
void CCJGame::attach() {
|
||||
Game::attach();
|
||||
|
||||
// create required files
|
||||
fileutils::dir_create_recursive("dev/raw/log");
|
||||
fileutils::bin_write("dev/raw/bootio", nullptr, 0);
|
||||
fileutils::bin_write("dev/raw/log/output_log.txt", nullptr, 0);
|
||||
|
||||
// preload libraries
|
||||
libutils::load_library("execexe.dll");
|
||||
libutils::load_library("libaio.dll");
|
||||
libutils::load_library("libaio-iob.dll");
|
||||
libutils::load_library("libaio-iob2_video.dll");
|
||||
libutils::load_library("win10actlog.dll");
|
||||
|
||||
detour::trampoline_try("execexe.dll", MAKEINTRESOURCE(11),
|
||||
(void*)execexe_CreateFileW_hook,(void**)&execexe_CreateFileW_orig);
|
||||
|
||||
// insert BI2X hooks
|
||||
bi2x_hook_init();
|
||||
|
||||
// insert trackball hooks
|
||||
trackball_hook_init();
|
||||
|
||||
// add card reader
|
||||
acioHandle = new acioemu::ACIOHandle(L"COM1");
|
||||
devicehook_init_trampoline();
|
||||
devicehook_add(acioHandle);
|
||||
}
|
||||
|
||||
void CCJGame::post_attach() {
|
||||
Game::post_attach();
|
||||
|
||||
detour::trampoline_try("kernel32.dll", "AddVectoredExceptionHandler",
|
||||
(void*)AddVectoredExceptionHandler_hook,(void**)&AddVectoredExceptionHandler_orig);
|
||||
detour::trampoline_try("kernel32.dll", "GetCommandLineA",
|
||||
(void*)GetCommandLineA_hook, (void**)&GetCommandLineA_orig);
|
||||
detour::trampoline_try("user32.dll", "ShowCursor",
|
||||
(void*)ShowCursor_hook, (void**)&ShowCursor_orig);
|
||||
|
||||
commandLine = std::format("{} {}", GetCommandLineA_orig(), CCJ_INJECT_ARGS);
|
||||
trackball_thread_start();
|
||||
}
|
||||
|
||||
void CCJGame::detach() {
|
||||
Game::detach();
|
||||
|
||||
devicehook_dispose();
|
||||
trackball_thread_stop();
|
||||
}
|
||||
}
|
||||
16
games/ccj/ccj.h
Normal file
16
games/ccj/ccj.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "games/game.h"
|
||||
|
||||
namespace games::ccj {
|
||||
extern std::string CCJ_INJECT_ARGS;
|
||||
|
||||
class CCJGame : public games::Game {
|
||||
public:
|
||||
CCJGame() : Game("Chase Chase Jokers") {}
|
||||
|
||||
virtual void attach() override;
|
||||
virtual void post_attach() override;
|
||||
virtual void detach() override;
|
||||
};
|
||||
}
|
||||
73
games/ccj/io.cpp
Normal file
73
games/ccj/io.cpp
Normal file
@@ -0,0 +1,73 @@
|
||||
#include "io.h"
|
||||
|
||||
std::vector<Button> &games::ccj::get_buttons() {
|
||||
static std::vector<Button> buttons;
|
||||
|
||||
if (buttons.empty()) {
|
||||
buttons = GameAPI::Buttons::getButtons("Chase Chase Jokers");
|
||||
|
||||
GameAPI::Buttons::sortButtons(
|
||||
&buttons,
|
||||
"Service",
|
||||
"Test",
|
||||
"Coin Mech",
|
||||
"Joystick Up",
|
||||
"Joystick Down",
|
||||
"Joystick Left",
|
||||
"Joystick Right",
|
||||
"Dash",
|
||||
"Action",
|
||||
"Jump",
|
||||
"Slide",
|
||||
"Special",
|
||||
"Headphones",
|
||||
"Trackball Up",
|
||||
"Trackball Down",
|
||||
"Trackball Left",
|
||||
"Trackball Right"
|
||||
);
|
||||
}
|
||||
|
||||
return buttons;
|
||||
}
|
||||
|
||||
std::vector<Analog> &games::ccj::get_analogs() {
|
||||
static std::vector<Analog> analogs;
|
||||
|
||||
if (analogs.empty()) {
|
||||
analogs = GameAPI::Analogs::getAnalogs("Chase Chase Jokers");
|
||||
|
||||
GameAPI::Analogs::sortAnalogs(
|
||||
&analogs,
|
||||
"Joystick X",
|
||||
"Joystick Y",
|
||||
"Trackball DX",
|
||||
"Trackball DY"
|
||||
);
|
||||
}
|
||||
|
||||
return analogs;
|
||||
}
|
||||
|
||||
std::vector<Light> &games::ccj::get_lights() {
|
||||
static std::vector<Light> lights;
|
||||
|
||||
if (lights.empty()) {
|
||||
lights = GameAPI::Lights::getLights("Chase Chase Jokers");
|
||||
GameAPI::Lights::sortLights(
|
||||
&lights,
|
||||
"Title Panel R",
|
||||
"Title Panel G",
|
||||
"Title Panel B",
|
||||
"Side Panel R",
|
||||
"Side Panel G",
|
||||
"Side Panel B",
|
||||
"Card Reader R",
|
||||
"Card Reader G",
|
||||
"Card Reader B",
|
||||
"Special Button"
|
||||
);
|
||||
}
|
||||
|
||||
return lights;
|
||||
}
|
||||
58
games/ccj/io.h
Normal file
58
games/ccj/io.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "cfg/api.h"
|
||||
|
||||
namespace games::ccj {
|
||||
namespace Buttons {
|
||||
enum {
|
||||
Service,
|
||||
Test,
|
||||
CoinMech,
|
||||
Joystick_Up,
|
||||
Joystick_Down,
|
||||
Joystick_Left,
|
||||
Joystick_Right,
|
||||
Button_Dash,
|
||||
Button_Action,
|
||||
Button_Jump,
|
||||
Button_Slide,
|
||||
Button_Special,
|
||||
Headphones,
|
||||
Trackball_Up,
|
||||
Trackball_Down,
|
||||
Trackball_Left,
|
||||
Trackball_Right
|
||||
};
|
||||
}
|
||||
|
||||
namespace Analogs {
|
||||
enum {
|
||||
Joystick_X,
|
||||
Joystick_Y,
|
||||
Trackball_DX,
|
||||
Trackball_DY
|
||||
};
|
||||
}
|
||||
|
||||
// all lights in correct order
|
||||
namespace Lights {
|
||||
typedef enum {
|
||||
TitlePanel_R,
|
||||
TitlePanel_G,
|
||||
TitlePanel_B,
|
||||
SidePanel_R,
|
||||
SidePanel_G,
|
||||
SidePanel_B,
|
||||
CardReader_R,
|
||||
CardReader_G,
|
||||
CardReader_B,
|
||||
SpecialButton,
|
||||
} ccj_lights_t;
|
||||
}
|
||||
|
||||
// getters
|
||||
std::vector<Button> &get_buttons();
|
||||
std::vector<Analog> &get_analogs();
|
||||
std::vector<Light> &get_lights();
|
||||
}
|
||||
267
games/ccj/trackball.cpp
Normal file
267
games/ccj/trackball.cpp
Normal file
@@ -0,0 +1,267 @@
|
||||
#include "trackball.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "games/io.h"
|
||||
#include "io.h"
|
||||
|
||||
namespace games::ccj {
|
||||
bool MOUSE_TRACKBALL = false;
|
||||
bool MOUSE_TRACKBALL_USE_TOGGLE = false;
|
||||
uint8_t TRACKBALL_SENSITIVITY = 10;
|
||||
|
||||
static HANDLE fakeHandle = (HANDLE)0xDEADBEEF;
|
||||
static HWND hWnd = nullptr;
|
||||
static WNDPROC wndProc = nullptr;
|
||||
static std::thread *tbThread = nullptr;
|
||||
static bool tbThreadRunning = false;
|
||||
|
||||
static const wchar_t *fakeDeviceName = L"VID_1241&PID_1111";
|
||||
static const wchar_t *windowName = L"ChaseProject";
|
||||
|
||||
static decltype(GetRawInputDeviceList) *GetRawInputDeviceList_orig = nullptr;
|
||||
static decltype(GetRawInputDeviceInfoW) *GetRawInputDeviceInfoW_orig = nullptr;
|
||||
static decltype(SetWindowLongPtrW) *SetWindowLongPtrW_orig = nullptr;
|
||||
static decltype(GetRawInputData) *GetRawInputData_orig = nullptr;
|
||||
static decltype(RegisterRawInputDevices) *RegisterRawInputDevices_orig = nullptr;
|
||||
|
||||
static UINT WINAPI GetRawInputDeviceList_hook(PRAWINPUTDEVICELIST pRawInputDeviceList, PUINT puiNumDevices,
|
||||
UINT cbSize) {
|
||||
auto result = GetRawInputDeviceList_orig(pRawInputDeviceList, puiNumDevices, cbSize);
|
||||
if (result == 0xFFFFFFFF)
|
||||
return result;
|
||||
|
||||
if (pRawInputDeviceList == NULL) {
|
||||
(*puiNumDevices)++;
|
||||
} else if (result < *puiNumDevices) {
|
||||
pRawInputDeviceList[result] = {fakeHandle, 0};
|
||||
result++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static UINT WINAPI GetRawInputDeviceInfoW_hook(HANDLE hDevice, UINT uiCommand, LPVOID pData, PUINT pcbSize) {
|
||||
if (hDevice != fakeHandle || uiCommand != RIDI_DEVICENAME)
|
||||
return GetRawInputDeviceInfoW_orig(hDevice, uiCommand, pData, pcbSize);
|
||||
|
||||
const auto requiredLen = (wcslen(fakeDeviceName) + 1) * sizeof(wchar_t);
|
||||
|
||||
if (*pcbSize < requiredLen) {
|
||||
*pcbSize = requiredLen;
|
||||
return 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
if (pData == NULL) {
|
||||
*pcbSize = requiredLen;
|
||||
return 0;
|
||||
}
|
||||
|
||||
wcscpy((wchar_t*)pData, fakeDeviceName);
|
||||
return requiredLen;
|
||||
}
|
||||
|
||||
static LONG_PTR WINAPI SetWindowLongPtrW_hook(HWND _hWnd, int nIndex, LONG_PTR dwNewLong) {
|
||||
wchar_t buffer[256];
|
||||
if (nIndex != GWLP_WNDPROC || GetWindowTextW(_hWnd, buffer, 256) == 0 || !wcswcs(buffer, windowName))
|
||||
return SetWindowLongPtrW_orig(_hWnd, nIndex, dwNewLong);
|
||||
|
||||
hWnd = _hWnd;
|
||||
wndProc = (WNDPROC)dwNewLong;
|
||||
return SetWindowLongPtrW_orig(_hWnd, nIndex, dwNewLong);
|
||||
}
|
||||
|
||||
static UINT WINAPI GetRawInputData_hook(HRAWINPUT hRawInput, UINT uiCommand, LPVOID pData, PUINT pcbSize, UINT cbSizeHeader) {
|
||||
if (hRawInput != fakeHandle)
|
||||
return GetRawInputData_orig(hRawInput, uiCommand, pData, pcbSize, cbSizeHeader);
|
||||
|
||||
if (pData == NULL) {
|
||||
if (uiCommand == RID_HEADER)
|
||||
*pcbSize = sizeof(RAWINPUTHEADER);
|
||||
else
|
||||
*pcbSize = sizeof(RAWINPUT);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const RAWINPUTHEADER header = { RIM_TYPEMOUSE, sizeof(RAWINPUT), fakeHandle, 0 };
|
||||
|
||||
if (uiCommand == RID_HEADER) {
|
||||
if (*pcbSize < sizeof(RAWINPUTHEADER)) {
|
||||
SetLastError(ERROR_INSUFFICIENT_BUFFER);
|
||||
return 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
*((RAWINPUTHEADER*)pData) = header;
|
||||
return sizeof(RAWINPUTHEADER);
|
||||
} else if (uiCommand == RID_INPUT) {
|
||||
if (*pcbSize < sizeof(RAWINPUT)) {
|
||||
SetLastError(ERROR_INSUFFICIENT_BUFFER);
|
||||
return 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
RAWMOUSE rawMouse {};
|
||||
|
||||
if (MOUSE_TRACKBALL) {
|
||||
static bool active = false;
|
||||
static bool lastState = false;
|
||||
|
||||
static std::chrono::time_point<std::chrono::steady_clock> lastModified = std::chrono::steady_clock::now();
|
||||
static std::chrono::milliseconds debounceDuration(100);
|
||||
auto currentTime = std::chrono::steady_clock::now();
|
||||
bool pressed = GetAsyncKeyState(VK_RBUTTON) & 0x8000;
|
||||
bool focused = GetForegroundWindow() == hWnd;
|
||||
|
||||
if (focused && MOUSE_TRACKBALL_USE_TOGGLE && pressed && (currentTime - lastModified > debounceDuration)) {
|
||||
active = !active;
|
||||
lastModified = currentTime;
|
||||
}
|
||||
|
||||
if (focused && ((MOUSE_TRACKBALL_USE_TOGGLE && active) || (!MOUSE_TRACKBALL_USE_TOGGLE && pressed))) {
|
||||
POINT cursor;
|
||||
RECT client;
|
||||
|
||||
GetClientRect(hWnd, &client);
|
||||
int sx = client.right - client.left;
|
||||
int sy = client.bottom - client.top;
|
||||
|
||||
GetCursorPos(&cursor);
|
||||
ScreenToClient(hWnd, &cursor);
|
||||
|
||||
static int lastX = cursor.x;
|
||||
static int lastY = cursor.y;
|
||||
|
||||
if (!lastState) {
|
||||
lastX = cursor.x;
|
||||
lastY = cursor.y;
|
||||
lastState = true;
|
||||
}
|
||||
|
||||
rawMouse.usFlags = MOUSE_MOVE_RELATIVE;
|
||||
rawMouse.lLastX = (int)((float)(cursor.x - lastX) * (float)TRACKBALL_SENSITIVITY / 20.0f);
|
||||
rawMouse.lLastY = (int)((float)(lastY - cursor.y) * (float)TRACKBALL_SENSITIVITY / 20.0f);
|
||||
|
||||
bool updateCursor = false;
|
||||
|
||||
if (cursor.x <= 0) {
|
||||
updateCursor = true;
|
||||
cursor.x = sx - 5;
|
||||
} else if (cursor.x >= sx - 1) {
|
||||
updateCursor = true;
|
||||
cursor.x = 5;
|
||||
}
|
||||
|
||||
if (cursor.y <= 0) {
|
||||
updateCursor = true;
|
||||
cursor.y = sy - 5;
|
||||
} else if (cursor.y >= sy - 1) {
|
||||
updateCursor = true;
|
||||
cursor.y = 5;
|
||||
}
|
||||
|
||||
lastX = cursor.x;
|
||||
lastY = cursor.y;
|
||||
|
||||
if (updateCursor) {
|
||||
ClientToScreen(hWnd, &cursor);
|
||||
SetCursorPos(cursor.x, cursor.y);
|
||||
}
|
||||
} else if (lastState && !active) {
|
||||
lastState = false;
|
||||
}
|
||||
} else {
|
||||
rawMouse.usFlags = MOUSE_MOVE_RELATIVE;
|
||||
|
||||
auto &analogs = get_analogs();
|
||||
if (analogs[Analogs::Trackball_DX].isSet() || analogs[Analogs::Trackball_DY].isSet()) {
|
||||
float x = GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::Trackball_DX]) * 2.0f - 1.0f;
|
||||
float y = GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::Trackball_DY]) * 2.0f - 1.0f;
|
||||
rawMouse.lLastX = (long) (x * (float) TRACKBALL_SENSITIVITY);
|
||||
rawMouse.lLastY = (long) (-y * (float) TRACKBALL_SENSITIVITY);
|
||||
}
|
||||
|
||||
auto &buttons = get_buttons();
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Trackball_Up]))
|
||||
rawMouse.lLastY = TRACKBALL_SENSITIVITY;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Trackball_Down]))
|
||||
rawMouse.lLastY = -TRACKBALL_SENSITIVITY;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Trackball_Left]))
|
||||
rawMouse.lLastX = -TRACKBALL_SENSITIVITY;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Trackball_Right]))
|
||||
rawMouse.lLastX = TRACKBALL_SENSITIVITY;
|
||||
}
|
||||
|
||||
*((RAWINPUT*)pData) = { header, { rawMouse } };
|
||||
return sizeof(RAWINPUT);
|
||||
}
|
||||
|
||||
return 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
static BOOL WINAPI RegisterRawInputDevices_hook(PCRAWINPUTDEVICE pRawInputDevices, UINT uiNumDevices, UINT cbSize) {
|
||||
if (uiNumDevices == 2 && pRawInputDevices[1].usUsage == HID_USAGE_GENERIC_GAMEPAD)
|
||||
uiNumDevices = 1;
|
||||
|
||||
return RegisterRawInputDevices_orig(pRawInputDevices, uiNumDevices, cbSize);
|
||||
}
|
||||
|
||||
|
||||
void trackball_hook_init() {
|
||||
// avoid double init
|
||||
static bool initialized = false;
|
||||
if (initialized) {
|
||||
return;
|
||||
} else {
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
// announce
|
||||
log_info("trackball", "init");
|
||||
|
||||
// user32
|
||||
const auto user32Dll = "user32.dll";
|
||||
detour::trampoline_try(user32Dll, "GetRawInputDeviceList",
|
||||
GetRawInputDeviceList_hook, &GetRawInputDeviceList_orig);
|
||||
detour::trampoline_try(user32Dll, "GetRawInputDeviceInfoW",
|
||||
GetRawInputDeviceInfoW_hook, &GetRawInputDeviceInfoW_orig);
|
||||
detour::trampoline_try(user32Dll, "SetWindowLongPtrW",
|
||||
SetWindowLongPtrW_hook, &SetWindowLongPtrW_orig);
|
||||
detour::trampoline_try(user32Dll, "GetRawInputData",
|
||||
GetRawInputData_hook, &GetRawInputData_orig);
|
||||
detour::trampoline_try(user32Dll, "RegisterRawInputDevices",
|
||||
RegisterRawInputDevices_hook, &RegisterRawInputDevices_orig);
|
||||
}
|
||||
|
||||
void trackball_thread_start() {
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
tbThreadRunning = true;
|
||||
|
||||
log_info("trackball", "thread start, use mouse: {}, toggle: {}", MOUSE_TRACKBALL, MOUSE_TRACKBALL_USE_TOGGLE);
|
||||
|
||||
tbThread = new std::thread([&] {
|
||||
while (tbThreadRunning) {
|
||||
if (hWnd && wndProc) {
|
||||
wndProc(hWnd, WM_INPUT, RIM_INPUT, (LPARAM)fakeHandle);
|
||||
}
|
||||
|
||||
if (!tbThreadRunning)
|
||||
break;
|
||||
|
||||
std::this_thread::sleep_for(10ms);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void trackball_thread_stop() {
|
||||
tbThreadRunning = false;
|
||||
if (tbThread)
|
||||
tbThread->join();
|
||||
|
||||
log_info("trackball", "thread stop");
|
||||
|
||||
delete tbThread;
|
||||
}
|
||||
}
|
||||
12
games/ccj/trackball.h
Normal file
12
games/ccj/trackball.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace games::ccj {
|
||||
extern bool MOUSE_TRACKBALL;
|
||||
extern bool MOUSE_TRACKBALL_USE_TOGGLE;
|
||||
extern uint8_t TRACKBALL_SENSITIVITY;
|
||||
|
||||
void trackball_hook_init();
|
||||
void trackball_thread_start();
|
||||
void trackball_thread_stop();
|
||||
}
|
||||
Reference in New Issue
Block a user