From 66921cf3e7c7c5d76ef7abbbc6ab4c4bc4f1e096 Mon Sep 17 00:00:00 2001 From: Cetaceaqua Date: Fri, 30 Oct 2026 13:59:54 +0800 Subject: [PATCH] bc: add light support and unity player arguments injection --- src/spice2x/CMakeLists.txt | 1 + src/spice2x/games/bc/bc.cpp | 42 +++++--- src/spice2x/games/bc/bc.h | 3 + src/spice2x/games/bc/bi2x_hook.cpp | 163 +++++++++++++++++++++++++++++ src/spice2x/games/bc/bi2x_hook.h | 9 ++ src/spice2x/games/bc/io.cpp | 30 +++++- src/spice2x/games/bc/io.h | 20 ++++ src/spice2x/games/io.cpp | 1 + src/spice2x/launcher/launcher.cpp | 3 + src/spice2x/launcher/options.cpp | 9 ++ src/spice2x/launcher/options.h | 1 + 11 files changed, 267 insertions(+), 15 deletions(-) create mode 100644 src/spice2x/games/bc/bi2x_hook.cpp create mode 100644 src/spice2x/games/bc/bi2x_hook.h diff --git a/src/spice2x/CMakeLists.txt b/src/spice2x/CMakeLists.txt index a6d91cd5..ab87161b 100644 --- a/src/spice2x/CMakeLists.txt +++ b/src/spice2x/CMakeLists.txt @@ -541,6 +541,7 @@ set(SOURCE_FILES ${SOURCE_FILES} games/onpara/touchpanel.cpp games/bc/bc.cpp games/bc/io.cpp + games/bc/bi2x_hook.cpp games/ccj/ccj.cpp games/ccj/io.cpp games/ccj/bi2x_hook.cpp diff --git a/src/spice2x/games/bc/bc.cpp b/src/spice2x/games/bc/bc.cpp index abe287b5..4255b739 100644 --- a/src/spice2x/games/bc/bc.cpp +++ b/src/spice2x/games/bc/bc.cpp @@ -1,25 +1,30 @@ #include "bc.h" +#include #include "hooks/setupapihook.h" +#include "util/fileutils.h" #include "util/libutils.h" #include "acio2emu/handle.h" #include "acioemu/handle.h" #include "hooks/graphics/graphics.h" -#include "util/detour.h" +#include "util/unity_player.h" #include "node.h" +#include "bi2x_hook.h" namespace games::bc { - static decltype(ShowCursor) *ShowCursor_orig = nullptr; - - static int WINAPI ShowCursor_hook(BOOL bShow) { - // log_misc("bsac", "ShowCursor {}", bShow); - return 1; - } + std::string BC_INJECT_ARGS = ""; void BCGame::attach() { Game::attach(); + // create required directories + fileutils::dir_create_recursive("dev/raw/log"); + + // preload libraries + libutils::load_library("libaio.dll"); + libutils::load_library("libaio-iob2_video.dll"); + setupapihook_init(libutils::load_library("libaio.dll")); SETUPAPI_SETTINGS settings {}; @@ -32,15 +37,24 @@ namespace games::bc { auto iob = new acio2emu::IOBHandle(L"COM3"); iob->register_node(std::make_unique()); - if (GRAPHICS_SHOW_CURSOR) { - detour::trampoline_try( - "user32.dll", "ShowCursor", - (void*)ShowCursor_hook, (void**)&ShowCursor_orig); - } +#if SPICE64 + // insert BI2X hooks + bi2x_hook_init(); +#endif - devicehook_init(); + devicehook_init_trampoline(); devicehook_add(iob); devicehook_add(new acioemu::ACIOHandle(L"COM1")); + + if (GRAPHICS_SHOW_CURSOR) { + unity_utils::force_show_cursor(true); + } + + unity_utils::set_args( + std::format("{} {}{}", + GetCommandLineA(), + BC_INJECT_ARGS, + unity_utils::get_unity_player_args())); } void BCGame::detach() { @@ -48,4 +62,4 @@ namespace games::bc { devicehook_dispose(); } -} \ No newline at end of file +} diff --git a/src/spice2x/games/bc/bc.h b/src/spice2x/games/bc/bc.h index 16fa1a06..2815ac97 100644 --- a/src/spice2x/games/bc/bc.h +++ b/src/spice2x/games/bc/bc.h @@ -1,8 +1,11 @@ #pragma once +#include #include "games/game.h" namespace games::bc { + extern std::string BC_INJECT_ARGS; + class BCGame : public games::Game { public: BCGame() : Game("Busou Shinki: Armored Princess Battle Conductor") {} diff --git a/src/spice2x/games/bc/bi2x_hook.cpp b/src/spice2x/games/bc/bi2x_hook.cpp new file mode 100644 index 00000000..5ffcd601 --- /dev/null +++ b/src/spice2x/games/bc/bi2x_hook.cpp @@ -0,0 +1,163 @@ +#include "bi2x_hook.h" + +#if SPICE64 + +#include +#include + +#include "util/detour.h" +#include "util/logging.h" +#include "rawinput/rawinput.h" +#include "games/io.h" +#include "io.h" +#include "util/tapeled.h" + +namespace games::bc { + + struct AIO_IOB2_BI2X_TBS; + + /* + * typedefs + */ + + typedef void (__fastcall *aioIob2Bi2xTBS_GetDeviceStatus_t)( + AIO_IOB2_BI2X_TBS *i_pNodeCtl, void *o_DevStatus); + typedef void (__fastcall *aioIob2Bi2xTBS_SetButton0Lamp_t)( + AIO_IOB2_BI2X_TBS *i_pNodeCtl, bool i_bOn); + typedef void (__fastcall *aioIob2Bi2xTBS_SetIrLed_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); + + static aioIob2Bi2xTBS_GetDeviceStatus_t aioIob2Bi2xTBS_GetDeviceStatus_orig = nullptr; + static aioIob2Bi2xTBS_SetButton0Lamp_t aioIob2Bi2xTBS_SetButton0Lamp_orig = nullptr; + static aioIob2Bi2xTBS_SetIrLed_t aioIob2Bi2xTBS_SetIrLed_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; + + /* + * hooks + */ + + static void write_light_rgb( + Lights::bc_lights_t r, Lights::bc_lights_t g, Lights::bc_lights_t b, uint32_t color) { + + auto &lights = get_lights(); + GameAPI::Lights::writeLight(RI_MGR, lights.at(r), ((color >> 16) & 0xFF) / 255.f); + GameAPI::Lights::writeLight(RI_MGR, lights.at(g), ((color >> 8) & 0xFF) / 255.f); + GameAPI::Lights::writeLight(RI_MGR, lights.at(b), (color & 0xFF) / 255.f); + } + + static void __fastcall aioIob2Bi2xTBS_GetDeviceStatus( + AIO_IOB2_BI2X_TBS *i_pNodeCtl, void *o_DevStatus) { + + RI_MGR->devices_flush_output(); + + if (aioIob2Bi2xTBS_GetDeviceStatus_orig) { + aioIob2Bi2xTBS_GetDeviceStatus_orig(i_pNodeCtl, o_DevStatus); + } + } + + static void __fastcall aioIob2Bi2xTBS_SetButton0Lamp( + AIO_IOB2_BI2X_TBS *i_pNodeCtl, bool i_bOn) { + + auto &lights = get_lights(); + GameAPI::Lights::writeLight(RI_MGR, lights.at(Lights::Button), (i_bOn ? 1.f : 0.f)); + + if (aioIob2Bi2xTBS_SetButton0Lamp_orig) { + aioIob2Bi2xTBS_SetButton0Lamp_orig(i_pNodeCtl, i_bOn); + } + } + + static void __fastcall aioIob2Bi2xTBS_SetIrLed( + AIO_IOB2_BI2X_TBS *i_pNodeCtl, bool i_bOn) { + + auto &lights = get_lights(); + GameAPI::Lights::writeLight(RI_MGR, lights.at(Lights::IrLed), (i_bOn ? 1.f : 0.f)); + + if (aioIob2Bi2xTBS_SetIrLed_orig) { + aioIob2Bi2xTBS_SetIrLed_orig(i_pNodeCtl, i_bOn); + } + } + + static void __fastcall aioIob2Bi2xTBS_SetIccrLed( + AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_RGB) { + + write_light_rgb(Lights::CardReader_R, Lights::CardReader_G, Lights::CardReader_B, i_RGB); + + if (aioIob2Bi2xTBS_SetIccrLed_orig) { + aioIob2Bi2xTBS_SetIccrLed_orig(i_pNodeCtl, i_RGB); + } + } + + static void __fastcall aioIob2Bi2xTBS_SetStickLed( + AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_RGB) { + + write_light_rgb(Lights::Joystick_R, Lights::Joystick_G, Lights::Joystick_B, i_RGB); + + if (aioIob2Bi2xTBS_SetStickLed_orig) { + aioIob2Bi2xTBS_SetStickLed_orig(i_pNodeCtl, i_RGB); + } + } + + static void __fastcall aioIob2Bi2xTBS_SetTapeLedData( + AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_TapeLed, uint8_t *i_pData) { + + 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]; + const auto rgb = tapeledutils::pick_color_from_led_tape(i_pData, map.data_size); + + auto &lights = get_lights(); + GameAPI::Lights::writeLight(RI_MGR, lights.at(map.r), rgb.r); + GameAPI::Lights::writeLight(RI_MGR, lights.at(map.g), rgb.g); + GameAPI::Lights::writeLight(RI_MGR, lights.at(map.b), rgb.b); + } + + if (aioIob2Bi2xTBS_SetTapeLedData_orig) { + aioIob2Bi2xTBS_SetTapeLedData_orig(i_pNodeCtl, i_TapeLed, i_pData); + } + } + + void bi2x_hook_init() { + static bool initialized = false; + if (initialized) { + return; + } else { + initialized = true; + } + + log_info("bc_bi2x_hook", "init"); + + const auto libaioIob2VideoDll = "libaio-iob2_video.dll"; + detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xTBS_GetDeviceStatus", + aioIob2Bi2xTBS_GetDeviceStatus, &aioIob2Bi2xTBS_GetDeviceStatus_orig); + detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xTBS_SetButton0Lamp", + aioIob2Bi2xTBS_SetButton0Lamp, &aioIob2Bi2xTBS_SetButton0Lamp_orig); + detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xTBS_SetIrLed", + aioIob2Bi2xTBS_SetIrLed, &aioIob2Bi2xTBS_SetIrLed_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); + } +} + +#endif diff --git a/src/spice2x/games/bc/bi2x_hook.h b/src/spice2x/games/bc/bi2x_hook.h new file mode 100644 index 00000000..c0c9cf04 --- /dev/null +++ b/src/spice2x/games/bc/bi2x_hook.h @@ -0,0 +1,9 @@ +#pragma once + +#if SPICE64 + +namespace games::bc { + void bi2x_hook_init(); +} + +#endif diff --git a/src/spice2x/games/bc/io.cpp b/src/spice2x/games/bc/io.cpp index ffbf6d01..c53f41e0 100644 --- a/src/spice2x/games/bc/io.cpp +++ b/src/spice2x/games/bc/io.cpp @@ -47,4 +47,32 @@ std::vector &games::bc::get_analogs() { } return analogs; -} \ No newline at end of file +} + +std::vector &games::bc::get_lights() { + static std::vector lights; + + if (lights.empty()) { + lights = GameAPI::Lights::getLights("Busou Shinki: Armored Princess Battle Conductor"); + + 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", + "Joystick R", + "Joystick G", + "Joystick B", + "Button", + "IR LED" + ); + } + + return lights; +} diff --git a/src/spice2x/games/bc/io.h b/src/spice2x/games/bc/io.h index 6e8c84ce..e3c78620 100644 --- a/src/spice2x/games/bc/io.h +++ b/src/spice2x/games/bc/io.h @@ -30,7 +30,27 @@ namespace games::bc { }; } + namespace Lights { + typedef enum { + TitlePanel_R, + TitlePanel_G, + TitlePanel_B, + SidePanel_R, + SidePanel_G, + SidePanel_B, + CardReader_R, + CardReader_G, + CardReader_B, + Joystick_R, + Joystick_G, + Joystick_B, + Button, + IrLed, + } bc_lights_t; + } + std::vector