Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/spice2x/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
42 changes: 28 additions & 14 deletions src/spice2x/games/bc/bc.cpp
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
#include "bc.h"

#include <format>
#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 {};
Expand All @@ -32,20 +37,29 @@ namespace games::bc {
auto iob = new acio2emu::IOBHandle(L"COM3");
iob->register_node(std::make_unique<TBSNode>());

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() {
Game::detach();

devicehook_dispose();
}
}
}
3 changes: 3 additions & 0 deletions src/spice2x/games/bc/bc.h
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
#pragma once

#include <string>
#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") {}
Expand Down
163 changes: 163 additions & 0 deletions src/spice2x/games/bc/bi2x_hook.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
#include "bi2x_hook.h"

#if SPICE64

#include <cstdint>
#include <iterator>

#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
9 changes: 9 additions & 0 deletions src/spice2x/games/bc/bi2x_hook.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#pragma once

#if SPICE64

namespace games::bc {
void bi2x_hook_init();
}

#endif
30 changes: 29 additions & 1 deletion src/spice2x/games/bc/io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,32 @@ std::vector<Analog> &games::bc::get_analogs() {
}

return analogs;
}
}

std::vector<Light> &games::bc::get_lights() {
static std::vector<Light> 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;
}
20 changes: 20 additions & 0 deletions src/spice2x/games/bc/io.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<Button> &get_buttons();
std::string get_buttons_help();
std::vector<Analog> &get_analogs();
std::vector<Light> &get_lights();
}
1 change: 1 addition & 0 deletions src/spice2x/games/io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ namespace games {
buttons.insert({ bc, bc::get_buttons() });
buttons_help.insert({ bc, bc::get_buttons_help() });
analogs.insert({ bc, bc::get_analogs() });
lights.insert({ bc, bc::get_lights() });
file_hints[bc].push_back({"game/bsac_app.exe"});

// ccj
Expand Down
3 changes: 3 additions & 0 deletions src/spice2x/launcher/launcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1485,6 +1485,9 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::CCJMouseTrackballWithToggle].value_bool()) {
games::ccj::MOUSE_TRACKBALL_USE_TOGGLE = true;
}
if (options[launcher::Options::BCArgs].is_active()) {
games::bc::BC_INJECT_ARGS = options[launcher::Options::BCArgs].value_text();
}
if (options[launcher::Options::CCJArgs].is_active()) {
games::ccj::CCJ_INJECT_ARGS = options[launcher::Options::CCJArgs].value_text();
}
Expand Down
9 changes: 9 additions & 0 deletions src/spice2x/launcher/options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2362,6 +2362,15 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = std::invoke([]()
{"all", "basic + HID devices"},
},
};
c[launcher::Options::BCArgs] = {
.title = "BC Arguments Override",
.name = "bcargs",
.desc = "Command line arguments passed to the game.",
.type = OptionType::Text,
.setting_name = "",
.game_name = "Busou Shinki: Armored Princess Battle Conductor",
.category = "Advanced Game Options",
};
c[launcher::Options::QKSArgs] = {
.title = "QKS Arguments Override",
.name = "qksargs",
Expand Down
1 change: 1 addition & 0 deletions src/spice2x/launcher/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ namespace launcher {
DisableAvsCache,
OutputPEB,
DumpSystemInfo,
BCArgs,
QKSArgs,
CCJArgs,
CCJMouseTrackball,
Expand Down
Loading