diff --git a/src/spice2x/CMakeLists.txt b/src/spice2x/CMakeLists.txt index fa9aa2b..aaa4c7d 100644 --- a/src/spice2x/CMakeLists.txt +++ b/src/spice2x/CMakeLists.txt @@ -401,6 +401,10 @@ set(SOURCE_FILES ${SOURCE_FILES} games/nost/poke.cpp games/gitadora/gitadora.cpp games/gitadora/io.cpp + games/gitadora/handle.cpp + games/gitadora/j32d.cpp + games/gitadora/j33i.cpp + games/gitadora/bi2x_hook.cpp games/mga/mga.cpp games/mga/io.cpp games/mga/gunio.cpp @@ -533,6 +537,7 @@ set(SOURCE_FILES ${SOURCE_FILES} misc/sciunit.cpp misc/sde.cpp misc/wintouchemu.cpp + misc/ami2000.cpp # nvapi nvapi/nvapi.cpp diff --git a/src/spice2x/games/gitadora/bi2x_hook.cpp b/src/spice2x/games/gitadora/bi2x_hook.cpp new file mode 100644 index 0000000..2ae60ae --- /dev/null +++ b/src/spice2x/games/gitadora/bi2x_hook.cpp @@ -0,0 +1,664 @@ +#include "bi2x_hook.h" + +#include +#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" +#include + +namespace games::gitadora { + + // /* + // * class definitions + // */ + + struct AIO_NMGR_IOB2 { + }; + + struct AIO_IOB2_BI2X_AC1__SETTING { + }; + + struct AIO_NMGR_IOB5 { + }; + + struct AIO_SCI_COMM { + }; + + struct AIO_IOB5_Y32D { + }; + + // struct AIO_IOB5_Y33I { + // }; + + struct AIO_IOB2_BI2X_AC1 { + }; + + struct AIO_NMGR_IOB__NODEINFO { + uint8_t data[0xA8]; + }; + + struct AIO_IOB2_BI2X_WRFIRM { + }; + + struct AIO_SCI_COMM_T { + uint64_t unk_0; + struct AIO_SCI_COMM *aioSciComm; + }; + + struct AIO_COMM_STATUS { + uint64_t unk[5]; + }; + + struct AIO_IOB2_BI2X_AC1__INPUT { + uint8_t DevIoCounter; + uint8_t bExIoAErr; + uint8_t bExIoBErr; + uint8_t bPcPowerOn; + uint8_t bPcPowerCheck; + uint8_t bCoin1Jam; + uint8_t bCoin2Jam; + uint8_t bCoin3Jam; + uint8_t bCoin4Jam; + uint8_t Coin1Count; + uint8_t Coin2Count; + uint8_t Coin3Count; + uint8_t Coin4Count; + uint16_t AnalogCh1; + uint16_t AnalogCh2; + uint16_t AnalogCh3; + uint16_t AnalogCh4; + uint8_t CN8_8; + uint8_t CN8_9; + uint8_t CN8_10; + uint8_t CN9_8; + uint8_t CN9_9; + uint8_t CN9_10; + uint8_t CN11_11; + uint8_t CN11_12; + uint8_t CN11_13; + uint8_t CN11_14; + uint8_t CN11_15; + uint8_t CN11_16; + uint8_t CN11_17; + uint8_t CN11_18; + uint8_t CN11_19; + uint8_t CN11_20; + uint8_t CN12_11; + uint8_t CN12_12; + uint8_t CN12_13; + uint8_t CN12_14; + uint8_t CN12_15; + uint8_t CN12_16; + uint8_t CN12_17; + uint8_t CN12_18; + uint8_t CN12_19; + uint8_t CN12_20; + uint8_t CN12_21; + uint8_t CN12_22; + uint8_t CN12_23; + uint8_t CN12_24; + uint8_t CN15_3; + uint8_t CN15_4; + uint8_t CN15_5; + uint8_t CN15_6; + uint8_t CN15_7; + uint8_t CN15_8; + uint8_t CN15_9; + uint8_t CN15_10; + uint8_t CN15_11; + uint8_t CN15_12; + uint8_t CN15_13; + uint8_t CN15_14; + uint8_t CN15_15; + uint8_t CN15_16; + uint8_t CN15_17; + uint8_t CN15_18; + uint8_t CN15_19; + uint8_t CN15_20; + uint8_t CN19_8; + uint8_t CN19_9; + uint8_t CN19_10; + uint8_t CN19_11; + uint8_t CN19_12; + uint8_t CN19_13; + uint8_t CN19_14; + uint8_t CN19_15; + }; + + struct AIO_IOB2_BI2X_AC1__INPUTDATA { + uint8_t data[247]; + }; + + struct AIO_IOB2_BI2X_AC1__OUTPUTDATA { + uint8_t data[48]; + }; + + struct AIO_IOB2_BI2X_AC1__ICNPIN { + uint16_t Ain[4]; + uint64_t CnPin; + }; + + struct AIO_IOB2_BI2X_AC1__DEVSTATUS { + uint8_t InputCounter; + uint8_t OutputCounter; + uint8_t IoResetCounter; + uint8_t TapeLedCounter; + uint8_t TapeLedRate[8]; + AIO_IOB2_BI2X_AC1__INPUT Input; + AIO_IOB2_BI2X_AC1__INPUTDATA InputData; + AIO_IOB2_BI2X_AC1__OUTPUTDATA OutputData; + AIO_IOB2_BI2X_AC1__ICNPIN ICnPinHist[20]; + }; + + struct AIO_IOB5_Y32D__DEVSTATUS { + uint8_t unk[0xB1]; + }; + + /* + * typedefs + */ + + // libaio-iob2_video.dll + typedef AIO_IOB2_BI2X_AC1 *(__fastcall *aioIob2Bi2xAC1_Create_t)(AIO_NMGR_IOB2 *i_pNodeMgr, uint32_t i_DevId, + AIO_IOB2_BI2X_AC1__SETTING *i_Setting); + typedef void(__fastcall *aioIob2Bi2xAC1_GetDeviceStatus_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, + AIO_IOB2_BI2X_AC1__DEVSTATUS *o_DevStatus); + typedef void(__fastcall *aioIob2Bi2xAC1_SetWatchDogTimer_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint8_t i_Count); + typedef void(__fastcall *aioIob2Bi2xAC1_AddCounter_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_Counter, + uint32_t i_Count); + typedef void(__fastcall *aioIob2Bi2xAC1_SetOutputData_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_CnPin, + uint8_t i_Data); + typedef void(__fastcall *aioIob2Bi2xAC1_SetTapeLedDataPart_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_TapeLedCh, + uint32_t i_Offset, uint8_t *i_pData, + uint32_t i_cntTapeLed, bool i_bReverse); + typedef void(__fastcall *aioIob2Bi2x_SetTapeLedDataLimit_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_Channel, + uint8_t i_Scale, uint8_t i_Limit); + 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); + + // 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_IOB5_Y32D *i_pNodeCtl, + AIO_NMGR_IOB__NODEINFO *o_NodeInfo); + + // libaio.dll + typedef AIO_SCI_COMM_T *(__fastcall *aioSciComm_Open_t)(AIO_SCI_COMM_T *unk); + typedef void(__fastcall *aioSci_Destroy_t)(AIO_SCI_COMM *i_pNodeMgr); + typedef void(__fastcall *aioSci_GetCommStatus_t)(AIO_SCI_COMM *i_pNodeMgr, AIO_COMM_STATUS *i_Status); + typedef void(__fastcall *aioNodeMgr_Destroy_t)(AIO_NMGR_IOB5 *i_pNodeMgr); + typedef int32_t(__fastcall *aioNodeMgr_GetState_t)(AIO_NMGR_IOB5 *i_pNodeMgr); + typedef bool(__fastcall *aioNodeMgr_IsReady_t)(AIO_NMGR_IOB5 *i_pNodeMgr, int32_t i_State); + typedef bool(__fastcall *aioNodeMgr_IsError_t)(AIO_NMGR_IOB5 *i_pNodeMgr, int32_t i_State); + typedef void(__fastcall *aioNodeCtl_Destroy_t)(AIO_IOB5_Y32D *i_pNodeCtl); + typedef int32_t(__fastcall *aioNodeCtl_GetState_t)(AIO_IOB5_Y32D *i_pNodeCtl); + typedef bool(__fastcall *aioNodeCtl_IsReady_t)(AIO_IOB5_Y32D *i_pNodeCtl, int32_t i_State); + typedef bool(__fastcall *aioNodeCtl_IsError_t)(AIO_IOB5_Y32D *i_pNodeCtl, int32_t i_State); + typedef void(__fastcall *aioNodeCtl_UpdateDevicesStatus_t)(); + + // libaio-iob5.dll + typedef AIO_NMGR_IOB5 *(__fastcall *aioNMgrIob5_Create_t)(AIO_SCI_COMM *i_pSci, uint32_t i_bfMode); + + // libaio-iob5_y32.dll + typedef AIO_IOB5_Y32D *(__fastcall *aioIob5Y32d_Create_t)(AIO_NMGR_IOB5 *i_pSci, uint32_t i_bfMode); + // typedef AIO_IOB5_Y33I *(__fastcall *aioIob5Y33i_Create_t)(AIO_NMGR_IOB5 *i_pSci, uint32_t i_bfMode); + + typedef void(__fastcall *aioIob5Y32d_GetDeviceStatus_t)(AIO_IOB5_Y32D *i_pNodeCtl, + AIO_IOB5_Y32D__DEVSTATUS *o_DevStatus); + // typedef void(__fastcall *aioIob5Y33i_GetDeviceStatus_t)(AIO_IOB5_Y33I *i_pNodeCtl, + // AIO_IOB2_BI2X_AC1__DEVSTATUS *o_DevStatus); + + /* + * function pointers + */ + + // libaio-iob2_video.dll + static aioIob2Bi2xAC1_Create_t aioIob2Bi2xAC1_Create_orig = nullptr; + static aioIob2Bi2xAC1_GetDeviceStatus_t aioIob2Bi2xAC1_GetDeviceStatus_orig = nullptr; + static aioIob2Bi2xAC1_SetWatchDogTimer_t aioIob2Bi2xAC1_SetWatchDogTimer_orig = nullptr; + static aioIob2Bi2xAC1_AddCounter_t aioIob2Bi2xAC1_AddCounter_orig = nullptr; + static aioIob2Bi2xAC1_SetOutputData_t aioIob2Bi2xAC1_SetOutputData_orig = nullptr; + static aioIob2Bi2xAC1_SetTapeLedDataPart_t aioIob2Bi2xAC1_SetTapeLedDataPart_orig = nullptr; + static aioIob2Bi2x_SetTapeLedDataLimit_t aioIob2Bi2x_SetTapeLedDataLimit_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; + + // libaio.dll + static aioSciComm_Open_t aioSciComm_Open_orig = nullptr; + static aioSci_Destroy_t aioSci_Destroy_orig = nullptr; + static aioSci_GetCommStatus_t aioSci_GetCommStatus_orig = nullptr; + 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; + static aioNodeCtl_UpdateDevicesStatus_t aioNodeCtl_UpdateDevicesStatus_orig = nullptr; + + // libaio-iob5.dll + static aioNMgrIob5_Create_t aioNMgrIob5_Create_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-iob5_y32.dll + static aioIob5Y32d_Create_t aioIob5Y32d_Create_orig = nullptr; + static aioIob5Y32d_GetDeviceStatus_t aioIob5Y32d_GetDeviceStatus_orig = nullptr; + + /* + * variables + */ + static AIO_IOB2_BI2X_AC1 *aioIob2Bi2xAc1; + static AIO_NMGR_IOB2 *aioNmgrIob2; + static AIO_NMGR_IOB5 *aioNmgrIob5; + static AIO_IOB5_Y32D *aioIob5Y32d; + static AIO_SCI_COMM *aioSciComm; + static AIO_IOB2_BI2X_WRFIRM *aioIob2Bi2xWrfirm; + + /* + * implementations + */ + + // libaio-iob2_video.dll + + static AIO_IOB2_BI2X_AC1 *__fastcall aioIob2Bi2xAC1_Create(AIO_NMGR_IOB2 *i_pNodeMgr, uint32_t i_DevId, + AIO_IOB2_BI2X_AC1__SETTING *i_Setting) { + + if (i_pNodeMgr == aioNmgrIob2) { + aioIob2Bi2xAc1 = new AIO_IOB2_BI2X_AC1; + return aioIob2Bi2xAc1; + } else { + return aioIob2Bi2xAC1_Create_orig(i_pNodeMgr, i_DevId, i_Setting); + } + } + + void __fastcall aioIob2Bi2xAC1_GetDeviceStatus(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, + AIO_IOB2_BI2X_AC1__DEVSTATUS *o_DevStatus) { + + RI_MGR->devices_flush_output(); + if (i_pNodeCtl != aioIob2Bi2xAc1) { + return aioIob2Bi2xAC1_GetDeviceStatus_orig(i_pNodeCtl, o_DevStatus); + } + + memset(o_DevStatus, 0x00, sizeof(AIO_IOB2_BI2X_AC1__DEVSTATUS)); + + + auto &buttons = get_buttons(); + // struct may be misaligned + o_DevStatus->Input.CN8_10 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test]) ? 0 : 0xFF; + o_DevStatus->Input.CN9_8 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service]) ? 0 : 0xFF; + o_DevStatus->Input.CN9_9 = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Coin]) ? 0 : 0xFF; + + // coin + o_DevStatus->Input.Coin1Count = eamuse_coin_get_stock(); + } + + void __fastcall aioIob2Bi2xAC1_SetWatchDogTimer(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint8_t i_Count) { + + if (i_pNodeCtl != aioIob2Bi2xAc1) { + return aioIob2Bi2xAC1_SetWatchDogTimer_orig(i_pNodeCtl, i_Count); + } + } + + void __fastcall aioIob2Bi2xAC1_AddCounter(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_Counter, uint32_t i_Count) { + + if (i_pNodeCtl == aioIob2Bi2xAc1 && i_Count == 0) { + eamuse_coin_set_stock((uint16_t)i_Count); + } else { + return aioIob2Bi2xAC1_AddCounter_orig(i_pNodeCtl, i_Counter, i_Count); + } + } + + void __fastcall aioIob2Bi2xAC1_SetOutputData(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_CnPin, uint8_t i_Data) { + + if (i_pNodeCtl != aioIob2Bi2xAc1) { + return aioIob2Bi2xAC1_SetOutputData_orig(i_pNodeCtl, i_CnPin, i_Data); + } + } + + void __fastcall aioIob2Bi2xAC1_SetTapeLedDataPart(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_TapeLedCh, + uint32_t i_Offset, uint8_t *i_pData, + uint32_t i_cntTapeLed, bool i_bReverse) { + + if (i_pNodeCtl != aioIob2Bi2xAc1) { + return aioIob2Bi2xAC1_SetTapeLedDataPart_orig(i_pNodeCtl, i_TapeLedCh, i_Offset, i_pData, i_cntTapeLed, i_bReverse); + } + } + + void __fastcall aioIob2Bi2x_SetTapeLedDataLimit(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_Channel, + uint8_t i_Scale, uint8_t i_Limit) { + + if (i_pNodeCtl != aioIob2Bi2xAc1) { + return aioIob2Bi2x_SetTapeLedDataLimit_orig(i_pNodeCtl, i_Channel, i_Scale, i_Limit); + } + } + + 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; + } else { + return aioIob2Bi2x_WriteFirmIsCompleted_orig(i_State); + } + } + + // libaio-iob.dll + 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_IOB5_Y32D *i_pNodeCtl, AIO_NMGR_IOB__NODEINFO *o_NodeInfo) { + + if (i_pNodeCtl == aioIob5Y32d) { + memset(o_NodeInfo, 0, sizeof(AIO_NMGR_IOB__NODEINFO)); + } else { + return aioNCtlIob_GetNodeInfo_orig(i_pNodeCtl, o_NodeInfo); + } + } + + // libaio.dll + + static AIO_SCI_COMM_T *__fastcall aioSciComm_Open(AIO_SCI_COMM_T *unk) { + + aioSciComm = new AIO_SCI_COMM; + memset(unk, 0, sizeof(AIO_SCI_COMM_T)); + unk->aioSciComm = aioSciComm; + return unk; + } + + static void __fastcall aioSci_GetCommStatus(AIO_SCI_COMM *i_pNodeMgr, AIO_COMM_STATUS *i_Status) { + + if (i_pNodeMgr == aioSciComm) { + memset(i_Status, 0, sizeof(AIO_COMM_STATUS)); + } else { + return aioSci_GetCommStatus_orig(i_pNodeMgr, i_Status); + } + } + + static void __fastcall aioSci_Destroy(AIO_SCI_COMM *i_pNodeMgr) { + + if (i_pNodeMgr == aioSciComm) { + delete aioSciComm; + } else { + return aioSci_Destroy_orig(i_pNodeMgr); + } + } + + static void __fastcall aioNodeMgr_Destroy(AIO_NMGR_IOB5 *i_pNodeMgr) { + + if (i_pNodeMgr == aioNmgrIob5 || + i_pNodeMgr == (AIO_NMGR_IOB5 *)aioIob2Bi2xAc1 || + i_pNodeMgr == (AIO_NMGR_IOB5 *)aioNmgrIob2) { + + delete aioNmgrIob5; + aioNmgrIob5 = nullptr; + } else { + return aioNodeMgr_Destroy_orig(i_pNodeMgr); + } + } + + static int32_t __fastcall aioNodeMgr_GetState(AIO_NMGR_IOB5 *i_pNodeMgr) { + + if (i_pNodeMgr == aioNmgrIob5 || + i_pNodeMgr == (AIO_NMGR_IOB5 *)aioIob2Bi2xAc1 || + i_pNodeMgr == (AIO_NMGR_IOB5 *)aioNmgrIob2) { + return 1; + } else { + return aioNodeMgr_GetState_orig(i_pNodeMgr); + } + } + + static bool __fastcall aioNodeMgr_IsReady(AIO_NMGR_IOB5 *i_pNodeMgr, int32_t i_State) { + + if (i_pNodeMgr == aioNmgrIob5 || + i_pNodeMgr == (AIO_NMGR_IOB5 *)aioIob2Bi2xAc1 || + i_pNodeMgr == (AIO_NMGR_IOB5 *)aioNmgrIob2) { + + return true; + } else { + return aioNodeMgr_IsReady_orig(i_pNodeMgr, i_State); + } + } + + static bool __fastcall aioNodeMgr_IsError(AIO_NMGR_IOB5 *i_pNodeMgr, int32_t i_State) { + + if (i_pNodeMgr == aioNmgrIob5 || + i_pNodeMgr == (AIO_NMGR_IOB5 *)aioIob2Bi2xAc1 || + i_pNodeMgr == (AIO_NMGR_IOB5 *)aioNmgrIob2) { + + return false; + } else { + return aioNodeMgr_IsError_orig(i_pNodeMgr, i_State); + } + } + + static void __fastcall aioNodeCtl_Destroy(AIO_IOB5_Y32D *i_pNodeCtl) { + + if (i_pNodeCtl == aioIob5Y32d || + i_pNodeCtl == (AIO_IOB5_Y32D *)aioIob2Bi2xAc1 || + i_pNodeCtl == (AIO_IOB5_Y32D *)aioNmgrIob2) { + + delete aioIob5Y32d; + aioIob5Y32d = nullptr; + } else { + return aioNodeCtl_Destroy_orig(i_pNodeCtl); + } + } + + static int32_t __fastcall aioNodeCtl_GetState(AIO_IOB5_Y32D *i_pNodeCtl) { + + if (i_pNodeCtl == aioIob5Y32d || + i_pNodeCtl == (AIO_IOB5_Y32D *)aioIob2Bi2xAc1 || + i_pNodeCtl == (AIO_IOB5_Y32D *)aioNmgrIob2) { + + return 1; + } else { + return aioNodeCtl_GetState_orig(i_pNodeCtl); + } + } + + static bool __fastcall aioNodeCtl_IsReady(AIO_IOB5_Y32D *i_pNodeCtl, int32_t i_State) { + + if (i_pNodeCtl == aioIob5Y32d || + i_pNodeCtl == (AIO_IOB5_Y32D *)aioIob2Bi2xAc1 || + i_pNodeCtl == (AIO_IOB5_Y32D *)aioNmgrIob2) { + + return true; + } else { + return aioNodeCtl_IsReady_orig(i_pNodeCtl, i_State); + } + } + + static bool __fastcall aioNodeCtl_IsError(AIO_IOB5_Y32D *i_pNodeCtl, int32_t i_State) { + + if (i_pNodeCtl == aioIob5Y32d || + i_pNodeCtl == (AIO_IOB5_Y32D *)aioIob2Bi2xAc1 || + i_pNodeCtl == (AIO_IOB5_Y32D *)aioNmgrIob2) { + + return false; + } else { + return aioNodeCtl_IsError_orig(i_pNodeCtl, i_State); + } + } + + static void __fastcall aioNodeCtl_UpdateDevicesStatus() { + } + + // libaio-iob5.dll + + static AIO_NMGR_IOB5 *__fastcall aioNMgrIob5_Create(AIO_SCI_COMM *i_pSci, uint32_t i_bfMode) { + + if (i_pSci == aioSciComm) { + aioNmgrIob5 = new AIO_NMGR_IOB5; + return aioNmgrIob5; + } else { + return aioNMgrIob5_Create_orig(i_pSci, i_bfMode); + } + } + + // libaio-iob5_y32.dll + static AIO_IOB5_Y32D *__fastcall aioIob5Y32d_Create(AIO_NMGR_IOB5 *i_pSci, uint32_t i_bfMode) { + + if (i_pSci == aioNmgrIob5) { + aioIob5Y32d = new AIO_IOB5_Y32D; + return aioIob5Y32d; + } else { + return aioIob5Y32d_Create_orig(i_pSci, i_bfMode); + } + } + + void __fastcall aioIob5Y32d_GetDeviceStatus(AIO_IOB5_Y32D *i_pNodeCtl, + AIO_IOB5_Y32D__DEVSTATUS *o_DevStatus) { + + if (i_pNodeCtl != aioIob5Y32d) { + return aioIob5Y32d_GetDeviceStatus_orig(i_pNodeCtl, o_DevStatus); + } + + memset(o_DevStatus, 0, sizeof(AIO_IOB5_Y32D__DEVSTATUS)); + } + + void bi2x_hook_init() { + + // avoid double init + static bool initialized = false; + if (initialized) { + return; + } else { + initialized = true; + } + + // announce + log_info("bi2x_hook", "init"); + + // libaio-iob5_y32.dll + const auto libaioIob5Y32Dll = "libaio-iob5_y32.dll"; + detour::trampoline_try(libaioIob5Y32Dll, "aioIob5Y32d_Create", + aioIob5Y32d_Create, &aioIob5Y32d_Create_orig); + detour::trampoline_try(libaioIob5Y32Dll, "aioIob5Y32i_Create", + aioIob5Y32d_Create, &aioIob5Y32d_Create_orig); + detour::trampoline_try(libaioIob5Y32Dll, "aioIob5Y32d_GetDeviceStatus", + aioIob5Y32d_GetDeviceStatus, &aioIob5Y32d_GetDeviceStatus_orig); + detour::trampoline_try(libaioIob5Y32Dll, "aioIob5Y32i_GetDeviceStatus", + aioIob5Y32d_GetDeviceStatus, &aioIob5Y32d_GetDeviceStatus_orig); + + // libaio-iob5.dll + const auto libaioIob5Dll = "libaio-iob5.dll"; + detour::trampoline_try(libaioIob5Dll, "aioNMgrIob5_Create", + aioNMgrIob5_Create, &aioNMgrIob5_Create_orig); + + // libaio-iob2_video.dll + const auto libaioIob2VideoDll = "libaio-iob2_video.dll"; + detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_Create", + aioIob2Bi2xAC1_Create, &aioIob2Bi2xAC1_Create_orig); + detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_GetDeviceStatus", + aioIob2Bi2xAC1_GetDeviceStatus, &aioIob2Bi2xAC1_GetDeviceStatus_orig); + detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_SetWatchDogTimer", + aioIob2Bi2xAC1_SetWatchDogTimer, &aioIob2Bi2xAC1_SetWatchDogTimer_orig); + detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_AddCounter", + aioIob2Bi2xAC1_AddCounter, &aioIob2Bi2xAC1_AddCounter_orig); + detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_SetOutputData", + aioIob2Bi2xAC1_SetOutputData, &aioIob2Bi2xAC1_SetOutputData_orig); + detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_SetTapeLedDataPart", + aioIob2Bi2xAC1_SetTapeLedDataPart, &aioIob2Bi2xAC1_SetTapeLedDataPart_orig); + detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2x_SetTapeLedDataLimit", + aioIob2Bi2x_SetTapeLedDataLimit, &aioIob2Bi2x_SetTapeLedDataLimit_orig); + detour::trampoline_try(libaioIob2VideoDll, "?CreateWriteFirmContext@AIO_IOB2_BI2X@@SAPEAUWRFIRM@1@II@Z", + aioIob2Bi2x_CreateWriteFirmContext, &aioIob2Bi2x_CreateWriteFirmContext_orig); + detour::trampoline_try(libaioIob2VideoDll, "?DestroyWriteFirmContext@AIO_IOB2_BI2X@@SAXPEAUWRFIRM@1@@Z", + aioIob2Bi2x_DestroyWriteFirmContext, &aioIob2Bi2x_DestroyWriteFirmContext_orig); + detour::trampoline_try(libaioIob2VideoDll, "?WriteFirmGetState@AIO_IOB2_BI2X@@SAHPEAUWRFIRM@1@@Z", + aioIob2Bi2x_WriteFirmGetState, &aioIob2Bi2x_WriteFirmGetState_orig); + detour::trampoline_try(libaioIob2VideoDll, "?WriteFirmIsCompleted@AIO_IOB2_BI2X@@SA_NH@Z", + aioIob2Bi2x_WriteFirmIsCompleted, &aioIob2Bi2x_WriteFirmIsCompleted_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, "?Open@AIO_SCI_COMM@@SA?AU?$AC_ERESULT_T@PEAVAIO_SCI_COMM@@@@PEBD0AEBUSETTING@1@@Z", + aioSciComm_Open, &aioSciComm_Open_orig); + detour::trampoline_try(libaioDll, "aioSci_GetCommStatus", + aioSci_GetCommStatus, &aioSci_GetCommStatus_orig); + detour::trampoline_try(libaioDll, "aioSci_Destroy", + aioSci_Destroy, &aioSci_Destroy_orig); + 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); + detour::trampoline_try(libaioDll, "aioNodeCtl_UpdateDevicesStatus", + aioNodeCtl_UpdateDevicesStatus, &aioNodeCtl_UpdateDevicesStatus_orig); + } +} \ No newline at end of file diff --git a/src/spice2x/games/gitadora/bi2x_hook.h b/src/spice2x/games/gitadora/bi2x_hook.h new file mode 100644 index 0000000..8572bd9 --- /dev/null +++ b/src/spice2x/games/gitadora/bi2x_hook.h @@ -0,0 +1,5 @@ +#pragma once + +namespace games::gitadora { + void bi2x_hook_init(); +} \ No newline at end of file diff --git a/src/spice2x/games/gitadora/device.h b/src/spice2x/games/gitadora/device.h new file mode 100644 index 0000000..cf34ac2 --- /dev/null +++ b/src/spice2x/games/gitadora/device.h @@ -0,0 +1,10 @@ +#pragma once + +#include "acioemu/acioemu.h" + +namespace games::gitadora { + class GitadoraDevice : public acioemu::ACIODeviceEmu { + public: + bool is_ready = false; + }; +} \ No newline at end of file diff --git a/src/spice2x/games/gitadora/gitadora.cpp b/src/spice2x/games/gitadora/gitadora.cpp index 4b108ae..2419a5c 100644 --- a/src/spice2x/games/gitadora/gitadora.cpp +++ b/src/spice2x/games/gitadora/gitadora.cpp @@ -1,7 +1,7 @@ #include "gitadora.h" - +#include "handle.h" +#include "bi2x_hook.h" #include - #include "cfg/configurator.h" #include "hooks/graphics/graphics.h" #include "util/cpuutils.h" @@ -9,6 +9,7 @@ #include "util/libutils.h" #include "util/logging.h" #include "util/sigscan.h" +#include "hooks/setupapihook.h" namespace games::gitadora { @@ -214,6 +215,29 @@ namespace games::gitadora { system_module, "sys_debug_dip_set_param")); #ifdef SPICE64 + // gitadora arena model + auto aio = libutils::try_library("libaio.dll"); + if (aio != nullptr) { + SETUPAPI_SETTINGS settings{}; + settings.class_guid[0] = 0x86E0D1E0; + settings.class_guid[1] = 0x11D08089; + settings.class_guid[2] = 0x0008E49C; + settings.class_guid[3] = 0x731F303E; + const char property[] = "1CCF(8050)_000"; + const char property_hardwareid[] = "USB\\VID_1CCF&PID_8050&MI_00\\000"; + memcpy(settings.property_devicedesc, property, sizeof(property)); + memcpy(settings.property_hardwareid, property_hardwareid, sizeof(property_hardwareid)); + setupapihook_init(avs::game::DLL_INSTANCE); + setupapihook_add(settings); + + // Gitadora IO(J32D/J33I) board emulation + devicehook_init(avs::game::DLL_INSTANCE); + devicehook_add(new GitaDoraSerialHandle()); + + // test/service/coin buttons + bi2x_hook_init(); + return; + } HMODULE gdme_module = libutils::try_module("libgdme.dll"); diff --git a/src/spice2x/games/gitadora/gitadora.h b/src/spice2x/games/gitadora/gitadora.h index 50049f6..b80a014 100644 --- a/src/spice2x/games/gitadora/gitadora.h +++ b/src/spice2x/games/gitadora/gitadora.h @@ -21,13 +21,13 @@ namespace games::gitadora { static inline bool is_drum() { return ( avs::game::is_model({ "J32", "K32", "L32" }) || - (avs::game::is_model("M32") && avs::game::SPEC[0] == 'B') + (avs::game::is_model("M32") && (avs::game::SPEC[0] == 'B' || avs::game::SPEC[0] == 'D')) ); } static inline bool is_guitar() { return ( avs::game::is_model({ "J33", "K33", "L33" }) || - (avs::game::is_model("M32") && avs::game::SPEC[0] == 'A') + (avs::game::is_model("M32") && (avs::game::SPEC[0] == 'A' || avs::game::SPEC[0] == 'C')) ); } diff --git a/src/spice2x/games/gitadora/handle.cpp b/src/spice2x/games/gitadora/handle.cpp new file mode 100644 index 0000000..5e9e433 --- /dev/null +++ b/src/spice2x/games/gitadora/handle.cpp @@ -0,0 +1,110 @@ +#include "handle.h" +#include "misc/eamuse.h" +#include "rawinput/rawinput.h" +#include "util/utils.h" +#include "util/time.h" +#include "io.h" +#include "gitadora.h" +#include "j32d.h" +#include "j33i.h" +#include "device.h" + + +using namespace acioemu; +static games::gitadora::GitadoraDevice *device; + +bool games::gitadora::GitaDoraSerialHandle::open(LPCWSTR lpFileName) { + if (wcscmp(lpFileName, L"COM1") != 0) { + return false; + } + + bool is_drum = games::gitadora::is_drum(); + + if (is_drum) { + device = new J32DSerialDevice(); + } else { + device = new J33ISerialDevice(); + } + + acio_emu.add_device(device); + log_info("gitadora", "Opened COM1 ({} DEVICE)", is_drum ? "J32D" : "J33I"); + return true; +} + +int games::gitadora::GitaDoraSerialHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) { + + auto buffer = reinterpret_cast(lpBuffer); + + // the game does not send cmd 0x12F to the IO + // but it is expected to receive 0x12F for user input + // so i manually created a payload so that parse_msg can be used + // to write to the response_buffer + if (device->is_ready) { + uint8_t chk = 0; + MessageData* msg = ACIODeviceEmu::create_msg(0x1, 0x12F, 0, 0); + msg->addr = 0x1; + auto write_buffer = reinterpret_cast(msg); + while (acio_emu.read() != std::nullopt); // there may be old stuffs in the buffer + acio_emu.write(0xAA); + acio_emu.write(0xAA); + + for(DWORD i = 0; i < 5; i++) { + chk += write_buffer[i]; + acio_emu.write(write_buffer[i]); + } + // direct copy and paste from write_msg + if (chk == ACIO_SOF || chk == ACIO_ESCAPE) { + acio_emu.write(ACIO_ESCAPE); + acio_emu.write(~chk); + } else { + acio_emu.write(chk); + } + } + + // read from emu + DWORD bytes_read = 0; + while (bytes_read < nNumberOfBytesToRead) { + auto cur_byte = acio_emu.read(); + + if (cur_byte.has_value()) { + buffer[bytes_read++] = cur_byte.value(); + } else { + break; + } + } + +#ifdef ACIOEMU_LOG + log_info("gitadora", "Read IO Data: {}", bin2hex(buffer, bytes_read)); +#endif + // return amount of bytes read + return (int)bytes_read; +} + + +int games::gitadora::GitaDoraSerialHandle::write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) +{ + auto buffer = reinterpret_cast(lpBuffer); + + // write to emu + for (DWORD i = 0; i < nNumberOfBytesToWrite; i++) { + acio_emu.write(buffer[i]); + } + +#ifdef ACIOEMU_LOG + log_info("gitadora", "Write IO Data: {}", bin2hex(buffer, nNumberOfBytesToWrite)); +#endif + // return all data written + return (int)nNumberOfBytesToWrite; +} + +int games::gitadora::GitaDoraSerialHandle::device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, + LPVOID lpOutBuffer, DWORD nOutBufferSize) { + + return -1; +} + +bool games::gitadora::GitaDoraSerialHandle::close() { + + log_info("gitadora", "Closed COM1"); + return true; +} diff --git a/src/spice2x/games/gitadora/handle.h b/src/spice2x/games/gitadora/handle.h new file mode 100644 index 0000000..0fd3457 --- /dev/null +++ b/src/spice2x/games/gitadora/handle.h @@ -0,0 +1,24 @@ +#pragma once + +#include "acioemu/acioemu.h" +#include "hooks/devicehook.h" + +namespace games::gitadora { + + class GitaDoraSerialHandle : public CustomHandle { + private: + acioemu::ACIOEmu acio_emu; + + public: + bool open(LPCWSTR lpFileName) override; + + int read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) override; + + int write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) override; + + int device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, + DWORD nOutBufferSize) override; + bool close() override; + + }; +} diff --git a/src/spice2x/games/gitadora/j32d.cpp b/src/spice2x/games/gitadora/j32d.cpp new file mode 100644 index 0000000..c2106ba --- /dev/null +++ b/src/spice2x/games/gitadora/j32d.cpp @@ -0,0 +1,110 @@ + +#include "misc/eamuse.h" +#include "util/utils.h" +#include "io.h" +#include "j32d.h" + + +using namespace acioemu; + +games::gitadora::J32DSerialDevice::J32DSerialDevice() { + this->node_count = 1; + log_info("gitadora", "J32D device created."); +} + +bool games::gitadora::J32DSerialDevice::parse_msg( + MessageData *msg_in, + circular_buffer *response_buffer) { + +#ifdef ACIOEMU_LOG + log_info("gitadora", "GTDR ADDR: {}, CMD: 0x{:04x}", msg_in->addr, msg_in->cmd.code); +#endif + + static uint8_t pid = 0; + // check command + switch (msg_in->cmd.code) { + case ACIO_CMD_GET_VERSION: { + // send version data + auto msg = this->create_msg(msg_in, MSG_VERSION_SIZE); + this->set_version(msg, 0x20a, 0, 0, 1, 0, "J32D"); + write_msg(msg, response_buffer); + delete msg; + break; + } + case ACIO_CMD_STARTUP: + case ACIO_CMD_CLEAR: + case 0x0120: { + if (msg_in->cmd.code == 0x0120) { + pid = msg_in->cmd.pid; + this->is_ready = true; + } + // send status 0 + auto msg = this->create_msg_status(msg_in, 0); + write_msg(msg, response_buffer); + delete msg; + break; + } + // IO + case 0x012F: { + auto msg = this->create_msg(msg_in, 0x11); + msg->cmd.pid = pid; + // don't know if it's needed/correct, + // without it the game seems to work just fine + pid += 1; + J32D_IO_STATUS payload; + memset(&payload, 0, sizeof(J32D_IO_STATUS)); + auto &buttons = get_buttons(); + + if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumHiTom])) { + payload.high_tom = 4092; + } + + if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumLowTom])) { + payload.low_tom = 4092; + } + + if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumSnare])) { + payload.snare = 4092; + } + + if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumFloorTom])) { + payload.floor_tom = 4092; + } + + if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumLeftCymbal])) { + payload.left_cymbal = 4092; + } + + if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumRightCymbal])) { + payload.right_cymbal = 4092; + } + + if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumHiHat]) || + GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumHiHatClosed]) || + GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumHiHatHalfOpen])) { + payload.hihat = 4092; + } + + if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumBassPedal])) { + payload.pedals |= 1; + } + + if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::DrumLeftPedal])) { + payload.pedals |= 2; + } + + memcpy(msg->cmd.raw, &payload, sizeof(J32D_IO_STATUS)); + write_msg(msg, response_buffer); + delete msg; + break; + } + default: +#ifdef ACIOEMU_LOG + log_info("gitadora", "Unhandled cmd {} received.", msg_in->cmd.code); +#endif + return false; + } + + // mark as handled + return true; +} \ No newline at end of file diff --git a/src/spice2x/games/gitadora/j32d.h b/src/spice2x/games/gitadora/j32d.h new file mode 100644 index 0000000..5adedee --- /dev/null +++ b/src/spice2x/games/gitadora/j32d.h @@ -0,0 +1,25 @@ +#pragma once +#include "acioemu/device.h" +#include "device.h" + +namespace games::gitadora { + + class J32DSerialDevice : public GitadoraDevice { + private: + uint8_t coin_counter = 0; + public: + J32DSerialDevice(); + bool parse_msg(acioemu::MessageData *msg_in, circular_buffer *response_buffer) override; + struct J32D_IO_STATUS { + uint16_t high_tom; + uint16_t low_tom; + uint16_t snare; + uint16_t floor_tom; + uint16_t left_cymbal; + uint16_t right_cymbal; + uint16_t hihat; + uint16_t unk7; + uint8_t pedals; + }; + }; +} \ No newline at end of file diff --git a/src/spice2x/games/gitadora/j33i.cpp b/src/spice2x/games/gitadora/j33i.cpp new file mode 100644 index 0000000..5cbd746 --- /dev/null +++ b/src/spice2x/games/gitadora/j33i.cpp @@ -0,0 +1,109 @@ + +#include "misc/eamuse.h" +#include "util/utils.h" +#include "util/time.h" +#include "io.h" +#include "j33i.h" + + +#define GUITAR_BTN_R 0x7 +#define GUITAR_BTN_G 0x6 +#define GUITAR_BTN_B 0x5 +#define GUITAR_BTN_Y 0x4 +#define GUITAR_BTN_P 0x3 +#define GUITAR_PICK_DOWN 0xC +#define GUITAR_PICK_UP 0xD + +using namespace acioemu; + +games::gitadora::J33ISerialDevice::J33ISerialDevice() { + this->node_count = 1; + log_info("gitadora", "J33I device created."); +} + +bool games::gitadora::J33ISerialDevice::parse_msg( + MessageData *msg_in, + circular_buffer *response_buffer) { + +#ifdef ACIOEMU_LOG + log_info("gitadora", "GTDR ADDR: {}, CMD: 0x{:04x}", msg_in->addr, msg_in->cmd.code); +#endif + + static uint8_t pid = 0; + // check command + switch (msg_in->cmd.code) + { + case ACIO_CMD_GET_VERSION: { + // send version data + auto msg = this->create_msg(msg_in, MSG_VERSION_SIZE); + this->set_version(msg, 0x10a, 0, 1, 0, 2, "J33I"); + write_msg(msg, response_buffer); + delete msg; + break; + } + case ACIO_CMD_STARTUP: + case ACIO_CMD_CLEAR: + case 0x0120: { + if (msg_in->cmd.code == 0x0120) { + pid = msg_in->cmd.pid; + this->is_ready = true; + } + // send status 0 + auto msg = this->create_msg_status(msg_in, 0); + write_msg(msg, response_buffer); + delete msg; + break; + } + // IO + case 0x012F: { + auto msg = this->create_msg(msg_in, 0x8); + msg->cmd.pid = pid; + pid += 1; + J33I_IO_STATUS payload; + memset(&payload, 0, sizeof(J33I_IO_STATUS)); + + bool picked = false; + + auto &buttons = get_buttons(); + if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1R])) { + payload.buttons |= 1 << GUITAR_BTN_R; + } + + if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1G])) { + payload.buttons |= 1 << GUITAR_BTN_G; + } + + if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1B])) { + payload.buttons |= 1 << GUITAR_BTN_B; + } + + if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1Y])) { + payload.buttons |= 1 << GUITAR_BTN_Y; + } + + if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1P])) { + payload.buttons |= 1 << GUITAR_BTN_P; + } + + if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1PickUp])) { + payload.buttons |= 1 << GUITAR_PICK_UP; + picked = true; + } else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1PickDown]) && !picked) { + payload.buttons |= 1 << GUITAR_PICK_DOWN; + } + + memcpy(msg->cmd.raw, &payload, sizeof(J33I_IO_STATUS)); + write_msg(msg, response_buffer); + delete msg; + break; + } + default: +#ifdef ACIOEMU_LOG + log_info("gitadora", "Unhandled cmd {} received.", msg_in->cmd.code); +#endif + return false; + } + + // mark as handled + return true; +} \ No newline at end of file diff --git a/src/spice2x/games/gitadora/j33i.h b/src/spice2x/games/gitadora/j33i.h new file mode 100644 index 0000000..e16b441 --- /dev/null +++ b/src/spice2x/games/gitadora/j33i.h @@ -0,0 +1,24 @@ +#pragma once + +#include "acioemu/device.h" +#include "device.h" + +namespace games::gitadora { + + class J33ISerialDevice : public GitadoraDevice { + private: + uint8_t coin_counter = 0; + public: + J33ISerialDevice(); + bool parse_msg(acioemu::MessageData *msg_in, circular_buffer *response_buffer) override; + + // there may be more, but the game doesn't seem to care + // TODO: add support for gyroscope, knob and wail + struct J33I_IO_STATUS { + uint16_t gyroscope_x; + uint16_t gyroscope_y; + uint16_t gyroscope_z; + uint16_t buttons; + }; + }; +} \ No newline at end of file diff --git a/src/spice2x/hooks/devicehook.cpp b/src/spice2x/hooks/devicehook.cpp index 32571e6..5dc072b 100644 --- a/src/spice2x/hooks/devicehook.cpp +++ b/src/spice2x/hooks/devicehook.cpp @@ -399,6 +399,12 @@ static BOOL WINAPI ClearCommError_hook(HANDLE hFile, LPDWORD lpErrors, LPCOMSTAT lpStat->cbInQue = custom_handle->bytes_available(); } + // gitadora arena model needs this, or else + // the game will keep spamming 0xAA + if (avs::game::is_model("M32") && (avs::game::SPEC[0] == 'C' || avs::game::SPEC[0] == 'D') && lpErrors) { + *lpErrors = 0; + } + return TRUE; } diff --git a/src/spice2x/launcher/launcher.cpp b/src/spice2x/launcher/launcher.cpp index e81f529..2ab18fd 100644 --- a/src/spice2x/launcher/launcher.cpp +++ b/src/spice2x/launcher/launcher.cpp @@ -88,6 +88,7 @@ #include "misc/device.h" #include "misc/eamuse.h" #include "misc/extdev.h" +#include "misc/ami2000.h" #include "misc/sciunit.h" #include "misc/sde.h" #include "misc/wintouchemu.h" @@ -195,6 +196,7 @@ int main_implementation(int argc, char *argv[]) { bool attach_icca = false; bool attach_device = false; bool attach_extdev = false; + bool attach_ami2000 = false; bool attach_sciunit = false; bool attach_cpusbxpkm_printer = false; bool attach_iidx = false; @@ -428,8 +430,8 @@ int main_implementation(int argc, char *argv[]) { if (options[launcher::Options::EnableEXTDEVModule].value_bool()) { attach_extdev = true; } - if (options[launcher::Options::EnableSCIUNITModule].value_bool()) { - attach_sciunit = true; + if (options[launcher::Options::EnableAMI2000Module].value_bool()) { + attach_ami2000 = true; } if (options[launcher::Options::EnableDevicePassthrough].value_bool()) { hooks::device::ENABLE = false; @@ -1640,6 +1642,7 @@ int main_implementation(int argc, char *argv[]) { attach_io = true; attach_device = true; attach_extdev = true; + attach_ami2000 = true; attach_gitadora = true; break; } @@ -2202,6 +2205,11 @@ int main_implementation(int argc, char *argv[]) { extdev_attach(); } + // ami 2000 card reader attach + if (attach_io || attach_ami2000) { + ami2000_attach(); + } + // sci unit attach if (attach_io || attach_sciunit) { sciunit_attach(); diff --git a/src/spice2x/launcher/options.cpp b/src/spice2x/launcher/options.cpp index 778e216..8f56197 100644 --- a/src/spice2x/launcher/options.cpp +++ b/src/spice2x/launcher/options.cpp @@ -1321,6 +1321,13 @@ static const std::vector OPTION_DEFINITIONS = { .type = OptionType::Bool, .category = "I/O Modules", }, + { + .title = "Enable AMI2000 Module", + .name = "ami2000", + .desc = "Manually enable AMI2000 emulation", + .type = OptionType::Bool, + .category = "I/O Modules", + }, { .title = "Enable SCIUNIT Module", .name = "sciunit", diff --git a/src/spice2x/launcher/options.h b/src/spice2x/launcher/options.h index 6bed3fd..6ec5eaa 100644 --- a/src/spice2x/launcher/options.h +++ b/src/spice2x/launcher/options.h @@ -147,6 +147,7 @@ namespace launcher { EnableICCAModule, EnableDEVICEModule, EnableEXTDEVModule, + EnableAMI2000Module, EnableSCIUNITModule, EnableDevicePassthrough, ForceWinTouch, diff --git a/src/spice2x/misc/ami2000.cpp b/src/spice2x/misc/ami2000.cpp new file mode 100644 index 0000000..1679870 --- /dev/null +++ b/src/spice2x/misc/ami2000.cpp @@ -0,0 +1,85 @@ +#include "ami2000.h" +#include "util/utils.h" +#include "util/detour.h" +#include "util/libutils.h" +#include "misc/eamuse.h" +#include "acio/icca/icca.h" + + +static int CARD_TYPE = 0; +static uint8_t CARD_UID[8]; + +static void __fastcall AMI2000_Initialize() { + log_info("ami2000", "ami2000 initialize."); +} + +static void __fastcall AMI2000_Startup() { + log_info("ami2000", "ami2000 startup."); +} + +static void __fastcall AMI2000_Cleanup() { + log_info("ami2000", "ami2000 cleanup."); +} + +static int __fastcall AMI2000_IsDeviceError() { + return 0; +} + +static int __fastcall AMI2000_ReadCtrl() { + return 0; +} + +static const char *__fastcall AMI2000_GetRomVersion() { + return "DUMMY"; +} + +static int __fastcall AMI2000_GetCardType() { + return CARD_TYPE; +} + +static uint64_t __fastcall AMI2000_GetCardIdentifier() { + if (eamuse_card_insert_consume(1, 0)) { + eamuse_get_card(1, 0, CARD_UID); + CARD_TYPE = is_card_uid_felica(CARD_UID) ? 2 : 1; + log_info("ami2000", "AMI2000_GetCardIdentifier: {:X}", bswap64(*(uint64_t*)CARD_UID)); + } else { + CARD_TYPE = 0; + return 0; + } + return bswap64(*(uint64_t *)CARD_UID); +} + + +void ami2000_attach() { + auto ami2000 = libutils::try_module("iccr-ami2000-api.dll"); + + if (!ami2000) { + log_warning("ami2000", "module not found, skipping."); + return; + } + + // card unit + detour::inline_hook((void *)AMI2000_Initialize, libutils::try_proc( + ami2000, "AMI2000_Initialize")); + detour::inline_hook((void *)AMI2000_Startup, libutils::try_proc( + ami2000, "AMI2000_Startup")); + detour::inline_hook((void *)AMI2000_IsDeviceError, libutils::try_proc( + ami2000, "AMI2000_IsDeviceError")); + detour::inline_hook((void *)AMI2000_ReadCtrl, libutils::try_proc( + ami2000, "AMI2000_ReadCtrl")); + detour::inline_hook((void *)AMI2000_Cleanup, libutils::try_proc( + ami2000, "AMI2000_Cleanup")); + detour::inline_hook((void *)AMI2000_GetRomVersion, libutils::try_proc( + ami2000, "AMI2000_GetRomVersion")); + detour::inline_hook((void *)AMI2000_GetCardType, libutils::try_proc( + ami2000, "AMI2000_GetCardType")); + detour::inline_hook((void *)AMI2000_GetCardIdentifier, libutils::try_proc( + ami2000, "AMI2000_GetCardIdentifier")); + + log_info("ami2000", "attached"); +} + +void ami2000_detach() +{ + +} diff --git a/src/spice2x/misc/ami2000.h b/src/spice2x/misc/ami2000.h new file mode 100644 index 0000000..c604c33 --- /dev/null +++ b/src/spice2x/misc/ami2000.h @@ -0,0 +1,4 @@ +#pragma once + +void ami2000_attach(); +void ami2000_detach(); diff --git a/src/spice2x/util/utils.h b/src/spice2x/util/utils.h index 747f171..d1166c5 100644 --- a/src/spice2x/util/utils.h +++ b/src/spice2x/util/utils.h @@ -350,4 +350,23 @@ static inline std::string wchar_to_u8(const wchar_t* wstr) { std::string out(size - 1, 0); WideCharToMultiByte(CP_UTF8, 0, wstr, -1, out.data(), size - 1, nullptr, nullptr); return out; -} \ No newline at end of file +} + +// convert litte vs big endian +inline uint64_t bswap64(uint64_t x) { +#if defined(__GNUC__) || defined(__clang__) + return __builtin_bswap64(x); +#endif +#ifdef _MSC_VER + return _byteswap_uint64(x); +#endif + + return (x >> 56) | + ((x >> 40) & 0x000000000000FF00ULL) | + ((x >> 24) & 0x0000000000FF0000ULL) | + ((x >> 8) & 0x00000000FF000000ULL) | + ((x << 8) & 0x000000FF00000000ULL) | + ((x << 24) & 0x0000FF0000000000ULL) | + ((x << 40) & 0x00FF000000000000ULL) | + (x << 56); + } \ No newline at end of file