mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-01 22:30:42 -07:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c590b87cff |
@@ -411,6 +411,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
|||||||
games/sdvx/sdvx_live2d.cpp
|
games/sdvx/sdvx_live2d.cpp
|
||||||
games/sdvx/io.cpp
|
games/sdvx/io.cpp
|
||||||
games/sdvx/camera.cpp
|
games/sdvx/camera.cpp
|
||||||
|
games/jb/bi2x_hook.cpp
|
||||||
games/jb/jb.cpp
|
games/jb/jb.cpp
|
||||||
games/jb/jb_touch.cpp
|
games/jb/jb_touch.cpp
|
||||||
games/jb/io.cpp
|
games/jb/io.cpp
|
||||||
|
|||||||
@@ -239,7 +239,7 @@ bool ICCADevice::parse_msg(MessageData *msg_in,
|
|||||||
// SDVX Old cabinet mode
|
// SDVX Old cabinet mode
|
||||||
if (avs::game::is_model("KFC") && avs::game::SPEC[0] != 'G' && avs::game::SPEC[0] != 'H')
|
if (avs::game::is_model("KFC") && avs::game::SPEC[0] != 'G' && avs::game::SPEC[0] != 'H')
|
||||||
answer_type = 1;
|
answer_type = 1;
|
||||||
if (avs::game::is_model("L44"))
|
if (avs::game::is_model({ "L44", "T44" }))
|
||||||
answer_type = 2;
|
answer_type = 2;
|
||||||
|
|
||||||
// check answer type
|
// check answer type
|
||||||
|
|||||||
@@ -1114,6 +1114,7 @@ namespace avs {
|
|||||||
#endif
|
#endif
|
||||||
// jubeat
|
// jubeat
|
||||||
{"jubeat.dll", 0x2000000},
|
{"jubeat.dll", 0x2000000},
|
||||||
|
{"jubeat2019.dll", 0x10000000},
|
||||||
|
|
||||||
// MUSECA
|
// MUSECA
|
||||||
{"museca.dll", 0xC000000},
|
{"museca.dll", 0xC000000},
|
||||||
|
|||||||
@@ -591,6 +591,7 @@ namespace avs {
|
|||||||
// for proper reporting of Omnimix and other song packs
|
// for proper reporting of Omnimix and other song packs
|
||||||
if (_stricmp(EA3_MODEL, "LDJ") == 0 ||
|
if (_stricmp(EA3_MODEL, "LDJ") == 0 ||
|
||||||
_stricmp(EA3_MODEL, "L44") == 0 ||
|
_stricmp(EA3_MODEL, "L44") == 0 ||
|
||||||
|
_stricmp(EA3_MODEL, "T44") == 0 ||
|
||||||
_stricmp(EA3_MODEL, "M39") == 0 ||
|
_stricmp(EA3_MODEL, "M39") == 0 ||
|
||||||
_stricmp(EA3_MODEL, "KFC") == 0)
|
_stricmp(EA3_MODEL, "KFC") == 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ namespace games {
|
|||||||
buttons_help.insert({ jb, jb::get_buttons_help() });
|
buttons_help.insert({ jb, jb::get_buttons_help() });
|
||||||
lights.insert({ jb, jb::get_lights() });
|
lights.insert({ jb, jb::get_lights() });
|
||||||
file_hints[jb].push_back({"jubeat.dll"});
|
file_hints[jb].push_back({"jubeat.dll"});
|
||||||
|
file_hints[jb].push_back({"jubeat2019.dll"});
|
||||||
|
|
||||||
// mga
|
// mga
|
||||||
const std::string mga("Metal Gear");
|
const std::string mga("Metal Gear");
|
||||||
|
|||||||
@@ -0,0 +1,336 @@
|
|||||||
|
#include "bi2x_hook.h"
|
||||||
|
|
||||||
|
#if SPICE64
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "games/io.h"
|
||||||
|
#include "games/jb/jb_touch.h"
|
||||||
|
#include "io.h"
|
||||||
|
#include "misc/eamuse.h"
|
||||||
|
#include "rawinput/rawinput.h"
|
||||||
|
#include "util/detour.h"
|
||||||
|
#include "util/logging.h"
|
||||||
|
|
||||||
|
namespace games::jb {
|
||||||
|
|
||||||
|
struct AIO_SCI_COMM {
|
||||||
|
std::array<uint8_t, 0x100> data;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AIO_NMGR_IOB2;
|
||||||
|
|
||||||
|
struct AIO_NMGR_IOB2_VTABLE {
|
||||||
|
std::array<void *, 10> unused;
|
||||||
|
void (__fastcall *begin_manage)(AIO_NMGR_IOB2 *node_mgr);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AIO_NMGR_IOB2 {
|
||||||
|
AIO_NMGR_IOB2_VTABLE *vtable;
|
||||||
|
std::array<uint8_t, 0x78> data;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AIO_IOB2_BI2X_T44 {
|
||||||
|
std::array<uint8_t, 0x80> data;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AIO_IOB2_BI2X_T44_DEVSTATUS {
|
||||||
|
std::array<uint8_t, 0x140> data;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AIO_IOB2_BI2X_WRFIRM {
|
||||||
|
uint8_t data;
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(sizeof(AIO_NMGR_IOB2) == 0x80);
|
||||||
|
static_assert(sizeof(AIO_IOB2_BI2X_T44_DEVSTATUS) == 0x140);
|
||||||
|
|
||||||
|
using aioIob2Bi2xT44_Create_t = AIO_IOB2_BI2X_T44 *(__fastcall *)(AIO_NMGR_IOB2 *node_mgr,
|
||||||
|
uint32_t device_id);
|
||||||
|
using aioIob2Bi2xT44_GetDeviceStatus_t = void (__fastcall *)(AIO_IOB2_BI2X_T44 *node,
|
||||||
|
AIO_IOB2_BI2X_T44_DEVSTATUS *status);
|
||||||
|
using aioIob2Bi2xT44_IoReset_t = void (__fastcall *)(AIO_IOB2_BI2X_T44 *node, uint32_t reset);
|
||||||
|
using aioIob2Bi2xT44_SetWatchDogTimer_t = void (__fastcall *)(AIO_IOB2_BI2X_T44 *node, uint8_t count);
|
||||||
|
using aioIob2Bi2xT44_ControlCoinBlocker_t = void (__fastcall *)(AIO_IOB2_BI2X_T44 *node,
|
||||||
|
uint32_t slot, bool open);
|
||||||
|
using aioIob2Bi2xT44_AddCounter_t = void (__fastcall *)(AIO_IOB2_BI2X_T44 *node,
|
||||||
|
uint32_t counter, uint32_t count);
|
||||||
|
using aioIob2Bi2xT44_SetIccrLed_t = void (__fastcall *)(AIO_IOB2_BI2X_T44 *node, uint32_t color);
|
||||||
|
using aioIob2Bi2xT44_SetTapeLedData_t = void (__fastcall *)(AIO_IOB2_BI2X_T44 *node,
|
||||||
|
uint32_t tape, const void *data);
|
||||||
|
using aioIob2Bi2x_OpenSciUsbCdc_t = AIO_SCI_COMM *(__fastcall *)(uint32_t serial_number);
|
||||||
|
using aioIob2Bi2x_CreateWriteFirmContext_t = AIO_IOB2_BI2X_WRFIRM *(__fastcall *)(
|
||||||
|
uint32_t serial_number, uint32_t iob_mask);
|
||||||
|
using aioIob2Bi2x_DestroyWriteFirmContext_t = void (__fastcall *)(AIO_IOB2_BI2X_WRFIRM *context);
|
||||||
|
using aioIob2Bi2x_WriteFirmGetState_t = int32_t (__fastcall *)(AIO_IOB2_BI2X_WRFIRM *context);
|
||||||
|
using aioIob2Bi2x_WriteFirmIsCompleted_t = bool (__fastcall *)(int32_t state);
|
||||||
|
using aioIob2Bi2x_WriteFirmIsError_t = bool (__fastcall *)(int32_t state);
|
||||||
|
using aioNMgrIob2_Create_t = AIO_NMGR_IOB2 *(__fastcall *)(AIO_SCI_COMM *sci, uint32_t mode);
|
||||||
|
using aioSci_Destroy_t = void (__fastcall *)(AIO_SCI_COMM *sci);
|
||||||
|
using aioNodeMgr_Destroy_t = void (__fastcall *)(AIO_NMGR_IOB2 *node_mgr);
|
||||||
|
using aioNodeCtl_Destroy_t = void (__fastcall *)(AIO_IOB2_BI2X_T44 *node);
|
||||||
|
using aioNodeCtl_UpdateDevicesStatus_t = void (__fastcall *)();
|
||||||
|
|
||||||
|
static aioIob2Bi2xT44_Create_t aioIob2Bi2xT44_Create_orig = nullptr;
|
||||||
|
static aioIob2Bi2xT44_GetDeviceStatus_t aioIob2Bi2xT44_GetDeviceStatus_orig = nullptr;
|
||||||
|
static aioIob2Bi2xT44_IoReset_t aioIob2Bi2xT44_IoReset_orig = nullptr;
|
||||||
|
static aioIob2Bi2xT44_SetWatchDogTimer_t aioIob2Bi2xT44_SetWatchDogTimer_orig = nullptr;
|
||||||
|
static aioIob2Bi2xT44_ControlCoinBlocker_t aioIob2Bi2xT44_ControlCoinBlocker_orig = nullptr;
|
||||||
|
static aioIob2Bi2xT44_AddCounter_t aioIob2Bi2xT44_AddCounter_orig = nullptr;
|
||||||
|
static aioIob2Bi2xT44_SetIccrLed_t aioIob2Bi2xT44_SetIccrLed_orig = nullptr;
|
||||||
|
static aioIob2Bi2xT44_SetTapeLedData_t aioIob2Bi2xT44_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;
|
||||||
|
static aioNMgrIob2_Create_t aioNMgrIob2_Create_orig = nullptr;
|
||||||
|
static aioSci_Destroy_t aioSci_Destroy_orig = nullptr;
|
||||||
|
static aioNodeMgr_Destroy_t aioNodeMgr_Destroy_orig = nullptr;
|
||||||
|
static aioNodeCtl_Destroy_t aioNodeCtl_Destroy_orig = nullptr;
|
||||||
|
static aioNodeCtl_UpdateDevicesStatus_t aioNodeCtl_UpdateDevicesStatus_orig = nullptr;
|
||||||
|
|
||||||
|
static AIO_SCI_COMM *aio_sci_comm = nullptr;
|
||||||
|
static AIO_NMGR_IOB2 *aio_node_mgr = nullptr;
|
||||||
|
static AIO_IOB2_BI2X_T44 *aio_t44 = nullptr;
|
||||||
|
static AIO_IOB2_BI2X_WRFIRM *aio_write_firm = nullptr;
|
||||||
|
static uint8_t input_counter = 0;
|
||||||
|
|
||||||
|
static void __fastcall aioNMgrIob_BeginManage(AIO_NMGR_IOB2 *) {
|
||||||
|
}
|
||||||
|
|
||||||
|
static AIO_NMGR_IOB2_VTABLE aio_node_mgr_vtable {
|
||||||
|
{},
|
||||||
|
aioNMgrIob_BeginManage,
|
||||||
|
};
|
||||||
|
|
||||||
|
static AIO_SCI_COMM *__fastcall aioIob2Bi2x_OpenSciUsbCdc(uint32_t) {
|
||||||
|
aio_sci_comm = new AIO_SCI_COMM {};
|
||||||
|
return aio_sci_comm;
|
||||||
|
}
|
||||||
|
|
||||||
|
static AIO_NMGR_IOB2 *__fastcall aioNMgrIob2_Create(AIO_SCI_COMM *sci, uint32_t mode) {
|
||||||
|
if (sci != aio_sci_comm) {
|
||||||
|
return aioNMgrIob2_Create_orig(sci, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
aio_node_mgr = new AIO_NMGR_IOB2 {};
|
||||||
|
aio_node_mgr->vtable = &aio_node_mgr_vtable;
|
||||||
|
return aio_node_mgr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static AIO_IOB2_BI2X_T44 *__fastcall aioIob2Bi2xT44_Create(
|
||||||
|
AIO_NMGR_IOB2 *node_mgr, uint32_t device_id) {
|
||||||
|
if (node_mgr != aio_node_mgr) {
|
||||||
|
return aioIob2Bi2xT44_Create_orig(node_mgr, device_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
log_info("jb::bi2x", "T44 node created");
|
||||||
|
aio_t44 = new AIO_IOB2_BI2X_T44 {};
|
||||||
|
return aio_t44;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __fastcall aioIob2Bi2xT44_GetDeviceStatus(
|
||||||
|
AIO_IOB2_BI2X_T44 *node, AIO_IOB2_BI2X_T44_DEVSTATUS *status) {
|
||||||
|
if (node != aio_t44) {
|
||||||
|
return aioIob2Bi2xT44_GetDeviceStatus_orig(node, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
RI_MGR->devices_flush_output();
|
||||||
|
std::memset(status, 0, sizeof(*status));
|
||||||
|
|
||||||
|
status->data[0x00] = input_counter;
|
||||||
|
status->data[0x03] = input_counter++;
|
||||||
|
status->data[0x0A] = static_cast<uint8_t>(eamuse_coin_get_stock());
|
||||||
|
|
||||||
|
games::jb::touch_update();
|
||||||
|
const auto touched = games::jb::touch_state();
|
||||||
|
auto &buttons = get_buttons();
|
||||||
|
|
||||||
|
status->data[0x04] = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test]) ? 0xFF : 0;
|
||||||
|
status->data[0x05] = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service]) ? 0xFF : 0;
|
||||||
|
status->data[0x06] = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::CoinMech]) ? 0xFF : 0;
|
||||||
|
|
||||||
|
static constexpr size_t PANEL_ORDER[16] = {
|
||||||
|
3, 7, 11, 15,
|
||||||
|
2, 6, 10, 14,
|
||||||
|
1, 5, 9, 13,
|
||||||
|
0, 4, 8, 12,
|
||||||
|
};
|
||||||
|
for (size_t status_index = 0; status_index < std::size(PANEL_ORDER); status_index++) {
|
||||||
|
const auto panel_index = PANEL_ORDER[status_index];
|
||||||
|
if (touched[panel_index] || GameAPI::Buttons::getState(
|
||||||
|
RI_MGR, buttons[Buttons::Button1 + panel_index])) {
|
||||||
|
status->data[0x0F + status_index] = 0xFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __fastcall aioIob2Bi2xT44_IoReset(AIO_IOB2_BI2X_T44 *node, uint32_t reset) {
|
||||||
|
if (node != aio_t44) {
|
||||||
|
return aioIob2Bi2xT44_IoReset_orig(node, reset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __fastcall aioIob2Bi2xT44_SetWatchDogTimer(AIO_IOB2_BI2X_T44 *node, uint8_t count) {
|
||||||
|
if (node != aio_t44) {
|
||||||
|
return aioIob2Bi2xT44_SetWatchDogTimer_orig(node, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __fastcall aioIob2Bi2xT44_ControlCoinBlocker(
|
||||||
|
AIO_IOB2_BI2X_T44 *node, uint32_t slot, bool open) {
|
||||||
|
if (node != aio_t44) {
|
||||||
|
return aioIob2Bi2xT44_ControlCoinBlocker_orig(node, slot, open);
|
||||||
|
}
|
||||||
|
|
||||||
|
eamuse_coin_set_block(!open);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __fastcall aioIob2Bi2xT44_AddCounter(
|
||||||
|
AIO_IOB2_BI2X_T44 *node, uint32_t counter, uint32_t count) {
|
||||||
|
if (node != aio_t44) {
|
||||||
|
return aioIob2Bi2xT44_AddCounter_orig(node, counter, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __fastcall aioIob2Bi2xT44_SetIccrLed(AIO_IOB2_BI2X_T44 *node, uint32_t color) {
|
||||||
|
if (node != aio_t44) {
|
||||||
|
return aioIob2Bi2xT44_SetIccrLed_orig(node, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __fastcall aioIob2Bi2xT44_SetTapeLedData(
|
||||||
|
AIO_IOB2_BI2X_T44 *node, uint32_t tape, const void *data) {
|
||||||
|
if (node != aio_t44) {
|
||||||
|
return aioIob2Bi2xT44_SetTapeLedData_orig(node, tape, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static AIO_IOB2_BI2X_WRFIRM *__fastcall aioIob2Bi2x_CreateWriteFirmContext(uint32_t, uint32_t) {
|
||||||
|
aio_write_firm = new AIO_IOB2_BI2X_WRFIRM {};
|
||||||
|
return aio_write_firm;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __fastcall aioIob2Bi2x_DestroyWriteFirmContext(AIO_IOB2_BI2X_WRFIRM *context) {
|
||||||
|
if (context != aio_write_firm) {
|
||||||
|
return aioIob2Bi2x_DestroyWriteFirmContext_orig(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete aio_write_firm;
|
||||||
|
aio_write_firm = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int32_t __fastcall aioIob2Bi2x_WriteFirmGetState(AIO_IOB2_BI2X_WRFIRM *context) {
|
||||||
|
if (context != aio_write_firm) {
|
||||||
|
return aioIob2Bi2x_WriteFirmGetState_orig(context);
|
||||||
|
}
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool __fastcall aioIob2Bi2x_WriteFirmIsCompleted(int32_t state) {
|
||||||
|
if (aio_write_firm) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return aioIob2Bi2x_WriteFirmIsCompleted_orig(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool __fastcall aioIob2Bi2x_WriteFirmIsError(int32_t state) {
|
||||||
|
if (aio_write_firm) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return aioIob2Bi2x_WriteFirmIsError_orig(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __fastcall aioSci_Destroy(AIO_SCI_COMM *sci) {
|
||||||
|
if (sci != aio_sci_comm) {
|
||||||
|
return aioSci_Destroy_orig(sci);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete aio_sci_comm;
|
||||||
|
aio_sci_comm = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __fastcall aioNodeMgr_Destroy(AIO_NMGR_IOB2 *node_mgr) {
|
||||||
|
if (node_mgr != aio_node_mgr) {
|
||||||
|
return aioNodeMgr_Destroy_orig(node_mgr);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete aio_node_mgr;
|
||||||
|
aio_node_mgr = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __fastcall aioNodeCtl_Destroy(AIO_IOB2_BI2X_T44 *node) {
|
||||||
|
if (node != aio_t44) {
|
||||||
|
return aioNodeCtl_Destroy_orig(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete aio_t44;
|
||||||
|
aio_t44 = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __fastcall aioNodeCtl_UpdateDevicesStatus() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void bi2x_hook_init() {
|
||||||
|
static bool initialized = false;
|
||||||
|
if (initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
initialized = true;
|
||||||
|
|
||||||
|
log_info("jb::bi2x", "initializing T44 hooks");
|
||||||
|
|
||||||
|
const auto libaio_iob2_video = "libaio-iob2_video.dll";
|
||||||
|
detour::trampoline_try(libaio_iob2_video, "aioIob2Bi2xT44_Create",
|
||||||
|
aioIob2Bi2xT44_Create, &aioIob2Bi2xT44_Create_orig);
|
||||||
|
detour::trampoline_try(libaio_iob2_video,
|
||||||
|
"?GetDeviceStatus@AIO_IOB2_BI2X_T44@@QEBAXAEAUDEVSTATUS@1@@Z",
|
||||||
|
aioIob2Bi2xT44_GetDeviceStatus, &aioIob2Bi2xT44_GetDeviceStatus_orig);
|
||||||
|
detour::trampoline_try(libaio_iob2_video, "?IoReset@AIO_IOB2_BI2X_T44@@QEAAXI@Z",
|
||||||
|
aioIob2Bi2xT44_IoReset, &aioIob2Bi2xT44_IoReset_orig);
|
||||||
|
detour::trampoline_try(libaio_iob2_video,
|
||||||
|
"?SetWatchDogTimer@AIO_IOB2_BI2X_T44@@QEAAXE@Z",
|
||||||
|
aioIob2Bi2xT44_SetWatchDogTimer, &aioIob2Bi2xT44_SetWatchDogTimer_orig);
|
||||||
|
detour::trampoline_try(libaio_iob2_video,
|
||||||
|
"?ControlCoinBlocker@AIO_IOB2_BI2X_T44@@QEAAXI_N@Z",
|
||||||
|
aioIob2Bi2xT44_ControlCoinBlocker, &aioIob2Bi2xT44_ControlCoinBlocker_orig);
|
||||||
|
detour::trampoline_try(libaio_iob2_video, "?AddCounter@AIO_IOB2_BI2X_T44@@QEAAXII@Z",
|
||||||
|
aioIob2Bi2xT44_AddCounter, &aioIob2Bi2xT44_AddCounter_orig);
|
||||||
|
detour::trampoline_try(libaio_iob2_video, "?SetIccrLed@AIO_IOB2_BI2X_T44@@QEAAXI@Z",
|
||||||
|
aioIob2Bi2xT44_SetIccrLed, &aioIob2Bi2xT44_SetIccrLed_orig);
|
||||||
|
detour::trampoline_try(libaio_iob2_video,
|
||||||
|
"?SetTapeLedData@AIO_IOB2_BI2X_T44@@QEAAXIPEBX@Z",
|
||||||
|
aioIob2Bi2xT44_SetTapeLedData, &aioIob2Bi2xT44_SetTapeLedData_orig);
|
||||||
|
|
||||||
|
const auto libaio_iob = "libaio-iob.dll";
|
||||||
|
detour::trampoline_try(libaio_iob, "aioIob2Bi2x_OpenSciUsbCdc",
|
||||||
|
aioIob2Bi2x_OpenSciUsbCdc, &aioIob2Bi2x_OpenSciUsbCdc_orig);
|
||||||
|
detour::trampoline_try(libaio_iob, "aioIob2Bi2x_CreateWriteFirmContext",
|
||||||
|
aioIob2Bi2x_CreateWriteFirmContext, &aioIob2Bi2x_CreateWriteFirmContext_orig);
|
||||||
|
detour::trampoline_try(libaio_iob, "aioIob2Bi2x_DestroyWriteFirmContext",
|
||||||
|
aioIob2Bi2x_DestroyWriteFirmContext, &aioIob2Bi2x_DestroyWriteFirmContext_orig);
|
||||||
|
detour::trampoline_try(libaio_iob, "aioIob2Bi2x_WriteFirmGetState",
|
||||||
|
aioIob2Bi2x_WriteFirmGetState, &aioIob2Bi2x_WriteFirmGetState_orig);
|
||||||
|
detour::trampoline_try(libaio_iob, "aioIob2Bi2x_WriteFirmIsCompleted",
|
||||||
|
aioIob2Bi2x_WriteFirmIsCompleted, &aioIob2Bi2x_WriteFirmIsCompleted_orig);
|
||||||
|
detour::trampoline_try(libaio_iob, "aioIob2Bi2x_WriteFirmIsError",
|
||||||
|
aioIob2Bi2x_WriteFirmIsError, &aioIob2Bi2x_WriteFirmIsError_orig);
|
||||||
|
detour::trampoline_try(libaio_iob, "aioNMgrIob2_Create",
|
||||||
|
aioNMgrIob2_Create, &aioNMgrIob2_Create_orig);
|
||||||
|
|
||||||
|
const auto libaio = "libaio.dll";
|
||||||
|
detour::trampoline_try(libaio, "aioSci_Destroy", aioSci_Destroy, &aioSci_Destroy_orig);
|
||||||
|
detour::trampoline_try(libaio, "aioNodeMgr_Destroy",
|
||||||
|
aioNodeMgr_Destroy, &aioNodeMgr_Destroy_orig);
|
||||||
|
detour::trampoline_try(libaio, "aioNodeCtl_Destroy",
|
||||||
|
aioNodeCtl_Destroy, &aioNodeCtl_Destroy_orig);
|
||||||
|
detour::trampoline_try(libaio, "aioNodeCtl_UpdateDevicesStatus",
|
||||||
|
aioNodeCtl_UpdateDevicesStatus, &aioNodeCtl_UpdateDevicesStatus_orig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if SPICE64
|
||||||
|
|
||||||
|
namespace games::jb {
|
||||||
|
void bi2x_hook_init();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -3,6 +3,8 @@
|
|||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
|
#include "avs/game.h"
|
||||||
|
#include "bi2x_hook.h"
|
||||||
#include "cfg/configurator.h"
|
#include "cfg/configurator.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
#include "util/detour.h"
|
#include "util/detour.h"
|
||||||
@@ -10,6 +12,8 @@
|
|||||||
|
|
||||||
namespace games::jb {
|
namespace games::jb {
|
||||||
|
|
||||||
|
#if !SPICE64
|
||||||
|
|
||||||
// fixes "IP ADDR CHANGE" errors with unusual network setups (e.g. a VPN)
|
// fixes "IP ADDR CHANGE" errors with unusual network setups (e.g. a VPN)
|
||||||
static BOOL __stdcall network_addr_is_changed() {
|
static BOOL __stdcall network_addr_is_changed() {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -31,6 +35,8 @@ namespace games::jb {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
JBGame::JBGame() : Game("Jubeat") {
|
JBGame::JBGame() : Game("Jubeat") {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,8 +66,18 @@ namespace games::jb {
|
|||||||
void JBGame::attach() {
|
void JBGame::attach() {
|
||||||
Game::attach();
|
Game::attach();
|
||||||
|
|
||||||
|
#if SPICE64
|
||||||
|
if (avs::game::DLL_NAME == "jubeat2019.dll") {
|
||||||
|
libutils::load_library("libaio.dll");
|
||||||
|
libutils::load_library("libaio-iob.dll");
|
||||||
|
libutils::load_library("libaio-iob2_video.dll");
|
||||||
|
bi2x_hook_init();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
touch_attach();
|
touch_attach();
|
||||||
|
|
||||||
|
#if !SPICE64
|
||||||
// enable debug logging of gftools
|
// enable debug logging of gftools
|
||||||
HMODULE gftools = libutils::try_module("gftools.dll");
|
HMODULE gftools = libutils::try_module("gftools.dll");
|
||||||
detour::inline_hook((void *) GFDbgSetReportFunc, libutils::try_proc(
|
detour::inline_hook((void *) GFDbgSetReportFunc, libutils::try_proc(
|
||||||
@@ -75,6 +91,8 @@ namespace games::jb {
|
|||||||
network, "network_get_network_check_info"));
|
network, "network_get_network_check_info"));
|
||||||
detour::inline_hook((void *) network_get_dhcp_result, libutils::try_proc(
|
detour::inline_hook((void *) network_get_dhcp_result, libutils::try_proc(
|
||||||
network, "network_get_dhcp_result"));
|
network, "network_get_dhcp_result"));
|
||||||
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void JBGame::detach() {
|
void JBGame::detach() {
|
||||||
|
|||||||
@@ -20,6 +20,8 @@
|
|||||||
// (6, 8) in landscape.
|
// (6, 8) in landscape.
|
||||||
#define JB_BUTTON_SIZE 160
|
#define JB_BUTTON_SIZE 160
|
||||||
#define JB_MAX_BUTTON_GAP 38
|
#define JB_MAX_BUTTON_GAP 38
|
||||||
|
#define JB_T44_BUTTON_SIZE 224
|
||||||
|
#define JB_T44_BUTTON_GAP 33
|
||||||
|
|
||||||
// improved and plus modes use this reach around each button. must be >= the
|
// improved and plus modes use this reach around each button. must be >= the
|
||||||
// diagonal half of the widest gap (~27px) so the grid centre still reaches a button.
|
// diagonal half of the widest gap (~27px) so the grid centre still reaches a button.
|
||||||
@@ -41,6 +43,7 @@ namespace games::jb {
|
|||||||
static std::atomic_bool TOUCH_ENABLE = false;
|
static std::atomic_bool TOUCH_ENABLE = false;
|
||||||
static bool TOUCH_ATTACHED = false;
|
static bool TOUCH_ATTACHED = false;
|
||||||
static bool IS_PORTRAIT = true;
|
static bool IS_PORTRAIT = true;
|
||||||
|
static bool IS_T44 = false;
|
||||||
static std::atomic_uint16_t TOUCH_STATE = 0;
|
static std::atomic_uint16_t TOUCH_STATE = 0;
|
||||||
|
|
||||||
// fixed-size contact view used by the debug overlay
|
// fixed-size contact view used by the debug overlay
|
||||||
@@ -85,35 +88,45 @@ namespace games::jb {
|
|||||||
// --- touch geometry ------------------------------------------------------
|
// --- touch geometry ------------------------------------------------------
|
||||||
// gaps between the four buttons along one axis (the middle gap is 1px wider)
|
// gaps between the four buttons along one axis (the middle gap is 1px wider)
|
||||||
static const int JB_BUTTON_GAPS[3] = { 37, JB_MAX_BUTTON_GAP, 37 };
|
static const int JB_BUTTON_GAPS[3] = { 37, JB_MAX_BUTTON_GAP, 37 };
|
||||||
|
static const int JB_T44_BUTTON_GAPS[3] = {
|
||||||
|
JB_T44_BUTTON_GAP,
|
||||||
|
JB_T44_BUTTON_GAP,
|
||||||
|
JB_T44_BUTTON_GAP,
|
||||||
|
};
|
||||||
|
|
||||||
struct AxisGeometry {
|
struct AxisGeometry {
|
||||||
|
int size;
|
||||||
int button[4]; // left/top edge of each button
|
int button[4]; // left/top edge of each button
|
||||||
};
|
};
|
||||||
|
|
||||||
// left/top edges of the four buttons along one axis, starting at `first`
|
// left/top edges of the four buttons along one axis, starting at `first`
|
||||||
static AxisGeometry axis_geometry(int first) {
|
static AxisGeometry axis_geometry(int first, int size, const int gaps[3]) {
|
||||||
AxisGeometry g {};
|
AxisGeometry g {};
|
||||||
|
g.size = size;
|
||||||
g.button[0] = first;
|
g.button[0] = first;
|
||||||
for (int i = 1; i < 4; i++) {
|
for (int i = 1; i < 4; i++) {
|
||||||
g.button[i] = g.button[i - 1] + JB_BUTTON_SIZE + JB_BUTTON_GAPS[i - 1];
|
g.button[i] = g.button[i - 1] + size + gaps[i - 1];
|
||||||
}
|
}
|
||||||
return g;
|
return g;
|
||||||
}
|
}
|
||||||
|
|
||||||
// button edges for the current orientation
|
// button edges for the current orientation
|
||||||
static void touch_geometry(AxisGeometry &gx, AxisGeometry &gy) {
|
static void touch_geometry(AxisGeometry &gx, AxisGeometry &gy) {
|
||||||
if (IS_PORTRAIT) {
|
if (IS_T44) {
|
||||||
gx = axis_geometry(8);
|
gx = axis_geometry(37, JB_T44_BUTTON_SIZE, JB_T44_BUTTON_GAPS);
|
||||||
gy = axis_geometry(602);
|
gy = axis_geometry(864, JB_T44_BUTTON_SIZE, JB_T44_BUTTON_GAPS);
|
||||||
|
} else if (IS_PORTRAIT) {
|
||||||
|
gx = axis_geometry(8, JB_BUTTON_SIZE, JB_BUTTON_GAPS);
|
||||||
|
gy = axis_geometry(602, JB_BUTTON_SIZE, JB_BUTTON_GAPS);
|
||||||
} else {
|
} else {
|
||||||
gx = axis_geometry(6);
|
gx = axis_geometry(6, JB_BUTTON_SIZE, JB_BUTTON_GAPS);
|
||||||
gy = axis_geometry(8);
|
gy = axis_geometry(8, JB_BUTTON_SIZE, JB_BUTTON_GAPS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// distance from `p` to a button along one axis (0 when inside)
|
// distance from `p` to a button along one axis (0 when inside)
|
||||||
static int axis_distance(int p, int button) {
|
static int axis_distance(int p, int button, int size) {
|
||||||
int end = button + JB_BUTTON_SIZE - 1;
|
int end = button + size - 1;
|
||||||
if (p < button) {
|
if (p < button) {
|
||||||
return button - p;
|
return button - p;
|
||||||
}
|
}
|
||||||
@@ -131,8 +144,8 @@ namespace games::jb {
|
|||||||
int best_dist = 0;
|
int best_dist = 0;
|
||||||
for (int r = 0; r < 4; r++) {
|
for (int r = 0; r < 4; r++) {
|
||||||
for (int c = 0; c < 4; c++) {
|
for (int c = 0; c < 4; c++) {
|
||||||
int dx = axis_distance(px, gx.button[c]);
|
int dx = axis_distance(px, gx.button[c], gx.size);
|
||||||
int dy = axis_distance(py, gy.button[r]);
|
int dy = axis_distance(py, gy.button[r], gy.size);
|
||||||
int dist = dx * dx + dy * dy;
|
int dist = dx * dx + dy * dy;
|
||||||
if (dist <= radius * radius && (best_index < 0 || dist < best_dist)) {
|
if (dist <= radius * radius && (best_index < 0 || dist < best_dist)) {
|
||||||
best_dist = dist;
|
best_dist = dist;
|
||||||
@@ -145,7 +158,8 @@ namespace games::jb {
|
|||||||
|
|
||||||
// detection reach for the current algorithm (0 = register only inside a button)
|
// detection reach for the current algorithm (0 = register only inside a button)
|
||||||
static int touch_radius() {
|
static int touch_radius() {
|
||||||
return TOUCH_ALGORITHM == AcAccurate ? 0 : JB_TOUCH_RADIUS;
|
return TOUCH_ALGORITHM == AcAccurate || (IS_T44 && TOUCH_ALGORITHM == Legacy) ?
|
||||||
|
0 : JB_TOUCH_RADIUS;
|
||||||
}
|
}
|
||||||
|
|
||||||
// mark the buttons a touch at (px, py) hits: only the nearest within `radius`, or
|
// mark the buttons a touch at (px, py) hits: only the nearest within `radius`, or
|
||||||
@@ -162,8 +176,8 @@ namespace games::jb {
|
|||||||
}
|
}
|
||||||
for (int r = 0; r < 4; r++) {
|
for (int r = 0; r < 4; r++) {
|
||||||
for (int c = 0; c < 4; c++) {
|
for (int c = 0; c < 4; c++) {
|
||||||
int dx = axis_distance(px, gx.button[c]);
|
int dx = axis_distance(px, gx.button[c], gx.size);
|
||||||
int dy = axis_distance(py, gy.button[r]);
|
int dy = axis_distance(py, gy.button[r], gy.size);
|
||||||
if (dx * dx + dy * dy <= radius * radius) {
|
if (dx * dx + dy * dy <= radius * radius) {
|
||||||
state |= uint16_t(1) << (r * 4 + c);
|
state |= uint16_t(1) << (r * 4 + c);
|
||||||
}
|
}
|
||||||
@@ -184,11 +198,15 @@ namespace games::jb {
|
|||||||
// one-time touch window attach
|
// one-time touch window attach
|
||||||
if (!TOUCH_ATTACHED) {
|
if (!TOUCH_ATTACHED) {
|
||||||
|
|
||||||
|
IS_T44 = avs::game::is_model("T44");
|
||||||
|
IS_PORTRAIT = IS_T44 || avs::game::is_model("L44");
|
||||||
|
|
||||||
// find the game window: prefer the foreground window, else search by
|
// find the game window: prefer the foreground window, else search by
|
||||||
// title (the model name prefixes the window title in every version)
|
// title (T44 uses a fixed title instead of the model prefix)
|
||||||
|
const char *window_title = IS_T44 ? "jubeat 10 main" : avs::game::MODEL;
|
||||||
HWND wnd = GetForegroundWindow();
|
HWND wnd = GetForegroundWindow();
|
||||||
if (!string_begins_with(GetActiveWindowTitle(), avs::game::MODEL)) {
|
if (!string_begins_with(GetActiveWindowTitle(), window_title)) {
|
||||||
wnd = FindWindowBeginsWith(avs::game::MODEL);
|
wnd = FindWindowBeginsWith(window_title);
|
||||||
}
|
}
|
||||||
if (!wnd) {
|
if (!wnd) {
|
||||||
log_warning("jubeat", "could not find window handle for touch");
|
log_warning("jubeat", "could not find window handle for touch");
|
||||||
@@ -197,10 +215,6 @@ namespace games::jb {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// only the L44 model runs in portrait; set this before starting the
|
|
||||||
// touch-window thread so the renderer only observes the final value
|
|
||||||
IS_PORTRAIT = avs::game::is_model("L44");
|
|
||||||
|
|
||||||
log_info("jubeat", "using window handle for touch: {}", fmt::ptr(wnd));
|
log_info("jubeat", "using window handle for touch: {}", fmt::ptr(wnd));
|
||||||
|
|
||||||
// let the rawinput stack correct the aspect ratio
|
// let the rawinput stack correct the aspect ratio
|
||||||
@@ -232,7 +246,7 @@ namespace games::jb {
|
|||||||
return !touch_matured(tp, now_ms, threshold_ms);
|
return !touch_matured(tp, now_ms, threshold_ms);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (TOUCH_ALGORITHM == Legacy) {
|
if (TOUCH_ALGORITHM == Legacy && !IS_T44) {
|
||||||
|
|
||||||
// legacy: evenly divide the play area into a 4x4 grid
|
// legacy: evenly divide the play area into a 4x4 grid
|
||||||
auto offset = IS_PORTRAIT ? 580 : 0;
|
auto offset = IS_PORTRAIT ? 580 : 0;
|
||||||
@@ -311,7 +325,7 @@ namespace games::jb {
|
|||||||
for (int c = 0; c < 4; c++) {
|
for (int c = 0; c < 4; c++) {
|
||||||
cells[r * 4 + c] = {
|
cells[r * 4 + c] = {
|
||||||
gx.button[c], gy.button[r],
|
gx.button[c], gy.button[r],
|
||||||
gx.button[c] + JB_BUTTON_SIZE, gy.button[r] + JB_BUTTON_SIZE
|
gx.button[c] + gx.size, gy.button[r] + gy.size
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -352,8 +366,8 @@ namespace games::jb {
|
|||||||
const AxisGeometry &gx, const AxisGeometry &gy, int arc_radius) {
|
const AxisGeometry &gx, const AxisGeometry &gy, int arc_radius) {
|
||||||
int c = index % 4;
|
int c = index % 4;
|
||||||
int r = index / 4;
|
int r = index / 4;
|
||||||
double mid = std::atan2((gy.button[r] + JB_BUTTON_SIZE / 2) - py,
|
double mid = std::atan2((gy.button[r] + gy.size / 2) - py,
|
||||||
(gx.button[c] + JB_BUTTON_SIZE / 2) - px);
|
(gx.button[c] + gx.size / 2) - px);
|
||||||
const double quarter = 3.14159265358979323846 / 2.0;
|
const double quarter = 3.14159265358979323846 / 2.0;
|
||||||
const int segments = 16;
|
const int segments = 16;
|
||||||
POINT arc[segments + 1];
|
POINT arc[segments + 1];
|
||||||
@@ -425,7 +439,7 @@ namespace games::jb {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// legacy divides the field evenly; the other algorithms use button squares
|
// legacy divides the field evenly; the other algorithms use button squares
|
||||||
bool legacy = (TOUCH_ALGORITHM == Legacy);
|
bool legacy = TOUCH_ALGORITHM == Legacy && !IS_T44;
|
||||||
AxisGeometry gx {}, gy {};
|
AxisGeometry gx {}, gy {};
|
||||||
if (!legacy) {
|
if (!legacy) {
|
||||||
touch_geometry(gx, gy);
|
touch_geometry(gx, gy);
|
||||||
|
|||||||
@@ -296,8 +296,9 @@ void hooks::lang::early_init() {
|
|||||||
&GetLocaleInfoA_orig);
|
&GetLocaleInfoA_orig);
|
||||||
}
|
}
|
||||||
|
|
||||||
// for TDJ subscreen search keyboard
|
// for TDJ subscreen search keyboard and T44 narrow-string handling
|
||||||
if (avs::game::is_model("LDJ") && games::iidx::TDJ_MODE) {
|
if ((avs::game::is_model("LDJ") && games::iidx::TDJ_MODE) ||
|
||||||
|
avs::game::is_model("T44")) {
|
||||||
log_info("hooks::lang", "hooking IsDBCSLeadByte");
|
log_info("hooks::lang", "hooking IsDBCSLeadByte");
|
||||||
detour::trampoline_try(
|
detour::trampoline_try(
|
||||||
"kernel32.dll",
|
"kernel32.dll",
|
||||||
@@ -306,7 +307,7 @@ void hooks::lang::early_init() {
|
|||||||
&IsDBCSLeadByte_orig);
|
&IsDBCSLeadByte_orig);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (games::gitadora::is_arena_model()) {
|
if (games::gitadora::is_arena_model() || avs::game::is_model("T44")) {
|
||||||
log_info("hooks::lang", "hooking WideCharToMultiByte");
|
log_info("hooks::lang", "hooking WideCharToMultiByte");
|
||||||
detour::trampoline_try(
|
detour::trampoline_try(
|
||||||
"kernel32.dll",
|
"kernel32.dll",
|
||||||
|
|||||||
@@ -1855,6 +1855,13 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (check_dll("jubeat2019.dll")) {
|
||||||
|
avs::game::DLL_NAME = "jubeat2019.dll";
|
||||||
|
attach_io = true;
|
||||||
|
attach_jb = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// RB
|
// RB
|
||||||
if (check_dll("reflecbeat.dll")) {
|
if (check_dll("reflecbeat.dll")) {
|
||||||
avs::game::DLL_NAME = "reflecbeat.dll";
|
avs::game::DLL_NAME = "reflecbeat.dll";
|
||||||
@@ -2283,7 +2290,7 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
games.push_back(new games::sdvx::SDVXGame());
|
games.push_back(new games::sdvx::SDVXGame());
|
||||||
}
|
}
|
||||||
if (attach_jb) {
|
if (attach_jb) {
|
||||||
avs::core::set_default_heap_size("jubeat.dll");
|
avs::core::set_default_heap_size(avs::game::DLL_NAME);
|
||||||
games.push_back(new games::jb::JBGame());
|
games.push_back(new games::jb::JBGame());
|
||||||
}
|
}
|
||||||
if (attach_nostalgia) {
|
if (attach_nostalgia) {
|
||||||
|
|||||||
@@ -673,7 +673,8 @@ void eamuse_autodetect_game() {
|
|||||||
eamuse_set_game("Beatmania IIDX");
|
eamuse_set_game("Beatmania IIDX");
|
||||||
else if (avs::game::is_model("J44") ||
|
else if (avs::game::is_model("J44") ||
|
||||||
avs::game::is_model("K44") ||
|
avs::game::is_model("K44") ||
|
||||||
avs::game::is_model("L44"))
|
avs::game::is_model("L44") ||
|
||||||
|
avs::game::is_model("T44"))
|
||||||
eamuse_set_game("Jubeat");
|
eamuse_set_game("Jubeat");
|
||||||
else if (avs::game::is_model("KDM"))
|
else if (avs::game::is_model("KDM"))
|
||||||
eamuse_set_game("Dance Evolution");
|
eamuse_set_game("Dance Evolution");
|
||||||
|
|||||||
Reference in New Issue
Block a user