#include "bi3a_hook.h" #if SPICE64 && !SPICE_XP #include #include #include "util/detour.h" #include "util/logging.h" #include "rawinput/rawinput.h" #include "misc/eamuse.h" #include "games/io.h" #include "popn.h" #include "io.h" #include "util/tapeled.h" #include #ifdef min #undef min #endif #ifdef max #undef max #endif namespace games::popn { /* * class definitions */ struct AIO_NMGR_IOB2 { uint8_t data[0xe18]; }; struct AIO_NMGR_IOB5 { uint8_t data[0xb10]; }; struct AIO_SCI_COMM { uint8_t data[0x108]; }; struct AIO_SCI_COMM_T { uint64_t unk_0; struct AIO_SCI_COMM *aioSciComm; }; struct AIO_COMM_STATUS { uint64_t unk[5]; }; struct AIO_IOB5_BI3A { uint8_t data[0x4FF0]; }; union AIO_IOB5_BI3A__DEVSTATUS { uint8_t RawBytes[648]; struct { uint8_t Reserved0[27]; // 0-26 uint8_t Coin0; // 27 uint8_t Coin1; // 28 uint8_t Reserved1[15]; // 29-43 uint8_t TestButton; // 44, io code offset rcx+0xe4 uint8_t ServiceButton; // 45, io code offset rcx+0xe5 uint8_t CoinMech; // 46, io code offset rcx+0xe6 uint8_t Reserved2[19]; // 47-65 uint8_t JOYL_SW; // 66 uint8_t Reserved3[4]; // 67-70 uint8_t JOYR_SW; // 71 uint8_t Headphones; // 72 uint8_t Recorder; // 73, unused uint8_t Reserved4[295]; // 74-368 uint8_t Buttons_0_8; // 369 uint8_t Button_9; // 370 } Input; }; // verified with M39-004-2025121500 static_assert(sizeof(AIO_NMGR_IOB2) == 0xe18); static_assert(sizeof(AIO_NMGR_IOB5) == 0xb10); static_assert(sizeof(AIO_SCI_COMM) == 0x108); static_assert(sizeof(AIO_IOB5_BI3A) == 0x4FF0); static_assert(sizeof(AIO_IOB5_BI3A__DEVSTATUS) == 0x288); /* * typedefs */ // 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_BI3A *i_pNodeCtl); typedef int32_t (__fastcall *aioNodeCtl_GetState_t)(AIO_IOB5_BI3A *i_pNodeCtl); typedef bool (__fastcall *aioNodeCtl_IsReady_t)(AIO_IOB5_BI3A *i_pNodeCtl, int32_t i_State); typedef bool (__fastcall *aioNodeCtl_IsError_t)(AIO_IOB5_BI3A *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); typedef AIO_IOB5_BI3A *(__fastcall *aioIob5Bi3a_Create_t)(AIO_NMGR_IOB5 *i_pNodeMgr, uint8_t i, void *p); typedef void(__fastcall *aioIob5Bi3a_GetDeviceStatus_t)(AIO_IOB5_BI3A *i_pNodeCtl, AIO_IOB5_BI3A__DEVSTATUS *o_DevStatus); typedef void(__fastcall *aioIob5Bi3a_SetOutputData_t)(AIO_IOB5_BI3A *a1, unsigned int a2, unsigned __int8 a3); typedef void(__fastcall *aioIob5Bi3a_SetTapeLedDataPart_t)(AIO_IOB5_BI3A *a1, unsigned int a2, char a3, const void *a4, unsigned int a5, bool a6); typedef int64_t (__fastcall *aioIob5Bi3a_SetTapeLedDataLimit_t)(AIO_IOB5_BI3A *i_pNodeCtl, uint32_t i_Channel, uint8_t i_Scale, uint8_t i_Limit); /* * function pointers */ // 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; static aioIob5Bi3a_Create_t aioIob5Bi3a_Create_orig = nullptr; static aioIob5Bi3a_GetDeviceStatus_t aioIob5Bi3a_GetDeviceStatus_orig = nullptr; static aioIob5Bi3a_SetOutputData_t aioIob5Bi3a_SetOutputData_orig = nullptr; static aioIob5Bi3a_SetTapeLedDataPart_t aioIob5Bi3a_SetTapeLedDataPart_orig = nullptr; static aioIob5Bi3a_SetTapeLedDataLimit_t aioIob5Bi3a_SetTapeLedDataLimit_orig = nullptr; /* * variables */ static AIO_NMGR_IOB2 *aioNmgrIob2; static AIO_NMGR_IOB5 *aioNmgrIob5; static AIO_SCI_COMM *aioSciComm; static AIO_IOB5_BI3A *aioIob5Bi3a; /* * implementations */ // 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 *)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 *)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 *)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 *)aioNmgrIob2) { return false; } else { return aioNodeMgr_IsError_orig(i_pNodeMgr, i_State); } } static void __fastcall aioNodeCtl_Destroy(AIO_IOB5_BI3A *i_pNodeCtl) { if (i_pNodeCtl != aioIob5Bi3a) { return aioNodeCtl_Destroy_orig(i_pNodeCtl); } delete aioIob5Bi3a; aioIob5Bi3a = nullptr; } static int32_t __fastcall aioNodeCtl_GetState(AIO_IOB5_BI3A *i_pNodeCtl) { if (i_pNodeCtl == aioIob5Bi3a) { return 1; } return aioNodeCtl_GetState_orig(i_pNodeCtl); } static bool __fastcall aioNodeCtl_IsReady(AIO_IOB5_BI3A *i_pNodeCtl, int32_t i_State) { if (i_pNodeCtl == aioIob5Bi3a) { return true; } return aioNodeCtl_IsReady_orig(i_pNodeCtl, i_State); } static bool __fastcall aioNodeCtl_IsError(AIO_IOB5_BI3A *i_pNodeCtl, int32_t i_State) { if (i_pNodeCtl == aioIob5Bi3a) { return false; } 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; log_info("bi3a_hook", "aioNMgrIob5_Create called, returning custom AIO_NMGR_IOB5: {}", fmt::ptr(aioNmgrIob5)); return aioNmgrIob5; } else { return aioNMgrIob5_Create_orig(i_pSci, i_bfMode); } } static AIO_IOB5_BI3A *__fastcall aioIob5Bi3a_Create(AIO_NMGR_IOB5 *i_pNodeMgr, uint8_t i, void *p) { log_info("bi3a_hook", "aioIob5Bi3a_Create called with i_pNodeMgr={}, i={}, p={}", fmt::ptr(i_pNodeMgr), i, fmt::ptr(p)); if (i_pNodeMgr == aioNmgrIob5) { aioIob5Bi3a = new AIO_IOB5_BI3A; log_info("bi3a_hook", "aioIob5Bi3a_Create: returning custom AIO_IOB5_BI3A: {}", fmt::ptr(aioIob5Bi3a)); return aioIob5Bi3a; } else { return aioIob5Bi3a_Create_orig(i_pNodeMgr, i, p); } } static void __fastcall aioIob5Bi3a_GetDeviceStatus(AIO_IOB5_BI3A *i_pNodeCtl, AIO_IOB5_BI3A__DEVSTATUS *o_DevStatus) { RI_MGR->devices_flush_output(); if (i_pNodeCtl != aioIob5Bi3a) { return aioIob5Bi3a_GetDeviceStatus_orig(i_pNodeCtl, o_DevStatus); } memset(o_DevStatus, 0, sizeof(*o_DevStatus)); auto &buttons = get_buttons(); // operator if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test])) { o_DevStatus->Input.TestButton = 1; } if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service])) { o_DevStatus->Input.ServiceButton = 1; } if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::CoinMech])) { o_DevStatus->Input.CoinMech = 1; } // buttons 1-9 if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button1])) { o_DevStatus->Input.Buttons_0_8 |= (1 << 0); } if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button2])) { o_DevStatus->Input.Buttons_0_8 |= (1 << 1); } if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button3])) { o_DevStatus->Input.Buttons_0_8 |= (1 << 2); } if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button4])) { o_DevStatus->Input.Buttons_0_8 |= (1 << 3); } if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button5])) { o_DevStatus->Input.Buttons_0_8 |= (1 << 4); } if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button6])) { o_DevStatus->Input.Buttons_0_8 |= (1 << 5); } if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button7])) { o_DevStatus->Input.Buttons_0_8 |= (1 << 6); } if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button8])) { o_DevStatus->Input.Buttons_0_8 |= (1 << 7); } if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button9])) { o_DevStatus->Input.Button_9 = 1; } // deka pop-kuns if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::RedPop])) { o_DevStatus->Input.JOYL_SW = 1; } if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BluePop])) { o_DevStatus->Input.JOYR_SW = 1; } // audio if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Headphone])) { o_DevStatus->Input.Headphones = 1; } // coin o_DevStatus->Input.Coin0 += eamuse_coin_get_stock(); } static void __fastcall aioIob5Bi3a_SetOutputData(AIO_IOB5_BI3A *i_pNodeCtl, uint32_t i_CnPin, uint8_t i_Data) { if (i_pNodeCtl != aioIob5Bi3a) { return aioIob5Bi3a_SetOutputData_orig(i_pNodeCtl, i_CnPin, i_Data); } // 7 = IC_CARD_R // 8 = IC_CARD_G // 9 = IC_CARD_B // 10 = WOOFER_R // 11 = WOOFER_G // 12 = WOOFER_B // values = 0 to 255 in test menu std::optional light; switch (i_CnPin) { case 7: light = Lights::IC_Card_R; break; case 8: light = Lights::IC_Card_G; break; case 9: light = Lights::IC_Card_B; break; case 10: light = Lights::WooferLED_R; break; case 11: light = Lights::WooferLED_G; break; case 12: light = Lights::WooferLED_B; break; default: break; } if (light.has_value()) { auto &lights = get_lights(); GameAPI::Lights::writeLight(RI_MGR, lights.at(light.value()), i_Data / 255.f); } return; } static int64_t __fastcall aioIob5Bi3a_SetTapeLedDataLimit( AIO_IOB5_BI3A *i_pNodeCtl, uint32_t i_Channel, uint8_t i_Scale, uint8_t i_Limit) { if (i_pNodeCtl != aioIob5Bi3a) { return aioIob5Bi3a_SetTapeLedDataLimit_orig(i_pNodeCtl, i_Channel, i_Scale, i_Limit); } return 0; } struct PopnLight { int data_index; Lights::popn_lights_t light; uint8_t size; PopnLight( int data_index, Lights::popn_lights_t light, uint8_t size) : data_index(data_index), light(light), size(size) {} }; static void __fastcall aioIob5Bi3a_SetTapeLedDataPart( AIO_IOB5_BI3A *i_pNodeCtl, uint32_t i_CnPin, char i_LedType, const void *i_pData, uint32_t i_DataSize, bool i_bIsLast) { if (i_pNodeCtl != aioIob5Bi3a) { return aioIob5Bi3a_SetTapeLedDataPart_orig(i_pNodeCtl, i_CnPin, i_LedType, i_pData, i_DataSize, i_bIsLast); } auto number_of_leds = i_DataSize; // buttons 1-9, but game passes in wrong length; fix it up if (i_CnPin == 0 && i_DataSize == 9) { number_of_leds = i_DataSize * 3; } if (FALSE) { std::string data; for (uint32_t i = 0; i < number_of_leds; i++) { auto dataByte = ((uint8_t *)i_pData)[i]; data += fmt::format("{:02X} ", dataByte); } log_info( "bi3a_hook", "aioIob5Bi3a_SetTapeLedDataPart Pin={}, Size={}, Type={}, data@{} = {}", i_CnPin, number_of_leds, (uint8_t)i_LedType, fmt::ptr(i_pData), data); } auto &lights = get_lights(); if (i_CnPin == 0 && number_of_leds == 9 * 3) { // special handling converting to non-RGB lights // take the max(R, G, B) and use it to write the light value uint8_t light_value[9] = { 0 }; for (uint32_t i = 0; i < number_of_leds; i += 3) { light_value[i / 3] = std::max({ ((uint8_t *)i_pData)[i], ((uint8_t *)i_pData)[i + 1], ((uint8_t *)i_pData)[i + 2] }); } constexpr Lights::popn_lights_t legacy_button_lights[] = { Lights::popn_lights_t::Button1, Lights::popn_lights_t::Button2, Lights::popn_lights_t::Button3, Lights::popn_lights_t::Button4, Lights::popn_lights_t::Button5, Lights::popn_lights_t::Button6, Lights::popn_lights_t::Button7, Lights::popn_lights_t::Button8, Lights::popn_lights_t::Button9 }; static_assert(std::size(legacy_button_lights) == 9); for (size_t light = 0; light < 9; light++) { // on the new cab, buttons are colorless plastic and they rely on RGB to be lit at all times, even when "off" // when translating to legacy on/off lights, treat anything above ~60% brightness as fully on, // otherwise off, to avoid dimly lit "off" state GameAPI::Lights::writeLight( RI_MGR, lights.at(legacy_button_lights[light]), light_value[light] > 150 ? 1.f : 0.f); } // color buttons constexpr Lights::popn_lights_t button_lights[] = { Lights::popn_lights_t::PikaButton1_R, Lights::popn_lights_t::PikaButton1_G, Lights::popn_lights_t::PikaButton1_B, Lights::popn_lights_t::PikaButton2_R, Lights::popn_lights_t::PikaButton2_G, Lights::popn_lights_t::PikaButton2_B, Lights::popn_lights_t::PikaButton3_R, Lights::popn_lights_t::PikaButton3_G, Lights::popn_lights_t::PikaButton3_B, Lights::popn_lights_t::PikaButton4_R, Lights::popn_lights_t::PikaButton4_G, Lights::popn_lights_t::PikaButton4_B, Lights::popn_lights_t::PikaButton5_R, Lights::popn_lights_t::PikaButton5_G, Lights::popn_lights_t::PikaButton5_B, Lights::popn_lights_t::PikaButton6_R, Lights::popn_lights_t::PikaButton6_G, Lights::popn_lights_t::PikaButton6_B, Lights::popn_lights_t::PikaButton7_R, Lights::popn_lights_t::PikaButton7_G, Lights::popn_lights_t::PikaButton7_B, Lights::popn_lights_t::PikaButton8_R, Lights::popn_lights_t::PikaButton8_G, Lights::popn_lights_t::PikaButton8_B, Lights::popn_lights_t::PikaButton9_R, Lights::popn_lights_t::PikaButton9_G, Lights::popn_lights_t::PikaButton9_B }; static_assert(std::size(button_lights) == 9 * 3); for (size_t light = 0; light < number_of_leds; light++) { GameAPI::Lights::writeLight( RI_MGR, lights.at(button_lights[light]), ((uint8_t *)i_pData)[light] / 255.f); } } if (tapeledutils::is_enabled() && i_CnPin > 0 && i_CnPin < std::size(TAPELED_MAPPING)) { auto &map = TAPELED_MAPPING[i_CnPin]; const auto data_size = std::min(map.data.capacity(), (size_t)number_of_leds / 3); // pick a color to use const auto rgb = tapeledutils::pick_color_from_led_tape(map, (uint8_t *)i_pData, data_size); // program the lights into API auto &lights = get_lights(); GameAPI::Lights::writeLight(RI_MGR, lights[map.index_r], rgb.r); GameAPI::Lights::writeLight(RI_MGR, lights[map.index_g], rgb.g); GameAPI::Lights::writeLight(RI_MGR, lights[map.index_b], rgb.b); // tape LED output over API not implemented // for (size_t i = 0; i < data_size; i++) { // map.data[i].r = ((uint8_t *)i_pData)[i * 3]; // map.data[i].g = ((uint8_t *)i_pData)[i * 3 + 1]; // map.data[i].b = ((uint8_t *)i_pData)[i * 3 + 2]; // } } } void bi3a_hook_init() { // avoid double init static bool initialized = false; if (initialized) { return; } else { initialized = true; } // announce log_info("bi3a_hook", "init"); // libaio-iob5.dll const auto libaioIob5Dll = "libaio-iob5.dll"; detour::trampoline_try(libaioIob5Dll, "aioNMgrIob5_Create", aioNMgrIob5_Create, &aioNMgrIob5_Create_orig); detour::trampoline_try(libaioIob5Dll, "aioIob5Bi3a_Create", aioIob5Bi3a_Create, &aioIob5Bi3a_Create_orig); detour::trampoline_try(libaioIob5Dll, "aioIob5Bi3a_GetDeviceStatus", aioIob5Bi3a_GetDeviceStatus, &aioIob5Bi3a_GetDeviceStatus_orig); detour::trampoline_try(libaioIob5Dll, "aioIob5Bi3a_SetOutputData", aioIob5Bi3a_SetOutputData, &aioIob5Bi3a_SetOutputData_orig); detour::trampoline_try(libaioIob5Dll, "aioIob5Bi3a_SetTapeLedDataPart", aioIob5Bi3a_SetTapeLedDataPart, &aioIob5Bi3a_SetTapeLedDataPart_orig); detour::trampoline_try(libaioIob5Dll, "aioIob5Bi3a_SetTapeLedDataLimit", aioIob5Bi3a_SetTapeLedDataLimit, &aioIob5Bi3a_SetTapeLedDataLimit_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@I@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); } } #endif