Compare commits

...

1 Commits

Author SHA1 Message Date
bicarus c590b87cff jb: add t44 support (#845)
With help from certain sea creature.
2026-08-01 01:20:31 -07:00
12 changed files with 422 additions and 32 deletions
+1
View File
@@ -411,6 +411,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
games/sdvx/sdvx_live2d.cpp
games/sdvx/io.cpp
games/sdvx/camera.cpp
games/jb/bi2x_hook.cpp
games/jb/jb.cpp
games/jb/jb_touch.cpp
games/jb/io.cpp
+1 -1
View File
@@ -239,7 +239,7 @@ bool ICCADevice::parse_msg(MessageData *msg_in,
// SDVX Old cabinet mode
if (avs::game::is_model("KFC") && avs::game::SPEC[0] != 'G' && avs::game::SPEC[0] != 'H')
answer_type = 1;
if (avs::game::is_model("L44"))
if (avs::game::is_model({ "L44", "T44" }))
answer_type = 2;
// check answer type
+1
View File
@@ -1114,6 +1114,7 @@ namespace avs {
#endif
// jubeat
{"jubeat.dll", 0x2000000},
{"jubeat2019.dll", 0x10000000},
// MUSECA
{"museca.dll", 0xC000000},
+1
View File
@@ -591,6 +591,7 @@ namespace avs {
// for proper reporting of Omnimix and other song packs
if (_stricmp(EA3_MODEL, "LDJ") == 0 ||
_stricmp(EA3_MODEL, "L44") == 0 ||
_stricmp(EA3_MODEL, "T44") == 0 ||
_stricmp(EA3_MODEL, "M39") == 0 ||
_stricmp(EA3_MODEL, "KFC") == 0)
{
+1
View File
@@ -133,6 +133,7 @@ namespace games {
buttons_help.insert({ jb, jb::get_buttons_help() });
lights.insert({ jb, jb::get_lights() });
file_hints[jb].push_back({"jubeat.dll"});
file_hints[jb].push_back({"jubeat2019.dll"});
// mga
const std::string mga("Metal Gear");
+336
View File
@@ -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
+9
View File
@@ -0,0 +1,9 @@
#pragma once
#if SPICE64
namespace games::jb {
void bi2x_hook_init();
}
#endif
+18
View File
@@ -3,6 +3,8 @@
#include <windows.h>
#include <filesystem>
#include "avs/game.h"
#include "bi2x_hook.h"
#include "cfg/configurator.h"
#include "util/logging.h"
#include "util/detour.h"
@@ -10,6 +12,8 @@
namespace games::jb {
#if !SPICE64
// fixes "IP ADDR CHANGE" errors with unusual network setups (e.g. a VPN)
static BOOL __stdcall network_addr_is_changed() {
return 0;
@@ -31,6 +35,8 @@ namespace games::jb {
return 0;
}
#endif
JBGame::JBGame() : Game("Jubeat") {
}
@@ -60,8 +66,18 @@ namespace games::jb {
void JBGame::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();
#if !SPICE64
// enable debug logging of gftools
HMODULE gftools = libutils::try_module("gftools.dll");
detour::inline_hook((void *) GFDbgSetReportFunc, libutils::try_proc(
@@ -75,6 +91,8 @@ namespace games::jb {
network, "network_get_network_check_info"));
detour::inline_hook((void *) network_get_dhcp_result, libutils::try_proc(
network, "network_get_dhcp_result"));
#endif
}
void JBGame::detach() {
+40 -26
View File
@@ -20,6 +20,8 @@
// (6, 8) in landscape.
#define JB_BUTTON_SIZE 160
#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
// 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 bool TOUCH_ATTACHED = false;
static bool IS_PORTRAIT = true;
static bool IS_T44 = false;
static std::atomic_uint16_t TOUCH_STATE = 0;
// fixed-size contact view used by the debug overlay
@@ -85,35 +88,45 @@ namespace games::jb {
// --- touch geometry ------------------------------------------------------
// 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_T44_BUTTON_GAPS[3] = {
JB_T44_BUTTON_GAP,
JB_T44_BUTTON_GAP,
JB_T44_BUTTON_GAP,
};
struct AxisGeometry {
int size;
int button[4]; // left/top edge of each button
};
// 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 {};
g.size = size;
g.button[0] = first;
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;
}
// button edges for the current orientation
static void touch_geometry(AxisGeometry &gx, AxisGeometry &gy) {
if (IS_PORTRAIT) {
gx = axis_geometry(8);
gy = axis_geometry(602);
if (IS_T44) {
gx = axis_geometry(37, JB_T44_BUTTON_SIZE, JB_T44_BUTTON_GAPS);
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 {
gx = axis_geometry(6);
gy = axis_geometry(8);
gx = axis_geometry(6, JB_BUTTON_SIZE, JB_BUTTON_GAPS);
gy = axis_geometry(8, JB_BUTTON_SIZE, JB_BUTTON_GAPS);
}
}
// distance from `p` to a button along one axis (0 when inside)
static int axis_distance(int p, int button) {
int end = button + JB_BUTTON_SIZE - 1;
static int axis_distance(int p, int button, int size) {
int end = button + size - 1;
if (p < button) {
return button - p;
}
@@ -131,8 +144,8 @@ namespace games::jb {
int best_dist = 0;
for (int r = 0; r < 4; r++) {
for (int c = 0; c < 4; c++) {
int dx = axis_distance(px, gx.button[c]);
int dy = axis_distance(py, gy.button[r]);
int dx = axis_distance(px, gx.button[c], gx.size);
int dy = axis_distance(py, gy.button[r], gy.size);
int dist = dx * dx + dy * dy;
if (dist <= radius * radius && (best_index < 0 || dist < best_dist)) {
best_dist = dist;
@@ -145,7 +158,8 @@ namespace games::jb {
// detection reach for the current algorithm (0 = register only inside a button)
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
@@ -162,8 +176,8 @@ namespace games::jb {
}
for (int r = 0; r < 4; r++) {
for (int c = 0; c < 4; c++) {
int dx = axis_distance(px, gx.button[c]);
int dy = axis_distance(py, gy.button[r]);
int dx = axis_distance(px, gx.button[c], gx.size);
int dy = axis_distance(py, gy.button[r], gy.size);
if (dx * dx + dy * dy <= radius * radius) {
state |= uint16_t(1) << (r * 4 + c);
}
@@ -184,11 +198,15 @@ namespace games::jb {
// one-time touch window attach
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
// 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();
if (!string_begins_with(GetActiveWindowTitle(), avs::game::MODEL)) {
wnd = FindWindowBeginsWith(avs::game::MODEL);
if (!string_begins_with(GetActiveWindowTitle(), window_title)) {
wnd = FindWindowBeginsWith(window_title);
}
if (!wnd) {
log_warning("jubeat", "could not find window handle for touch");
@@ -197,10 +215,6 @@ namespace games::jb {
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));
// let the rawinput stack correct the aspect ratio
@@ -232,7 +246,7 @@ namespace games::jb {
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
auto offset = IS_PORTRAIT ? 580 : 0;
@@ -311,7 +325,7 @@ namespace games::jb {
for (int c = 0; c < 4; c++) {
cells[r * 4 + c] = {
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) {
int c = index % 4;
int r = index / 4;
double mid = std::atan2((gy.button[r] + JB_BUTTON_SIZE / 2) - py,
(gx.button[c] + JB_BUTTON_SIZE / 2) - px);
double mid = std::atan2((gy.button[r] + gy.size / 2) - py,
(gx.button[c] + gx.size / 2) - px);
const double quarter = 3.14159265358979323846 / 2.0;
const int segments = 16;
POINT arc[segments + 1];
@@ -425,7 +439,7 @@ namespace games::jb {
}
// 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 {};
if (!legacy) {
touch_geometry(gx, gy);
+4 -3
View File
@@ -296,8 +296,9 @@ void hooks::lang::early_init() {
&GetLocaleInfoA_orig);
}
// for TDJ subscreen search keyboard
if (avs::game::is_model("LDJ") && games::iidx::TDJ_MODE) {
// for TDJ subscreen search keyboard and T44 narrow-string handling
if ((avs::game::is_model("LDJ") && games::iidx::TDJ_MODE) ||
avs::game::is_model("T44")) {
log_info("hooks::lang", "hooking IsDBCSLeadByte");
detour::trampoline_try(
"kernel32.dll",
@@ -306,7 +307,7 @@ void hooks::lang::early_init() {
&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");
detour::trampoline_try(
"kernel32.dll",
+8 -1
View File
@@ -1855,6 +1855,13 @@ int main_implementation(int argc, char *argv[]) {
break;
}
if (check_dll("jubeat2019.dll")) {
avs::game::DLL_NAME = "jubeat2019.dll";
attach_io = true;
attach_jb = true;
break;
}
// RB
if (check_dll("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());
}
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());
}
if (attach_nostalgia) {
+2 -1
View File
@@ -673,7 +673,8 @@ void eamuse_autodetect_game() {
eamuse_set_game("Beatmania IIDX");
else if (avs::game::is_model("J44") ||
avs::game::is_model("K44") ||
avs::game::is_model("L44"))
avs::game::is_model("L44") ||
avs::game::is_model("T44"))
eamuse_set_game("Jubeat");
else if (avs::game::is_model("KDM"))
eamuse_set_game("Dance Evolution");