mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-09-29 10:17:56 +03:00
Dance around early support (#897)
## Link to GitHub Issue or related Pull Request, if one exists N/A ## Description of change This PR add initial supports for running Dance aRound in window mode, with keyboard, cardio supports. ## Testing - Tested game play under Windows 10 using Kinect V1 with my visionpose compatible layer (https://github.com/Avimitin/dance-around-anygear) https://github.com/user-attachments/assets/4b821dcf-751d-4953-b626-4a9491569183 - Tested full game play, user profile saving with Asphyxia Assisted by AI. --------- Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
@@ -554,6 +554,9 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
games/pc/pc.cpp
|
||||
games/pc/io.cpp
|
||||
games/pc/bi2x_hook.cpp
|
||||
games/udn/udn.cpp
|
||||
games/udn/io.cpp
|
||||
games/udn/bi2x_hook.cpp
|
||||
|
||||
# hooks
|
||||
hooks/audio/acm.cpp
|
||||
@@ -605,6 +608,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
hooks/graphics/backends/d3d9/d3d9_texture.cpp
|
||||
hooks/graphics/backends/d3d11/d3d11_backend.cpp
|
||||
hooks/graphics/backends/d3d11/d3d11_swapchain.cpp
|
||||
hooks/graphics/backends/d3d11/d3d11_swapchain_udn.cpp
|
||||
hooks/graphics/backends/d3d11/d3d11_factory.cpp
|
||||
hooks/graphics/backends/d3d11/d3d11_vtable_capture.cpp
|
||||
hooks/graphics/backends/d3d11/d3d11_screenshot.cpp
|
||||
|
||||
@@ -573,6 +573,13 @@ static inline void easrv_init_messages() {
|
||||
"\x5b\x99\xa1\x15\x78\x16\x84\xe0\x67\x2c\x75\xf7\xc0\x38\xd5\x20\xf7\x81\xae\x1f\x77\x02\xdf\x0c"
|
||||
"\x60\x68\x2e\x26\xe5\xbb\xb7\xb4\x41\x72\xc4\xa4\x43\x2a\xc8\x81\xd3\x8a\x1f\xe8\x94\x39\x7e\xf6"
|
||||
"\x69\x56\x9e\xaf\x06\x10\xfd\x47\x69\xb8\x14\xe6\x48\x36\xd6\x25\x18\x0c\x2b\x3d\x69");
|
||||
if (avs::game::is_model("UDN")) {
|
||||
// Only UDN needs an ASCII location/name for the Unity score-screen marshaler.
|
||||
// These 12 encoded bytes replace the name with "SPICE2 LOCAL";
|
||||
// its terminator, the other fields and the response length stay unchanged.
|
||||
EA_FACILITY_GET.replace(311, 12,
|
||||
"\xf2\x20\xcc\x9a\xe3\x32\x78\x80\x9a\xba\xcd\xc3");
|
||||
}
|
||||
EA_PCBEVENT_PUT = std::string(
|
||||
"\x17\x7e\x7c\x0e\x1a\x15\x41\x0b\xae\xd5\xa0\xf5\x2c\xa1\xb9\xd2\x53\xca\xd2\x0d\x81\x34\xdb\x94\x79\xd0"
|
||||
"\xc8\x9d\xb5\xfb\xef\x97");
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
#include "qks/io.h"
|
||||
#include "mfg/io.h"
|
||||
#include "pc/io.h"
|
||||
#include "udn/io.h"
|
||||
|
||||
namespace games {
|
||||
|
||||
@@ -332,6 +333,14 @@ namespace games {
|
||||
analogs.insert({ pc, pc::get_analogs() });
|
||||
lights.insert({ pc, pc::get_lights() });
|
||||
file_hints[pc].push_back({"game/svm.exe"});
|
||||
|
||||
// DANCE aROUND
|
||||
const std::string udn("DANCE aROUND");
|
||||
games.push_back(udn);
|
||||
buttons.insert({ udn, udn::get_buttons() });
|
||||
buttons_help.insert({ udn, udn::get_buttons_help() });
|
||||
lights.insert({ udn, udn::get_lights() });
|
||||
file_hints[udn].push_back({"game/dancearound_Data"});
|
||||
}
|
||||
|
||||
const std::vector<std::string> &get_games() {
|
||||
|
||||
@@ -0,0 +1,686 @@
|
||||
#include "bi2x_hook.h"
|
||||
|
||||
#if SPICE64
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#include "acio/icca/icca.h"
|
||||
#include "games/io.h"
|
||||
#include "io.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "util/execexe.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/tapeled.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
namespace games::udn {
|
||||
|
||||
struct AIO_SCI_COMM {
|
||||
uint8_t data[0xf8];
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB2 {
|
||||
uint8_t data[0xe30];
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_UDN {
|
||||
uint8_t data[0x3b00];
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_WRFIRM {
|
||||
uint8_t data[0x20f48];
|
||||
};
|
||||
|
||||
struct AIO_IOB_ICCA;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct AIO_IOB2_BI2X_UDN__INPUT {
|
||||
uint8_t DevIoCounter;
|
||||
uint8_t bExIoAErr;
|
||||
uint8_t bExIoBErr;
|
||||
uint8_t bPcPowerOn;
|
||||
uint8_t bPcPowerCheck;
|
||||
uint8_t CoinCount;
|
||||
uint8_t bTest;
|
||||
uint8_t bService;
|
||||
uint8_t bCoinSw;
|
||||
uint8_t bCoinJam;
|
||||
uint8_t bStart;
|
||||
uint8_t bUp;
|
||||
uint8_t bDown;
|
||||
uint8_t bLeft;
|
||||
uint8_t bRight;
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_UDN__DEVSTATUS {
|
||||
uint8_t InputCounter;
|
||||
uint8_t OutputCounter;
|
||||
uint8_t TapeLedCounter;
|
||||
uint8_t TapeLedRate[8];
|
||||
AIO_IOB2_BI2X_UDN__INPUT Input;
|
||||
uint8_t InputData[241];
|
||||
uint8_t OutputData[48];
|
||||
uint8_t IoResetData[4];
|
||||
uint8_t PlyrInHist[18];
|
||||
};
|
||||
|
||||
// Fixed card-status wire format used by the compatibility interface.
|
||||
// The payload is exactly 58 bytes.
|
||||
// Field widths remain explicit to keep the boundary stable.
|
||||
struct AIO_IOB_ICCA__CARDID {
|
||||
uint8_t CardType;
|
||||
uint8_t cbCardId;
|
||||
uint8_t CardId[10];
|
||||
};
|
||||
|
||||
struct AIO_IOB_ICCA__INPUT {
|
||||
uint8_t cuStatus;
|
||||
uint8_t cuError;
|
||||
AIO_IOB_ICCA__CARDID CardId;
|
||||
uint8_t KeyNo;
|
||||
uint8_t KeyCount;
|
||||
uint8_t bKeyDataValid;
|
||||
uint8_t Key2No;
|
||||
uint8_t Key2Count;
|
||||
uint8_t bKey2DataValid;
|
||||
uint8_t bKey0;
|
||||
uint8_t bKey1;
|
||||
uint8_t bKey4;
|
||||
uint8_t bKey7;
|
||||
uint8_t bKey00;
|
||||
uint8_t bKey2;
|
||||
uint8_t bKey5;
|
||||
uint8_t bKey8;
|
||||
uint8_t bKeyBK;
|
||||
uint8_t bKey3;
|
||||
uint8_t bKey6;
|
||||
uint8_t bKey9;
|
||||
uint8_t CN1_1;
|
||||
uint8_t CN1_2;
|
||||
uint8_t CN1_3;
|
||||
uint8_t CN1_4;
|
||||
};
|
||||
|
||||
struct AIO_IOB_ICCA__DEVSTATUS {
|
||||
uint8_t InputCounter;
|
||||
uint8_t OutputCounter;
|
||||
AIO_IOB_ICCA__INPUT Input;
|
||||
uint8_t InputData[16];
|
||||
uint8_t OutputData[4];
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
static_assert(sizeof(AIO_SCI_COMM) == 0xf8);
|
||||
static_assert(sizeof(AIO_NMGR_IOB2) == 0xe30);
|
||||
static_assert(sizeof(AIO_IOB2_BI2X_UDN) == 0x3b00);
|
||||
static_assert(sizeof(AIO_IOB2_BI2X_WRFIRM) == 0x20f48);
|
||||
static_assert(sizeof(AIO_IOB2_BI2X_UDN__INPUT) == 15);
|
||||
static_assert(sizeof(AIO_IOB2_BI2X_UDN__DEVSTATUS) == 337);
|
||||
static_assert(sizeof(AIO_IOB_ICCA__CARDID) == 12);
|
||||
static_assert(sizeof(AIO_IOB_ICCA__INPUT) == 36);
|
||||
static_assert(sizeof(AIO_IOB_ICCA__DEVSTATUS) == 58);
|
||||
|
||||
using aioIob2Bi2xUDN_Create_t = AIO_IOB2_BI2X_UDN *(__fastcall *)(AIO_NMGR_IOB2 *, uint32_t);
|
||||
using aioIob2Bi2xUDN_GetDeviceStatus_t = uint32_t (__fastcall *)(
|
||||
AIO_IOB2_BI2X_UDN *, AIO_IOB2_BI2X_UDN__DEVSTATUS *, uint32_t);
|
||||
using aioIob2Bi2xAC1_GetDeviceStatus_t = uint32_t (__fastcall *)(void *, void *, uint32_t);
|
||||
using aioIob2Bi2xUDN_IoReset_t = void (__fastcall *)(AIO_IOB2_BI2X_UDN *, uint32_t);
|
||||
using aioIob2Bi2xUDN_SetWatchDogTimer_t = void (__fastcall *)(AIO_IOB2_BI2X_UDN *, uint8_t);
|
||||
using aioIob2Bi2xUDN_ControlCoinBlocker_t = void (__fastcall *)(AIO_IOB2_BI2X_UDN *, uint32_t, bool);
|
||||
using aioIob2Bi2xUDN_AddCounter_t = void (__fastcall *)(AIO_IOB2_BI2X_UDN *, uint32_t, uint32_t);
|
||||
using aioIob2Bi2xUDN_SetStartLamp_t = void (__fastcall *)(AIO_IOB2_BI2X_UDN *, bool);
|
||||
using aioIob2Bi2xUDN_SetPlayerButtonLamp_t = void (__fastcall *)(AIO_IOB2_BI2X_UDN *, uint32_t, bool);
|
||||
using aioIob2Bi2xUDN_SetIccrLed_t = void (__fastcall *)(AIO_IOB2_BI2X_UDN *, uint32_t);
|
||||
using aioIob2Bi2xUDN_SetTapeLedDataLimit_t = void (__fastcall *)(
|
||||
AIO_IOB2_BI2X_UDN *, uint32_t, uint8_t, uint8_t);
|
||||
using aioIob2Bi2xUDN_SetTapeLedData_t = void (__fastcall *)(AIO_IOB2_BI2X_UDN *, uint32_t, uint8_t *);
|
||||
using aioIob2Bi2x_OpenSciUsbCdc_t = AIO_SCI_COMM *(__fastcall *)(uint32_t);
|
||||
using aioIob2Bi2x_CreateWriteFirmContext_t = AIO_IOB2_BI2X_WRFIRM *(__fastcall *)(uint32_t, uint32_t);
|
||||
using aioIob2Bi2x_DestroyWriteFirmContext_t = void (__fastcall *)(AIO_IOB2_BI2X_WRFIRM *);
|
||||
using aioIob2Bi2x_WriteFirmGetState_t = int32_t (__fastcall *)(AIO_IOB2_BI2X_WRFIRM *);
|
||||
|
||||
using aioIobIcca_Create_t = AIO_IOB_ICCA *(__fastcall *)(void *, uint32_t, uint8_t);
|
||||
using aioIobIcca_GetDeviceStatus_t = uint32_t (__fastcall *)(
|
||||
AIO_IOB_ICCA *, AIO_IOB_ICCA__DEVSTATUS *, uint32_t);
|
||||
using aioIobIcca_BeginGetCardId_t = void (__fastcall *)(AIO_IOB_ICCA *);
|
||||
using aioIobIcca_EndGetCardId_t = void (__fastcall *)(AIO_IOB_ICCA *);
|
||||
|
||||
using aioNMgrIob2_Create_t = AIO_NMGR_IOB2 *(__fastcall *)(AIO_SCI_COMM *, uint32_t);
|
||||
using aioNMgrIob_BeginManage_t = void (__fastcall *)(AIO_NMGR_IOB2 *);
|
||||
|
||||
using aioNodeMgr_Destroy_t = void (__fastcall *)(AIO_NMGR_IOB2 *);
|
||||
using aioNodeMgr_GetState_t = int32_t (__fastcall *)(AIO_NMGR_IOB2 *);
|
||||
using aioNodeMgr_IsReady_t = bool (__fastcall *)(AIO_NMGR_IOB2 *, int32_t);
|
||||
using aioNodeCtl_Destroy_t = void (__fastcall *)(AIO_IOB2_BI2X_UDN *);
|
||||
using aioNodeCtl_GetState_t = int32_t (__fastcall *)(AIO_IOB2_BI2X_UDN *);
|
||||
using aioNodeCtl_IsReady_t = bool (__fastcall *)(AIO_IOB2_BI2X_UDN *, int32_t);
|
||||
|
||||
static aioIob2Bi2xUDN_Create_t aioIob2Bi2xUDN_Create_orig = nullptr;
|
||||
static aioIob2Bi2xUDN_GetDeviceStatus_t aioIob2Bi2xUDN_GetDeviceStatus_orig = nullptr;
|
||||
static aioIob2Bi2xAC1_GetDeviceStatus_t aioIob2Bi2xAC1_GetDeviceStatus_orig = nullptr;
|
||||
static aioIob2Bi2xUDN_IoReset_t aioIob2Bi2xUDN_IoReset_orig = nullptr;
|
||||
static aioIob2Bi2xUDN_SetWatchDogTimer_t aioIob2Bi2xUDN_SetWatchDogTimer_orig = nullptr;
|
||||
static aioIob2Bi2xUDN_ControlCoinBlocker_t aioIob2Bi2xUDN_ControlCoinBlocker_orig = nullptr;
|
||||
static aioIob2Bi2xUDN_AddCounter_t aioIob2Bi2xUDN_AddCounter_orig = nullptr;
|
||||
static aioIob2Bi2xUDN_SetStartLamp_t aioIob2Bi2xUDN_SetStartLamp_orig = nullptr;
|
||||
static aioIob2Bi2xUDN_SetPlayerButtonLamp_t aioIob2Bi2xUDN_SetPlayerButtonLamp_orig = nullptr;
|
||||
static aioIob2Bi2xUDN_SetIccrLed_t aioIob2Bi2xUDN_SetIccrLed_orig = nullptr;
|
||||
static aioIob2Bi2xUDN_SetTapeLedDataLimit_t aioIob2Bi2xUDN_SetTapeLedDataLimit_orig = nullptr;
|
||||
static aioIob2Bi2xUDN_SetTapeLedData_t aioIob2Bi2xUDN_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 aioIobIcca_Create_t aioIobIcca_Create_orig = nullptr;
|
||||
static aioIobIcca_GetDeviceStatus_t aioIobIcca_GetDeviceStatus_orig = nullptr;
|
||||
static aioIobIcca_BeginGetCardId_t aioIobIcca_BeginGetCardId_orig = nullptr;
|
||||
static aioIobIcca_EndGetCardId_t aioIobIcca_EndGetCardId_orig = nullptr;
|
||||
|
||||
static aioNMgrIob2_Create_t aioNMgrIob2_Create_orig = nullptr;
|
||||
static aioNMgrIob_BeginManage_t aioNMgrIob_BeginManage_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 aioNodeCtl_Destroy_t aioNodeCtl_Destroy_orig = nullptr;
|
||||
static aioNodeCtl_GetState_t aioNodeCtl_GetState_orig = nullptr;
|
||||
static aioNodeCtl_IsReady_t aioNodeCtl_IsReady_orig = nullptr;
|
||||
|
||||
static std::unique_ptr<AIO_SCI_COMM> aio_sci_comm;
|
||||
static std::unique_ptr<AIO_NMGR_IOB2> aio_node_manager;
|
||||
static AIO_NMGR_IOB2 *aio_icca_node_manager = nullptr;
|
||||
static std::unique_ptr<AIO_IOB2_BI2X_UDN> aio_udn;
|
||||
static std::unique_ptr<AIO_IOB2_BI2X_WRFIRM> aio_write_firm;
|
||||
static uint8_t input_counter = 0;
|
||||
|
||||
// Native ICCA status/begin/end operations also lock the reader state.
|
||||
// The managed wrapper can destroy its node from a finalizer thread.
|
||||
static std::mutex icca_mutex;
|
||||
static AIO_IOB_ICCA *aio_icca = nullptr;
|
||||
static bool icca_reading = false;
|
||||
static bool icca_card_ready = false;
|
||||
static uint8_t icca_card_uid[8] {};
|
||||
static uint8_t icca_input_counter = 0;
|
||||
|
||||
static void write_light(Lights::udn_lights_t light, float value) {
|
||||
auto &lights = get_lights();
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights.at(light), value);
|
||||
}
|
||||
|
||||
static AIO_IOB2_BI2X_UDN *__fastcall aioIob2Bi2xUDN_Create(
|
||||
AIO_NMGR_IOB2 *node_manager, uint32_t device_id) {
|
||||
|
||||
if (node_manager != aio_node_manager.get()) {
|
||||
return aioIob2Bi2xUDN_Create_orig(node_manager, device_id);
|
||||
}
|
||||
|
||||
if (!aio_udn) {
|
||||
aio_udn = std::make_unique<AIO_IOB2_BI2X_UDN>();
|
||||
log_info("udn_bi2x", "UDN node created");
|
||||
}
|
||||
return aio_udn.get();
|
||||
}
|
||||
|
||||
static uint32_t __fastcall aioIob2Bi2xAC1_GetDeviceStatus(
|
||||
void *node, void *status, uint32_t status_size) {
|
||||
|
||||
// aioIob2Bi2xUDN.GetDeviceStatus() first invokes its managed AC1 base method.
|
||||
if (node == aio_udn.get()) {
|
||||
if (status && status_size > 0) {
|
||||
std::memset(status, 0, status_size);
|
||||
}
|
||||
return status_size;
|
||||
}
|
||||
return aioIob2Bi2xAC1_GetDeviceStatus_orig(node, status, status_size);
|
||||
}
|
||||
|
||||
static uint32_t __fastcall aioIob2Bi2xUDN_GetDeviceStatus(
|
||||
AIO_IOB2_BI2X_UDN *node, AIO_IOB2_BI2X_UDN__DEVSTATUS *status,
|
||||
uint32_t status_size) {
|
||||
|
||||
if (node != aio_udn.get()) {
|
||||
return aioIob2Bi2xUDN_GetDeviceStatus_orig(node, status, status_size);
|
||||
}
|
||||
|
||||
RI_MGR->devices_flush_output();
|
||||
|
||||
AIO_IOB2_BI2X_UDN__DEVSTATUS current {};
|
||||
current.InputCounter = input_counter;
|
||||
current.Input.DevIoCounter = input_counter++;
|
||||
|
||||
auto &buttons = get_buttons();
|
||||
current.Input.bService = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service]) ? 1 : 0;
|
||||
current.Input.bTest = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test]) ? 1 : 0;
|
||||
current.Input.bCoinSw = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::CoinMech]) ? 1 : 0;
|
||||
current.Input.bStart = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Start]) ? 1 : 0;
|
||||
current.Input.bUp = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Up]) ? 1 : 0;
|
||||
current.Input.bDown = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Down]) ? 1 : 0;
|
||||
current.Input.bLeft = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Left]) ? 1 : 0;
|
||||
current.Input.bRight = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Right]) ? 1 : 0;
|
||||
// The hardware counter wraps at 256; clamping it would stop accepting
|
||||
// coins once the shared stock reaches 255.
|
||||
current.Input.CoinCount = static_cast<uint8_t>(eamuse_coin_get_stock());
|
||||
|
||||
const auto copy_size = std::min<uint32_t>(status_size, sizeof(current));
|
||||
if (status && copy_size > 0) {
|
||||
std::memcpy(status, ¤t, copy_size);
|
||||
}
|
||||
return copy_size;
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xUDN_IoReset(AIO_IOB2_BI2X_UDN *node, uint32_t reset_bits) {
|
||||
if (node != aio_udn.get()) {
|
||||
return aioIob2Bi2xUDN_IoReset_orig(node, reset_bits);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xUDN_SetWatchDogTimer(AIO_IOB2_BI2X_UDN *node, uint8_t count) {
|
||||
if (node != aio_udn.get()) {
|
||||
return aioIob2Bi2xUDN_SetWatchDogTimer_orig(node, count);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xUDN_ControlCoinBlocker(
|
||||
AIO_IOB2_BI2X_UDN *node, uint32_t slot, bool open) {
|
||||
|
||||
if (node != aio_udn.get()) {
|
||||
return aioIob2Bi2xUDN_ControlCoinBlocker_orig(node, slot, open);
|
||||
}
|
||||
eamuse_coin_set_block(!open);
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xUDN_AddCounter(
|
||||
AIO_IOB2_BI2X_UDN *node, uint32_t counter, uint32_t count) {
|
||||
|
||||
if (node != aio_udn.get()) {
|
||||
return aioIob2Bi2xUDN_AddCounter_orig(node, counter, count);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xUDN_SetStartLamp(AIO_IOB2_BI2X_UDN *node, bool on) {
|
||||
if (node != aio_udn.get()) {
|
||||
return aioIob2Bi2xUDN_SetStartLamp_orig(node, on);
|
||||
}
|
||||
write_light(Lights::Start, on ? 1.f : 0.f);
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xUDN_SetPlayerButtonLamp(
|
||||
AIO_IOB2_BI2X_UDN *node, uint32_t button, bool on) {
|
||||
|
||||
if (node != aio_udn.get()) {
|
||||
return aioIob2Bi2xUDN_SetPlayerButtonLamp_orig(node, button, on);
|
||||
}
|
||||
|
||||
static const Lights::udn_lights_t mapping[] {
|
||||
Lights::Start,
|
||||
Lights::Up,
|
||||
Lights::Down,
|
||||
Lights::Left,
|
||||
Lights::Right,
|
||||
};
|
||||
if (button < std::size(mapping)) {
|
||||
write_light(mapping[button], on ? 1.f : 0.f);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xUDN_SetIccrLed(AIO_IOB2_BI2X_UDN *node, uint32_t rgb) {
|
||||
if (node != aio_udn.get()) {
|
||||
return aioIob2Bi2xUDN_SetIccrLed_orig(node, rgb);
|
||||
}
|
||||
|
||||
write_light(Lights::CardReaderR, ((rgb >> 16) & 0xff) / 255.f);
|
||||
write_light(Lights::CardReaderG, ((rgb >> 8) & 0xff) / 255.f);
|
||||
write_light(Lights::CardReaderB, (rgb & 0xff) / 255.f);
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xUDN_SetTapeLedDataLimit(
|
||||
AIO_IOB2_BI2X_UDN *node, uint32_t channel, uint8_t scale, uint8_t limit) {
|
||||
|
||||
if (node != aio_udn.get()) {
|
||||
return aioIob2Bi2xUDN_SetTapeLedDataLimit_orig(node, channel, scale, limit);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xUDN_SetTapeLedData(
|
||||
AIO_IOB2_BI2X_UDN *node, uint32_t tape, uint8_t *data) {
|
||||
|
||||
if (node != aio_udn.get()) {
|
||||
return aioIob2Bi2xUDN_SetTapeLedData_orig(node, tape, data);
|
||||
}
|
||||
|
||||
struct TapeMapping {
|
||||
size_t color_count;
|
||||
Lights::udn_lights_t r;
|
||||
Lights::udn_lights_t g;
|
||||
Lights::udn_lights_t b;
|
||||
};
|
||||
static const TapeMapping mapping[] {
|
||||
{62, Lights::TitleR, Lights::TitleG, Lights::TitleB},
|
||||
{152, Lights::PillarRightTopR, Lights::PillarRightTopG, Lights::PillarRightTopB},
|
||||
{165, Lights::PillarRightBottomR, Lights::PillarRightBottomG, Lights::PillarRightBottomB},
|
||||
{9, Lights::StageLeftR, Lights::StageLeftG, Lights::StageLeftB},
|
||||
{17, Lights::CabinetLeftR, Lights::CabinetLeftG, Lights::CabinetLeftB},
|
||||
{152, Lights::PillarLeftTopR, Lights::PillarLeftTopG, Lights::PillarLeftTopB},
|
||||
{165, Lights::PillarLeftBottomR, Lights::PillarLeftBottomG, Lights::PillarLeftBottomB},
|
||||
{9, Lights::StageRightR, Lights::StageRightG, Lights::StageRightB},
|
||||
{17, Lights::CabinetRightR, Lights::CabinetRightG, Lights::CabinetRightB},
|
||||
};
|
||||
|
||||
if (data && tapeledutils::is_enabled() && tape < std::size(mapping)) {
|
||||
const auto &entry = mapping[tape];
|
||||
const auto color = tapeledutils::pick_color_from_led_tape(data, entry.color_count);
|
||||
write_light(entry.r, color.r);
|
||||
write_light(entry.g, color.g);
|
||||
write_light(entry.b, color.b);
|
||||
}
|
||||
}
|
||||
|
||||
static AIO_SCI_COMM *__fastcall aioIob2Bi2x_OpenSciUsbCdc(uint32_t serial_number) {
|
||||
if (!aio_sci_comm) {
|
||||
aio_sci_comm = std::make_unique<AIO_SCI_COMM>();
|
||||
}
|
||||
return aio_sci_comm.get();
|
||||
}
|
||||
|
||||
static AIO_IOB2_BI2X_WRFIRM *__fastcall aioIob2Bi2x_CreateWriteFirmContext(
|
||||
uint32_t serial_number, uint32_t iob_mask) {
|
||||
|
||||
if (!aio_write_firm) {
|
||||
aio_write_firm = std::make_unique<AIO_IOB2_BI2X_WRFIRM>();
|
||||
}
|
||||
return aio_write_firm.get();
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2x_DestroyWriteFirmContext(AIO_IOB2_BI2X_WRFIRM *context) {
|
||||
if (context != aio_write_firm.get()) {
|
||||
return aioIob2Bi2x_DestroyWriteFirmContext_orig(context);
|
||||
}
|
||||
aio_write_firm.reset();
|
||||
}
|
||||
|
||||
static int32_t __fastcall aioIob2Bi2x_WriteFirmGetState(AIO_IOB2_BI2X_WRFIRM *context) {
|
||||
if (context == aio_write_firm.get()) {
|
||||
// The native state machine treats both 7 (NODEV) and 8
|
||||
// (COMPLETED) as a successful terminal state. UDN additionally
|
||||
// interprets state 8 as "firmware was written this boot" and
|
||||
// deliberately powers the cabinet down after loading so the real
|
||||
// I/O board can reboot. There is no physical BI2X board behind
|
||||
// this emulation, so report NODEV: the check is complete without
|
||||
// scheduling that otherwise endless firmware-update reboot.
|
||||
return 7;
|
||||
}
|
||||
return aioIob2Bi2x_WriteFirmGetState_orig(context);
|
||||
}
|
||||
|
||||
static AIO_IOB_ICCA *__fastcall aioIobIcca_Create(
|
||||
void *node_manager, uint32_t device_id, uint8_t init_mode) {
|
||||
|
||||
auto *node = aioIobIcca_Create_orig(node_manager, device_id, init_mode);
|
||||
if (node) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(icca_mutex);
|
||||
aio_icca = node;
|
||||
aio_icca_node_manager =
|
||||
reinterpret_cast<AIO_NMGR_IOB2 *>(node_manager);
|
||||
icca_reading = false;
|
||||
icca_card_ready = false;
|
||||
std::memset(icca_card_uid, 0, sizeof(icca_card_uid));
|
||||
}
|
||||
log_info("udn_bi2x", "ICCA node attached to Spice CardIO bridge");
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
static uint32_t __fastcall aioIobIcca_GetDeviceStatus(
|
||||
AIO_IOB_ICCA *node, AIO_IOB_ICCA__DEVSTATUS *status, uint32_t status_size) {
|
||||
|
||||
std::unique_lock<std::mutex> lock(icca_mutex);
|
||||
if (node != aio_icca) {
|
||||
lock.unlock();
|
||||
return aioIobIcca_GetDeviceStatus_orig(node, status, status_size);
|
||||
}
|
||||
|
||||
AIO_IOB_ICCA__DEVSTATUS current {};
|
||||
current.InputCounter = icca_input_counter++;
|
||||
current.OutputCounter = current.InputCounter;
|
||||
|
||||
// DNI while idle, BUSY while waiting for a card, READY once a UID is
|
||||
// available. The card-reader state machine accepts a card only on READY.
|
||||
current.Input.cuStatus = icca_reading ? 1 : 0;
|
||||
if (icca_reading && !icca_card_ready && eamuse_card_insert_consume(1, 0)) {
|
||||
uint8_t uid[8] {};
|
||||
if (eamuse_get_card(1, 0, uid)) {
|
||||
std::memcpy(icca_card_uid, uid, sizeof(icca_card_uid));
|
||||
icca_card_ready = true;
|
||||
log_info(
|
||||
"udn_bi2x",
|
||||
"ICCA accepted UID {} from the Spice card queue; reporting READY to Unity",
|
||||
bin2hex(icca_card_uid, sizeof(icca_card_uid)));
|
||||
} else {
|
||||
log_warning(
|
||||
"udn_bi2x",
|
||||
"ICCA consumed a card-insert event but Spice returned no UID");
|
||||
}
|
||||
}
|
||||
|
||||
if (icca_card_ready) {
|
||||
current.Input.cuStatus = 2;
|
||||
current.Input.CardId.CardType = is_card_uid_felica(icca_card_uid) ? 2 : 1;
|
||||
current.Input.CardId.cbCardId = sizeof(icca_card_uid);
|
||||
std::memcpy(current.Input.CardId.CardId, icca_card_uid, sizeof(icca_card_uid));
|
||||
}
|
||||
|
||||
// The ICCA keypad scan-code order is identical to eamuse.h. Keeping
|
||||
// these bits populated also preserves the usual Spice numpad PIN flow.
|
||||
const auto keypad = eamuse_get_keypad_state(0);
|
||||
auto *key_states = ¤t.Input.bKey0;
|
||||
for (size_t key = 0; key < EAM_IO_KEYPAD_COUNT; ++key) {
|
||||
key_states[key] = (keypad & (1u << key)) ? 1 : 0;
|
||||
}
|
||||
|
||||
const auto copy_size = std::min<uint32_t>(status_size, sizeof(current));
|
||||
if (status && copy_size > 0) {
|
||||
std::memcpy(status, ¤t, copy_size);
|
||||
}
|
||||
return copy_size;
|
||||
}
|
||||
|
||||
static void __fastcall aioIobIcca_BeginGetCardId(AIO_IOB_ICCA *node) {
|
||||
std::unique_lock<std::mutex> lock(icca_mutex);
|
||||
if (node != aio_icca) {
|
||||
lock.unlock();
|
||||
return aioIobIcca_BeginGetCardId_orig(node);
|
||||
}
|
||||
if (!icca_reading) {
|
||||
log_info("udn_bi2x", "ICCA BeginGetCardId: waiting for the Spice/CardIO queue");
|
||||
}
|
||||
icca_reading = true;
|
||||
icca_card_ready = false;
|
||||
std::memset(icca_card_uid, 0, sizeof(icca_card_uid));
|
||||
}
|
||||
|
||||
static void __fastcall aioIobIcca_EndGetCardId(AIO_IOB_ICCA *node) {
|
||||
std::unique_lock<std::mutex> lock(icca_mutex);
|
||||
if (node != aio_icca) {
|
||||
lock.unlock();
|
||||
return aioIobIcca_EndGetCardId_orig(node);
|
||||
}
|
||||
if (icca_reading || icca_card_ready) {
|
||||
log_info("udn_bi2x", "ICCA EndGetCardId: returning to idle");
|
||||
}
|
||||
icca_reading = false;
|
||||
icca_card_ready = false;
|
||||
std::memset(icca_card_uid, 0, sizeof(icca_card_uid));
|
||||
}
|
||||
|
||||
static AIO_NMGR_IOB2 *__fastcall aioNMgrIob2_Create(AIO_SCI_COMM *sci, uint32_t mode) {
|
||||
if (sci != aio_sci_comm.get()) {
|
||||
return aioNMgrIob2_Create_orig(sci, mode);
|
||||
}
|
||||
if (!aio_node_manager) {
|
||||
aio_node_manager = std::make_unique<AIO_NMGR_IOB2>();
|
||||
}
|
||||
return aio_node_manager.get();
|
||||
}
|
||||
|
||||
static void __fastcall aioNMgrIob_BeginManage(AIO_NMGR_IOB2 *node_manager) {
|
||||
if (node_manager != aio_node_manager.get()) {
|
||||
return aioNMgrIob_BeginManage_orig(node_manager);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioNodeMgr_Destroy(AIO_NMGR_IOB2 *node_manager) {
|
||||
if (node_manager != aio_node_manager.get()) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(icca_mutex);
|
||||
if (node_manager == aio_icca_node_manager) {
|
||||
aio_icca_node_manager = nullptr;
|
||||
}
|
||||
}
|
||||
return aioNodeMgr_Destroy_orig(node_manager);
|
||||
}
|
||||
aio_node_manager.reset();
|
||||
}
|
||||
|
||||
static int32_t __fastcall aioNodeMgr_GetState(AIO_NMGR_IOB2 *node_manager) {
|
||||
bool virtual_manager = node_manager == aio_node_manager.get();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(icca_mutex);
|
||||
virtual_manager |= node_manager == aio_icca_node_manager;
|
||||
}
|
||||
if (virtual_manager) {
|
||||
return 0;
|
||||
}
|
||||
return aioNodeMgr_GetState_orig(node_manager);
|
||||
}
|
||||
|
||||
static bool __fastcall aioNodeMgr_IsReady(AIO_NMGR_IOB2 *node_manager, int32_t state) {
|
||||
bool virtual_manager = node_manager == aio_node_manager.get();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(icca_mutex);
|
||||
virtual_manager |= node_manager == aio_icca_node_manager;
|
||||
}
|
||||
if (virtual_manager) {
|
||||
return true;
|
||||
}
|
||||
return aioNodeMgr_IsReady_orig(node_manager, state);
|
||||
}
|
||||
|
||||
static void __fastcall aioNodeCtl_Destroy(AIO_IOB2_BI2X_UDN *node) {
|
||||
if (node != aio_udn.get()) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(icca_mutex);
|
||||
if (reinterpret_cast<AIO_IOB_ICCA *>(node) == aio_icca) {
|
||||
aio_icca = nullptr;
|
||||
aio_icca_node_manager = nullptr;
|
||||
icca_reading = false;
|
||||
icca_card_ready = false;
|
||||
std::memset(icca_card_uid, 0, sizeof(icca_card_uid));
|
||||
}
|
||||
}
|
||||
return aioNodeCtl_Destroy_orig(node);
|
||||
}
|
||||
aio_udn.reset();
|
||||
}
|
||||
|
||||
static int32_t __fastcall aioNodeCtl_GetState(AIO_IOB2_BI2X_UDN *node) {
|
||||
bool virtual_node = node == aio_udn.get();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(icca_mutex);
|
||||
virtual_node |= reinterpret_cast<AIO_IOB_ICCA *>(node) == aio_icca;
|
||||
}
|
||||
if (virtual_node) {
|
||||
return 0;
|
||||
}
|
||||
return aioNodeCtl_GetState_orig(node);
|
||||
}
|
||||
|
||||
static bool __fastcall aioNodeCtl_IsReady(AIO_IOB2_BI2X_UDN *node, int32_t state) {
|
||||
bool virtual_node = node == aio_udn.get();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(icca_mutex);
|
||||
virtual_node |= reinterpret_cast<AIO_IOB_ICCA *>(node) == aio_icca;
|
||||
}
|
||||
if (virtual_node) {
|
||||
return true;
|
||||
}
|
||||
return aioNodeCtl_IsReady_orig(node, state);
|
||||
}
|
||||
|
||||
void bi2x_hook_init() {
|
||||
log_info("udn_bi2x", "init (BI2X + ICCA/CardIO bridge)");
|
||||
|
||||
// The runtime imports the alias DLL names, not their lib-prefixed originals.
|
||||
const auto aio_iob2_video = "aio-iob2_video.dll";
|
||||
execexe::trampoline_try(aio_iob2_video, "aioIob2Bi2xUDN_Create",
|
||||
aioIob2Bi2xUDN_Create, &aioIob2Bi2xUDN_Create_orig);
|
||||
execexe::trampoline_try(aio_iob2_video, "aioIob2Bi2xUDN_GetDeviceStatus",
|
||||
aioIob2Bi2xUDN_GetDeviceStatus, &aioIob2Bi2xUDN_GetDeviceStatus_orig);
|
||||
execexe::trampoline_try(aio_iob2_video, "aioIob2Bi2xAC1_GetDeviceStatus",
|
||||
aioIob2Bi2xAC1_GetDeviceStatus, &aioIob2Bi2xAC1_GetDeviceStatus_orig);
|
||||
execexe::trampoline_try(aio_iob2_video, "aioIob2Bi2xUDN_IoReset",
|
||||
aioIob2Bi2xUDN_IoReset, &aioIob2Bi2xUDN_IoReset_orig);
|
||||
execexe::trampoline_try(aio_iob2_video, "aioIob2Bi2xUDN_SetWatchDogTimer",
|
||||
aioIob2Bi2xUDN_SetWatchDogTimer, &aioIob2Bi2xUDN_SetWatchDogTimer_orig);
|
||||
execexe::trampoline_try(aio_iob2_video, "aioIob2Bi2xUDN_ControlCoinBlocker",
|
||||
aioIob2Bi2xUDN_ControlCoinBlocker, &aioIob2Bi2xUDN_ControlCoinBlocker_orig);
|
||||
execexe::trampoline_try(aio_iob2_video, "aioIob2Bi2xUDN_AddCounter",
|
||||
aioIob2Bi2xUDN_AddCounter, &aioIob2Bi2xUDN_AddCounter_orig);
|
||||
execexe::trampoline_try(aio_iob2_video, "aioIob2Bi2xUDN_SetStartLamp",
|
||||
aioIob2Bi2xUDN_SetStartLamp, &aioIob2Bi2xUDN_SetStartLamp_orig);
|
||||
execexe::trampoline_try(aio_iob2_video, "aioIob2Bi2xUDN_SetPlayerButtonLamp",
|
||||
aioIob2Bi2xUDN_SetPlayerButtonLamp, &aioIob2Bi2xUDN_SetPlayerButtonLamp_orig);
|
||||
execexe::trampoline_try(aio_iob2_video, "aioIob2Bi2xUDN_SetIccrLed",
|
||||
aioIob2Bi2xUDN_SetIccrLed, &aioIob2Bi2xUDN_SetIccrLed_orig);
|
||||
execexe::trampoline_try(aio_iob2_video, "aioIob2Bi2xUDN_SetTapeLedDataLimit",
|
||||
aioIob2Bi2xUDN_SetTapeLedDataLimit, &aioIob2Bi2xUDN_SetTapeLedDataLimit_orig);
|
||||
execexe::trampoline_try(aio_iob2_video, "aioIob2Bi2xUDN_SetTapeLedData",
|
||||
aioIob2Bi2xUDN_SetTapeLedData, &aioIob2Bi2xUDN_SetTapeLedData_orig);
|
||||
execexe::trampoline_try(aio_iob2_video, "aioIob2Bi2x_OpenSciUsbCdc",
|
||||
aioIob2Bi2x_OpenSciUsbCdc, &aioIob2Bi2x_OpenSciUsbCdc_orig);
|
||||
execexe::trampoline_try(aio_iob2_video, "aioIob2Bi2x_CreateWriteFirmContext",
|
||||
aioIob2Bi2x_CreateWriteFirmContext, &aioIob2Bi2x_CreateWriteFirmContext_orig);
|
||||
execexe::trampoline_try(aio_iob2_video, "aioIob2Bi2x_DestroyWriteFirmContext",
|
||||
aioIob2Bi2x_DestroyWriteFirmContext, &aioIob2Bi2x_DestroyWriteFirmContext_orig);
|
||||
execexe::trampoline_try(aio_iob2_video, "aioIob2Bi2x_WriteFirmGetState",
|
||||
aioIob2Bi2x_WriteFirmGetState, &aioIob2Bi2x_WriteFirmGetState_orig);
|
||||
|
||||
const auto aio_iob_video = "aio-iob_video.dll";
|
||||
execexe::trampoline_try(aio_iob_video, "aioIobIcca_Create",
|
||||
aioIobIcca_Create, &aioIobIcca_Create_orig);
|
||||
execexe::trampoline_try(aio_iob_video, "aioIobIcca_GetDeviceStatus",
|
||||
aioIobIcca_GetDeviceStatus, &aioIobIcca_GetDeviceStatus_orig);
|
||||
execexe::trampoline_try(aio_iob_video, "aioIobIcca_BeginGetCardId",
|
||||
aioIobIcca_BeginGetCardId, &aioIobIcca_BeginGetCardId_orig);
|
||||
execexe::trampoline_try(aio_iob_video, "aioIobIcca_EndGetCardId",
|
||||
aioIobIcca_EndGetCardId, &aioIobIcca_EndGetCardId_orig);
|
||||
|
||||
const auto aio_iob = "aio-iob.dll";
|
||||
execexe::trampoline_try(aio_iob, "aioNMgrIob2_Create",
|
||||
aioNMgrIob2_Create, &aioNMgrIob2_Create_orig);
|
||||
execexe::trampoline_try(aio_iob, "aioNMgrIob_BeginManage",
|
||||
aioNMgrIob_BeginManage, &aioNMgrIob_BeginManage_orig);
|
||||
|
||||
const auto aio = "aio.dll";
|
||||
execexe::trampoline_try(aio, "aioNodeMgr_Destroy",
|
||||
aioNodeMgr_Destroy, &aioNodeMgr_Destroy_orig);
|
||||
execexe::trampoline_try(aio, "aioNodeMgr_GetState",
|
||||
aioNodeMgr_GetState, &aioNodeMgr_GetState_orig);
|
||||
execexe::trampoline_try(aio, "aioNodeMgr_IsReady",
|
||||
aioNodeMgr_IsReady, &aioNodeMgr_IsReady_orig);
|
||||
execexe::trampoline_try(aio, "aioNodeCtl_Destroy",
|
||||
aioNodeCtl_Destroy, &aioNodeCtl_Destroy_orig);
|
||||
execexe::trampoline_try(aio, "aioNodeCtl_GetState",
|
||||
aioNodeCtl_GetState, &aioNodeCtl_GetState_orig);
|
||||
execexe::trampoline_try(aio, "aioNodeCtl_IsReady",
|
||||
aioNodeCtl_IsReady, &aioNodeCtl_IsReady_orig);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
namespace games::udn {
|
||||
void bi2x_hook_init();
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
#include "io.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
std::vector<Button> &games::udn::get_buttons() {
|
||||
static std::vector<Button> buttons;
|
||||
|
||||
if (buttons.empty()) {
|
||||
buttons = GameAPI::Buttons::getButtons("DANCE aROUND");
|
||||
|
||||
const std::vector<std::string> names {
|
||||
"Service",
|
||||
"Test",
|
||||
"Coin Mech",
|
||||
"Start",
|
||||
"Up",
|
||||
"Down",
|
||||
"Left",
|
||||
"Right",
|
||||
};
|
||||
const std::vector<unsigned short> vkey_defaults {
|
||||
0xFF,
|
||||
0xFF,
|
||||
0xFF,
|
||||
VK_RETURN,
|
||||
VK_UP,
|
||||
VK_DOWN,
|
||||
VK_LEFT,
|
||||
VK_RIGHT,
|
||||
};
|
||||
|
||||
buttons = GameAPI::Buttons::sortButtons(buttons, names, &vkey_defaults);
|
||||
}
|
||||
|
||||
return buttons;
|
||||
}
|
||||
|
||||
std::string games::udn::get_buttons_help() {
|
||||
return
|
||||
" Arrow keys: menu directions\n"
|
||||
" Enter: start/confirm\n"
|
||||
" MIDI input is disabled for this game.\n";
|
||||
}
|
||||
|
||||
std::vector<Light> &games::udn::get_lights() {
|
||||
static std::vector<Light> lights;
|
||||
|
||||
if (lights.empty()) {
|
||||
lights = GameAPI::Lights::getLights("DANCE aROUND");
|
||||
GameAPI::Lights::sortLights(
|
||||
&lights,
|
||||
"Card Reader R",
|
||||
"Card Reader G",
|
||||
"Card Reader B",
|
||||
"Start",
|
||||
"Up",
|
||||
"Down",
|
||||
"Left",
|
||||
"Right",
|
||||
"Title R",
|
||||
"Title G",
|
||||
"Title B",
|
||||
"Pillar Right Top R",
|
||||
"Pillar Right Top G",
|
||||
"Pillar Right Top B",
|
||||
"Pillar Right Bottom R",
|
||||
"Pillar Right Bottom G",
|
||||
"Pillar Right Bottom B",
|
||||
"Stage Left R",
|
||||
"Stage Left G",
|
||||
"Stage Left B",
|
||||
"Cabinet Left R",
|
||||
"Cabinet Left G",
|
||||
"Cabinet Left B",
|
||||
"Pillar Left Top R",
|
||||
"Pillar Left Top G",
|
||||
"Pillar Left Top B",
|
||||
"Pillar Left Bottom R",
|
||||
"Pillar Left Bottom G",
|
||||
"Pillar Left Bottom B",
|
||||
"Stage Right R",
|
||||
"Stage Right G",
|
||||
"Stage Right B",
|
||||
"Cabinet Right R",
|
||||
"Cabinet Right G",
|
||||
"Cabinet Right B"
|
||||
);
|
||||
}
|
||||
|
||||
return lights;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "cfg/api.h"
|
||||
|
||||
namespace games::udn {
|
||||
namespace Buttons {
|
||||
enum {
|
||||
Service,
|
||||
Test,
|
||||
CoinMech,
|
||||
Start,
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
};
|
||||
}
|
||||
|
||||
namespace Lights {
|
||||
enum udn_lights_t {
|
||||
CardReaderR,
|
||||
CardReaderG,
|
||||
CardReaderB,
|
||||
Start,
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
TitleR,
|
||||
TitleG,
|
||||
TitleB,
|
||||
PillarRightTopR,
|
||||
PillarRightTopG,
|
||||
PillarRightTopB,
|
||||
PillarRightBottomR,
|
||||
PillarRightBottomG,
|
||||
PillarRightBottomB,
|
||||
StageLeftR,
|
||||
StageLeftG,
|
||||
StageLeftB,
|
||||
CabinetLeftR,
|
||||
CabinetLeftG,
|
||||
CabinetLeftB,
|
||||
PillarLeftTopR,
|
||||
PillarLeftTopG,
|
||||
PillarLeftTopB,
|
||||
PillarLeftBottomR,
|
||||
PillarLeftBottomG,
|
||||
PillarLeftBottomB,
|
||||
StageRightR,
|
||||
StageRightG,
|
||||
StageRightB,
|
||||
CabinetRightR,
|
||||
CabinetRightG,
|
||||
CabinetRightB,
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<Button> &get_buttons();
|
||||
std::string get_buttons_help();
|
||||
std::vector<Light> &get_lights();
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
// QueryDisplayConfig
|
||||
#define _WIN32_WINNT 0x0601
|
||||
|
||||
#include "udn.h"
|
||||
|
||||
#include <format>
|
||||
|
||||
#include "acioemu/handle.h"
|
||||
#include "bi2x_hook.h"
|
||||
#include "cfg/screen_resize.h"
|
||||
#include "hooks/devicehook.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/execexe.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/unity_player.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
namespace games::udn {
|
||||
std::string UDN_INJECT_ARGS = "";
|
||||
bool UDN_NO_IO = false;
|
||||
|
||||
static acioemu::ACIOHandle *acio_handle = nullptr;
|
||||
static const std::wstring port_name = L"COM1";
|
||||
|
||||
static decltype(RegisterRawInputDevices) *RegisterRawInputDevices_orig = nullptr;
|
||||
static decltype(EnumDisplaySettingsW) *EnumDisplaySettingsW_orig = nullptr;
|
||||
static decltype(QueryDisplayConfig) *QueryDisplayConfig_orig = nullptr;
|
||||
|
||||
// Unity reapplies its cabinet window settings after startup. Use the
|
||||
// existing graphics message callback so changes stay on the window thread.
|
||||
static LRESULT CALLBACK window_proc(HWND window, UINT message, WPARAM w_param, LPARAM l_param) {
|
||||
if (!GRAPHICS_WINDOWED || GRAPHICS_HOOKED_WINDOW != window) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool interactive_resize = false;
|
||||
switch (message) {
|
||||
case WM_ENTERSIZEMOVE:
|
||||
interactive_resize = true;
|
||||
break;
|
||||
case WM_EXITSIZEMOVE:
|
||||
interactive_resize = false;
|
||||
break;
|
||||
case WM_STYLECHANGING:
|
||||
if (w_param == static_cast<WPARAM>(GWL_STYLE)) {
|
||||
auto styles = reinterpret_cast<STYLESTRUCT *>(l_param);
|
||||
styles->styleNew &= ~WS_POPUP;
|
||||
if (cfg::SCREENRESIZE->window_decoration == cfg::WindowDecorationMode::Borderless) {
|
||||
styles->styleNew &= ~WS_OVERLAPPEDWINDOW;
|
||||
} else {
|
||||
styles->styleNew |= WS_OVERLAPPEDWINDOW;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case WM_WINDOWPOSCHANGING:
|
||||
if (!interactive_resize && cfg::SCREENRESIZE->enable_window_resize) {
|
||||
auto position = reinterpret_cast<WINDOWPOS *>(l_param);
|
||||
// The shared WM_MOVE/WM_SIZE handlers keep these values in
|
||||
// sync with interactive changes and the overlay settings.
|
||||
if (!(position->flags & SWP_NOMOVE)) {
|
||||
position->x = cfg::SCREENRESIZE->window_offset_x;
|
||||
position->y = cfg::SCREENRESIZE->window_offset_y;
|
||||
}
|
||||
if (!(position->flags & SWP_NOSIZE)) {
|
||||
position->cx = cfg::SCREENRESIZE->client_width +
|
||||
cfg::SCREENRESIZE->window_deco_width;
|
||||
position->cy = cfg::SCREENRESIZE->client_height +
|
||||
cfg::SCREENRESIZE->window_deco_height;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static BOOL WINAPI RegisterRawInputDevices_hook(
|
||||
PCRAWINPUTDEVICE devices, UINT device_count, UINT structure_size) {
|
||||
|
||||
if (devices && device_count > 0 && devices[0].hwndTarget == RI_MGR->input_hwnd) {
|
||||
return RegisterRawInputDevices_orig(devices, device_count, structure_size);
|
||||
}
|
||||
|
||||
SetLastError(0xDEADBEEF);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static BOOL WINAPI EnumDisplaySettingsW_hook(
|
||||
LPCWSTR device_name, DWORD mode_number, DEVMODEW *mode) {
|
||||
|
||||
const auto result = EnumDisplaySettingsW_orig(device_name, mode_number, mode);
|
||||
if (result && mode_number == ENUM_CURRENT_SETTINGS) {
|
||||
mode->dmPelsWidth = 1920;
|
||||
mode->dmPelsHeight = 1080;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static LONG WINAPI QueryDisplayConfig_hook(
|
||||
UINT32, UINT32 *, DISPLAYCONFIG_PATH_INFO *, UINT32 *, DISPLAYCONFIG_MODE_INFO *,
|
||||
DISPLAYCONFIG_TOPOLOGY_ID *) {
|
||||
return ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
void UDNGame::pre_attach() {
|
||||
// The title has no MIDI controls. The legacy WinMM provider can fault in
|
||||
// MMDevAPI while Unity and its audio backend initialize concurrently, so
|
||||
// disable MIDI enumeration for this game.
|
||||
rawinput::DISABLE_MIDI = true;
|
||||
log_info("udn", "MIDI input disabled to avoid WinMM initialization errors");
|
||||
}
|
||||
|
||||
void UDNGame::attach() {
|
||||
Game::attach();
|
||||
|
||||
acio_handle = new acioemu::ACIOHandle(port_name.c_str(), 1);
|
||||
devicehook_init_trampoline();
|
||||
devicehook_add(acio_handle);
|
||||
|
||||
execexe::init();
|
||||
execexe::init_deferred([]() {
|
||||
execexe::init_port_hook(port_name, acio_handle);
|
||||
|
||||
// Runtime import names omit the "lib" prefix.
|
||||
execexe::load_library("aio.dll");
|
||||
execexe::load_library("aio-iob.dll");
|
||||
execexe::load_library("aio-iob_video.dll");
|
||||
execexe::load_library("aio-iob2_video.dll");
|
||||
execexe::load_library("win10actlog.dll", false);
|
||||
|
||||
#if SPICE64
|
||||
if (!UDN_NO_IO) {
|
||||
bi2x_hook_init();
|
||||
}
|
||||
#endif
|
||||
});
|
||||
|
||||
const auto user32_dll = "user32.dll";
|
||||
detour::trampoline_try(user32_dll, "RegisterRawInputDevices",
|
||||
RegisterRawInputDevices_hook, &RegisterRawInputDevices_orig);
|
||||
if (GRAPHICS_WINDOWED) {
|
||||
// Keep Unity's cabinet-sized UI in windowed mode. Fullscreen uses
|
||||
// the real display topology and resolution.
|
||||
detour::trampoline_try(user32_dll, "QueryDisplayConfig",
|
||||
QueryDisplayConfig_hook, &QueryDisplayConfig_orig);
|
||||
detour::trampoline_try(user32_dll, "EnumDisplaySettingsW",
|
||||
EnumDisplaySettingsW_hook, &EnumDisplaySettingsW_orig);
|
||||
graphics_add_wnd_proc(window_proc);
|
||||
}
|
||||
|
||||
if (GRAPHICS_SHOW_CURSOR) {
|
||||
unity_utils::force_show_cursor(true);
|
||||
}
|
||||
|
||||
unity_utils::set_args(
|
||||
std::format("{} {}{}",
|
||||
GetCommandLineA(),
|
||||
UDN_INJECT_ARGS,
|
||||
unity_utils::get_unity_player_args()));
|
||||
}
|
||||
|
||||
void UDNGame::detach() {
|
||||
Game::detach();
|
||||
graphics_remove_wnd_proc(window_proc);
|
||||
devicehook_dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include "games/game.h"
|
||||
|
||||
namespace games::udn {
|
||||
extern std::string UDN_INJECT_ARGS;
|
||||
extern bool UDN_NO_IO;
|
||||
|
||||
class UDNGame : public games::Game {
|
||||
public:
|
||||
UDNGame() : Game("DANCE aROUND") {}
|
||||
|
||||
virtual void pre_attach() override;
|
||||
virtual void attach() override;
|
||||
virtual void detach() override;
|
||||
};
|
||||
}
|
||||
@@ -26,6 +26,7 @@ namespace d3d11_hooks {
|
||||
// ignore_hwnd.
|
||||
void note_main_hwnd(HWND hwnd);
|
||||
HWND main_hwnd();
|
||||
bool is_main_game_swapchain(IDXGISwapChain *swapchain);
|
||||
void ignore_hwnd(HWND hwnd);
|
||||
|
||||
// capture backbuffer to PNG if a screenshot was requested.
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <dxgi1_2.h>
|
||||
|
||||
#include "d3d11_internal.h"
|
||||
#include "d3d11_swapchain_udn.h"
|
||||
|
||||
#include "external/imgui/imgui.h"
|
||||
#include "external/imgui/backends/imgui_impl_dx11.h"
|
||||
@@ -115,34 +116,6 @@ bool looks_like_game_window(HWND hwnd) {
|
||||
&& client.bottom > client.top;
|
||||
}
|
||||
|
||||
// only the main game window; ignore sub-screens / IME helpers.
|
||||
bool is_main_game_swapchain(IDXGISwapChain *swapchain) {
|
||||
DXGI_SWAP_CHAIN_DESC desc {};
|
||||
if (!swapchain || FAILED(swapchain->GetDesc(&desc)) || !desc.OutputWindow) {
|
||||
return false;
|
||||
}
|
||||
|
||||
HWND main = d3d11_hooks::main_hwnd();
|
||||
if (!main) {
|
||||
// no creation hook recorded a window, so fall back to the presenting one;
|
||||
// the choice is permanent, so require a plausible game window
|
||||
if (!looks_like_game_window(desc.OutputWindow)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
log_misc(
|
||||
"graphics::d3d11",
|
||||
"try to notemain hwnd from swapchain present: 0x{:x}",
|
||||
(uintptr_t)desc.OutputWindow);
|
||||
|
||||
d3d11_hooks::note_main_hwnd(desc.OutputWindow);
|
||||
|
||||
// it may have been ignored, or another thread may have won the slot
|
||||
main = d3d11_hooks::main_hwnd();
|
||||
}
|
||||
return desc.OutputWindow == main;
|
||||
}
|
||||
|
||||
// checks are ordered cheapest first, since this runs on every present
|
||||
void try_create_overlay(IDXGISwapChain *swapchain) {
|
||||
if (!swapchain) {
|
||||
@@ -160,7 +133,7 @@ void try_create_overlay(IDXGISwapChain *swapchain) {
|
||||
}
|
||||
|
||||
// ignore sub windows
|
||||
if (!is_main_game_swapchain(swapchain)) {
|
||||
if (!d3d11_hooks::is_main_game_swapchain(swapchain)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -198,7 +171,7 @@ void try_create_overlay(IDXGISwapChain *swapchain) {
|
||||
void pump_frame(IDXGISwapChain *swapchain) {
|
||||
const bool has_overlay =
|
||||
overlay::OVERLAY && overlay::OVERLAY->uses_swapchain(swapchain);
|
||||
if (!has_overlay && !is_main_game_swapchain(swapchain)) {
|
||||
if (!has_overlay && !d3d11_hooks::is_main_game_swapchain(swapchain)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -240,8 +213,10 @@ HRESULT STDMETHODCALLTYPE Present_hook(
|
||||
{
|
||||
// a test present doesn't display anything; don't pick a window or take a screenshot off it
|
||||
if (!(Flags & DXGI_PRESENT_TEST)) {
|
||||
d3d11_hooks::udn::schedule_window_attach(swapchain);
|
||||
try_create_overlay(swapchain);
|
||||
pump_frame(swapchain);
|
||||
d3d11_hooks::udn::apply_present_policy(swapchain, SyncInterval);
|
||||
}
|
||||
return Present_orig(swapchain, SyncInterval, Flags);
|
||||
}
|
||||
@@ -251,8 +226,10 @@ HRESULT STDMETHODCALLTYPE Present1_hook(
|
||||
const DXGI_PRESENT_PARAMETERS *pParams)
|
||||
{
|
||||
if (!(Flags & DXGI_PRESENT_TEST)) {
|
||||
d3d11_hooks::udn::schedule_window_attach(swapchain);
|
||||
try_create_overlay(swapchain);
|
||||
pump_frame(swapchain);
|
||||
d3d11_hooks::udn::apply_present_policy(swapchain, SyncInterval);
|
||||
}
|
||||
return Present1_orig(swapchain, SyncInterval, Flags, pParams);
|
||||
}
|
||||
@@ -287,6 +264,33 @@ namespace {
|
||||
std::atomic<HWND> g_ignored_hwnd { nullptr };
|
||||
}
|
||||
|
||||
bool is_main_game_swapchain(IDXGISwapChain *swapchain) {
|
||||
DXGI_SWAP_CHAIN_DESC desc {};
|
||||
if (!swapchain || FAILED(swapchain->GetDesc(&desc)) || !desc.OutputWindow) {
|
||||
return false;
|
||||
}
|
||||
|
||||
HWND main = d3d11_hooks::main_hwnd();
|
||||
if (!main) {
|
||||
// no creation hook recorded a window, so fall back to the presenting one;
|
||||
// the choice is permanent, so require a plausible game window
|
||||
if (!looks_like_game_window(desc.OutputWindow)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
log_misc(
|
||||
"graphics::d3d11",
|
||||
"try to notemain hwnd from swapchain present: 0x{:x}",
|
||||
(uintptr_t)desc.OutputWindow);
|
||||
|
||||
d3d11_hooks::note_main_hwnd(desc.OutputWindow);
|
||||
|
||||
// it may have been ignored, or another thread may have won the slot
|
||||
main = d3d11_hooks::main_hwnd();
|
||||
}
|
||||
return desc.OutputWindow == main;
|
||||
}
|
||||
|
||||
void note_main_hwnd(HWND hwnd) {
|
||||
if (!hwnd || hwnd == g_ignored_hwnd.load()) {
|
||||
return;
|
||||
@@ -326,6 +330,8 @@ void install_swapchain_hooks(IDXGISwapChain *swapchain) {
|
||||
}
|
||||
}
|
||||
|
||||
udn::install_swapchain_hooks(swapchain);
|
||||
|
||||
if (!g_swapchain1_hooked) {
|
||||
IDXGISwapChain1 *sc1 = nullptr;
|
||||
if (SUCCEEDED(swapchain->QueryInterface(IID_PPV_ARGS(&sc1))) && sc1) {
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
// DANCE aROUND window attachment, frame pacing and display-mode overrides.
|
||||
#include "d3d11_backend.h"
|
||||
|
||||
#ifdef SPICE_D3D11
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
|
||||
#include <windows.h>
|
||||
#include <d3d11.h>
|
||||
#include <dxgi.h>
|
||||
|
||||
#include "d3d11_internal.h"
|
||||
#include "d3d11_swapchain_udn.h"
|
||||
#include "avs/game.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "util/precise_timer.h"
|
||||
|
||||
namespace d3d11_hooks::udn {
|
||||
namespace {
|
||||
|
||||
using SetFullscreenState_t = HRESULT(STDMETHODCALLTYPE *)(
|
||||
IDXGISwapChain *, BOOL, IDXGIOutput *);
|
||||
using ResizeTarget_t = HRESULT(STDMETHODCALLTYPE *)(
|
||||
IDXGISwapChain *, const DXGI_MODE_DESC *);
|
||||
|
||||
SetFullscreenState_t SetFullscreenState_orig = nullptr;
|
||||
ResizeTarget_t ResizeTarget_orig = nullptr;
|
||||
|
||||
bool g_set_fullscreen_hooked = false;
|
||||
bool g_resize_target_hooked = false;
|
||||
// Present and Present1 share one waitable timer and frame deadline.
|
||||
std::mutex g_forced_present_pacing_mutex;
|
||||
std::chrono::steady_clock::time_point g_next_forced_present {};
|
||||
|
||||
// Unity creates and owns its top-level window on a different thread from the
|
||||
// D3D11 Present thread. Subclassing or resizing that HWND from Present can
|
||||
// synchronously wait for the owner thread and deadlock Unity. A one-time
|
||||
// message-queue hook performs graphics_hook_window on the HWND's owner thread.
|
||||
std::mutex g_window_attach_mutex;
|
||||
HHOOK g_window_attach_hook = nullptr;
|
||||
HWND g_window_attach_target = nullptr;
|
||||
bool g_window_attach_complete = false;
|
||||
|
||||
UINT window_attach_message() {
|
||||
static const UINT message = RegisterWindowMessageW(
|
||||
L"SpiceTools.D3D11.AttachGameWindow");
|
||||
return message;
|
||||
}
|
||||
|
||||
void pace_forced_present(UINT frame_rate) {
|
||||
static timeutils::PreciseSleepTimer timer;
|
||||
const auto period = std::chrono::nanoseconds(
|
||||
1'000'000'000ULL / static_cast<uint64_t>(frame_rate));
|
||||
std::lock_guard<std::mutex> lock(g_forced_present_pacing_mutex);
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (g_next_forced_present.time_since_epoch().count() == 0 ||
|
||||
now > g_next_forced_present + period) {
|
||||
g_next_forced_present = now + period;
|
||||
return;
|
||||
}
|
||||
if (now < g_next_forced_present) {
|
||||
timer.sleep(g_next_forced_present - now);
|
||||
}
|
||||
g_next_forced_present += period;
|
||||
}
|
||||
|
||||
LRESULT CALLBACK window_attach_getmessage(
|
||||
int code, WPARAM w_param, LPARAM l_param) {
|
||||
HWND target = nullptr;
|
||||
HHOOK hook = nullptr;
|
||||
if (code >= 0 && w_param == PM_REMOVE) {
|
||||
const auto message = reinterpret_cast<const MSG *>(l_param);
|
||||
std::lock_guard<std::mutex> lock(g_window_attach_mutex);
|
||||
if (!g_window_attach_complete && message->hwnd == g_window_attach_target &&
|
||||
message->message == window_attach_message()) {
|
||||
target = g_window_attach_target;
|
||||
hook = g_window_attach_hook;
|
||||
g_window_attach_hook = nullptr;
|
||||
g_window_attach_complete = true;
|
||||
}
|
||||
}
|
||||
|
||||
const LRESULT result = CallNextHookEx(nullptr, code, w_param, l_param);
|
||||
if (target) {
|
||||
UnhookWindowsHookEx(hook);
|
||||
graphics_hook_window(target, nullptr);
|
||||
log_info(
|
||||
"graphics::d3d11",
|
||||
"attached Spice window hooks on owner thread for hwnd=0x{:x}",
|
||||
(uintptr_t) target);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE SetFullscreenState_hook(
|
||||
IDXGISwapChain *swapchain, BOOL Fullscreen, IDXGIOutput *Target) {
|
||||
if (Fullscreen && GRAPHICS_WINDOWED && avs::game::is_model("UDN") &&
|
||||
is_main_game_swapchain(swapchain)) {
|
||||
log_info(
|
||||
"graphics::d3d11",
|
||||
"blocked UDN request to enter DXGI exclusive fullscreen");
|
||||
return SetFullscreenState_orig(swapchain, FALSE, nullptr);
|
||||
}
|
||||
return SetFullscreenState_orig(swapchain, Fullscreen, Target);
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE ResizeTarget_hook(
|
||||
IDXGISwapChain *swapchain, const DXGI_MODE_DESC *NewTargetParameters) {
|
||||
if (GRAPHICS_WINDOWED && avs::game::is_model("UDN") &&
|
||||
is_main_game_swapchain(swapchain)) {
|
||||
if (NewTargetParameters) {
|
||||
log_info(
|
||||
"graphics::d3d11",
|
||||
"blocked UDN output-mode resize to {}x{}",
|
||||
NewTargetParameters->Width,
|
||||
NewTargetParameters->Height);
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
return ResizeTarget_orig(swapchain, NewTargetParameters);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void schedule_window_attach(IDXGISwapChain *swapchain) {
|
||||
if (!GRAPHICS_WINDOWED || !avs::game::is_model("UDN") ||
|
||||
!is_main_game_swapchain(swapchain)) {
|
||||
return;
|
||||
}
|
||||
|
||||
DXGI_SWAP_CHAIN_DESC desc {};
|
||||
if (FAILED(swapchain->GetDesc(&desc)) || !desc.OutputWindow) {
|
||||
return;
|
||||
}
|
||||
const HWND hwnd = desc.OutputWindow;
|
||||
|
||||
std::lock_guard<std::mutex> lock(g_window_attach_mutex);
|
||||
if (g_window_attach_complete || g_window_attach_hook != nullptr) {
|
||||
return;
|
||||
}
|
||||
if (GRAPHICS_HOOKED_WINDOW.has_value()) {
|
||||
g_window_attach_complete = true;
|
||||
return;
|
||||
}
|
||||
|
||||
DWORD process_id = 0;
|
||||
const DWORD thread_id = GetWindowThreadProcessId(hwnd, &process_id);
|
||||
if (!thread_id || process_id != GetCurrentProcessId()) {
|
||||
log_warning(
|
||||
"graphics::d3d11",
|
||||
"could not identify owner thread for hwnd=0x{:x}",
|
||||
(uintptr_t) hwnd);
|
||||
return;
|
||||
}
|
||||
|
||||
const HHOOK hook = SetWindowsHookExW(
|
||||
WH_GETMESSAGE, window_attach_getmessage, nullptr, thread_id);
|
||||
if (!hook) {
|
||||
log_warning(
|
||||
"graphics::d3d11",
|
||||
"could not schedule owner-thread window attach for hwnd=0x{:x}: {}",
|
||||
(uintptr_t) hwnd,
|
||||
GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
g_window_attach_target = hwnd;
|
||||
g_window_attach_hook = hook;
|
||||
if (!PostMessageW(hwnd, window_attach_message(), 0, 0)) {
|
||||
const DWORD error = GetLastError();
|
||||
g_window_attach_hook = nullptr;
|
||||
g_window_attach_target = nullptr;
|
||||
UnhookWindowsHookEx(hook);
|
||||
log_warning(
|
||||
"graphics::d3d11",
|
||||
"could not post owner-thread window attach for hwnd=0x{:x}: {}",
|
||||
(uintptr_t) hwnd,
|
||||
error);
|
||||
return;
|
||||
}
|
||||
|
||||
log_info(
|
||||
"graphics::d3d11",
|
||||
"scheduled Spice window attach on owner thread {} for hwnd=0x{:x}",
|
||||
thread_id,
|
||||
(uintptr_t) hwnd);
|
||||
}
|
||||
|
||||
void apply_present_policy(IDXGISwapChain *swapchain, UINT &sync_interval) {
|
||||
if (!GRAPHICS_WINDOWED || !avs::game::is_model("UDN") ||
|
||||
!is_main_game_swapchain(swapchain)) {
|
||||
return;
|
||||
}
|
||||
// Interval two gives 60 FPS on the cabinet's 120 Hz display, but only
|
||||
// 37.5 FPS on a 75 Hz desktop. Pace windowed mode with an explicit clock.
|
||||
// Fullscreen keeps the game's own presentation interval and flags.
|
||||
pace_forced_present(60);
|
||||
sync_interval = 0;
|
||||
}
|
||||
|
||||
void install_swapchain_hooks(IDXGISwapChain *swapchain) {
|
||||
if (!GRAPHICS_WINDOWED || !avs::game::is_model("UDN")) {
|
||||
return;
|
||||
}
|
||||
// The shared installer holds its mutex while calling this function.
|
||||
if (!g_set_fullscreen_hooked) {
|
||||
g_set_fullscreen_hooked = hook_vtbl(
|
||||
swapchain, 10, (void *) SetFullscreenState_hook,
|
||||
(void **) &SetFullscreenState_orig,
|
||||
"IDXGISwapChain::SetFullscreenState");
|
||||
}
|
||||
if (!g_resize_target_hooked) {
|
||||
g_resize_target_hooked = hook_vtbl(
|
||||
swapchain, 14, (void *) ResizeTarget_hook,
|
||||
(void **) &ResizeTarget_orig, "IDXGISwapChain::ResizeTarget");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace d3d11_hooks::udn
|
||||
|
||||
#endif // SPICE_D3D11
|
||||
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef SPICE_D3D11
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
struct IDXGISwapChain;
|
||||
|
||||
namespace d3d11_hooks::udn {
|
||||
|
||||
void schedule_window_attach(IDXGISwapChain *swapchain);
|
||||
void apply_present_policy(IDXGISwapChain *swapchain, UINT &sync_interval);
|
||||
void install_swapchain_hooks(IDXGISwapChain *swapchain);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,11 +1,16 @@
|
||||
#include "libraryhook.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
|
||||
#include "external/robin_hood.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
static bool LHOOK_ENABLED = false;
|
||||
// SDK plugins can add aliases while other threads load DLLs.
|
||||
static std::shared_mutex LIBRARIES_MUTEX;
|
||||
static robin_hood::unordered_map<std::string, HMODULE> LIBRARIES_A;
|
||||
static robin_hood::unordered_map<std::wstring, HMODULE> LIBRARIES_W;
|
||||
static robin_hood::unordered_map<std::string, FARPROC> PROCS;
|
||||
@@ -21,6 +26,7 @@ static HMODULE WINAPI LoadLibraryA_hook(LPCTSTR lpFileName) {
|
||||
|
||||
// check hooks
|
||||
if (lpFileName) {
|
||||
std::shared_lock lock(LIBRARIES_MUTEX);
|
||||
auto module = LIBRARIES_A.find(lpFileName);
|
||||
if (module != LIBRARIES_A.end()) {
|
||||
return module->second;
|
||||
@@ -35,6 +41,7 @@ static HMODULE WINAPI LoadLibraryW_hook(LPCWSTR lpFileName) {
|
||||
|
||||
// check hooks
|
||||
if (lpFileName) {
|
||||
std::shared_lock lock(LIBRARIES_MUTEX);
|
||||
auto module = LIBRARIES_W.find(lpFileName);
|
||||
if (module != LIBRARIES_W.end()) {
|
||||
return module->second;
|
||||
@@ -49,6 +56,7 @@ static HMODULE WINAPI LoadLibraryExW_hook(LPCWSTR lpFileName, HANDLE hFile, DWOR
|
||||
|
||||
// check hooks
|
||||
if (lpFileName) {
|
||||
std::shared_lock lock(LIBRARIES_MUTEX);
|
||||
auto module = LIBRARIES_W.find(lpFileName);
|
||||
if (module != LIBRARIES_W.end()) {
|
||||
return module->second;
|
||||
@@ -63,6 +71,7 @@ static HMODULE WINAPI GetModuleHandleA_hook(LPCSTR lpModuleName) {
|
||||
|
||||
// check hooks
|
||||
if (lpModuleName) {
|
||||
std::shared_lock lock(LIBRARIES_MUTEX);
|
||||
auto module = LIBRARIES_A.find(lpModuleName);
|
||||
if (module != LIBRARIES_A.end()) {
|
||||
return module->second;
|
||||
@@ -77,6 +86,7 @@ static HMODULE WINAPI GetModuleHandleW_hook(LPCWSTR lpModuleName) {
|
||||
|
||||
// check hooks
|
||||
if (lpModuleName) {
|
||||
std::shared_lock lock(LIBRARIES_MUTEX);
|
||||
auto module = LIBRARIES_W.find(lpModuleName);
|
||||
if (module != LIBRARIES_W.end()) {
|
||||
return module->second;
|
||||
@@ -129,8 +139,10 @@ void libraryhook_enable(HMODULE module) {
|
||||
|
||||
void libraryhook_hook_library(std::string library_name, HMODULE library_address) {
|
||||
|
||||
const auto library_name_w = s2ws(library_name);
|
||||
std::unique_lock lock(LIBRARIES_MUTEX);
|
||||
// add library to list
|
||||
LIBRARIES_W.insert_or_assign(s2ws(library_name), library_address);
|
||||
LIBRARIES_W.insert_or_assign(library_name_w, library_address);
|
||||
LIBRARIES_A.insert_or_assign(std::move(library_name), library_address);
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
#include "games/qks/qks.h"
|
||||
#include "games/mfg/mfg.h"
|
||||
#include "games/pc/pc.h"
|
||||
#include "games/udn/udn.h"
|
||||
#include "games/museca/museca.h"
|
||||
#include "hooks/avshook.h"
|
||||
#include "hooks/audio/audio.h"
|
||||
@@ -248,6 +249,7 @@ int main_implementation(int argc, char *argv[]) {
|
||||
bool attach_qks = false;
|
||||
bool attach_mfg = false;
|
||||
bool attach_pc = false;
|
||||
bool attach_udn = false;
|
||||
bool attach_museca = false;
|
||||
bool show_cursor_if_no_touch = false;
|
||||
|
||||
@@ -784,6 +786,9 @@ int main_implementation(int argc, char *argv[]) {
|
||||
if (options[launcher::Options::LoadPCModule].value_bool()) {
|
||||
attach_pc = true;
|
||||
}
|
||||
if (options[launcher::Options::LoadUDNModule].value_bool()) {
|
||||
attach_udn = true;
|
||||
}
|
||||
if (options[launcher::Options::LoadMusecaModule].value_bool()) {
|
||||
attach_museca = true;
|
||||
}
|
||||
@@ -1489,6 +1494,12 @@ int main_implementation(int argc, char *argv[]) {
|
||||
if (options[launcher::Options::PCKnobMode].value_bool()) {
|
||||
games::pc::PC_KNOB_MODE = true;
|
||||
}
|
||||
if (options[launcher::Options::UDNArgs].is_active()) {
|
||||
games::udn::UDN_INJECT_ARGS = options[launcher::Options::UDNArgs].value_text();
|
||||
}
|
||||
if (options[launcher::Options::UDNNoIO].is_active()) {
|
||||
games::udn::UDN_NO_IO = options[launcher::Options::UDNNoIO].value_bool();
|
||||
}
|
||||
if (options[launcher::Options::spice2x_EnableSMXStage].value_bool()) {
|
||||
rawinput::ENABLE_SMX_STAGE = true;
|
||||
}
|
||||
@@ -2303,6 +2314,16 @@ int main_implementation(int argc, char *argv[]) {
|
||||
break;
|
||||
}
|
||||
|
||||
// DANCE aROUND
|
||||
if (check_dll("kamunity.dll") && fileutils::dir_exists("game/dancearound_Data")) {
|
||||
avs::game::DLL_NAME = "kamunity.dll";
|
||||
attach_io = true;
|
||||
attach_udn = true;
|
||||
launcher::signal::USE_VEH_WORKAROUND = true;
|
||||
show_cursor_if_no_touch = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Busou Shinki: Armored Princess Battle Conductor
|
||||
if (check_dll("kamunity.dll") && fileutils::file_exists("game/bsac_app.exe")) {
|
||||
avs::game::DLL_NAME = "kamunity.dll";
|
||||
@@ -2468,6 +2489,10 @@ int main_implementation(int argc, char *argv[]) {
|
||||
avs::core::HEAP_SIZE = 536870912; // 512MB
|
||||
games.push_back(new games::pc::PCGame());
|
||||
}
|
||||
if (attach_udn) {
|
||||
avs::core::HEAP_SIZE = 1073741824; // 1GB
|
||||
games.push_back(new games::udn::UDNGame());
|
||||
}
|
||||
|
||||
// apply user heap size, if defined
|
||||
if (user_heap_size > 0) {
|
||||
|
||||
@@ -1511,6 +1511,14 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = std::invoke([]()
|
||||
.game_name = "Polaris Chord",
|
||||
.category = "Advanced Game Options",
|
||||
};
|
||||
c[launcher::Options::LoadUDNModule] = {
|
||||
.title = "Force Load DANCE aROUND Module",
|
||||
.name = "udn",
|
||||
.desc = "Manually enable DANCE aROUND module.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "DANCE aROUND",
|
||||
.category = "Advanced Game Options",
|
||||
};
|
||||
c[launcher::Options::LoadMusecaModule] = {
|
||||
.title = "Force Load Museca Module",
|
||||
.name = "museca",
|
||||
@@ -2451,6 +2459,24 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = std::invoke([]()
|
||||
.game_name = "Polaris Chord",
|
||||
.category = "Game Options",
|
||||
};
|
||||
c[launcher::Options::UDNArgs] = {
|
||||
.title = "DANCE aROUND Arguments Override",
|
||||
.name = "udnargs",
|
||||
.desc = "Command line arguments passed to the game.",
|
||||
.type = OptionType::Text,
|
||||
.setting_name = "",
|
||||
.game_name = "DANCE aROUND",
|
||||
.category = "Advanced Game Options",
|
||||
};
|
||||
c[launcher::Options::UDNNoIO] = {
|
||||
.title = "DANCE aROUND Disable IO Emulation",
|
||||
.name = "udnnoio",
|
||||
.desc = "Disables BI2X hooks for DANCE aROUND.",
|
||||
.type = OptionType::Bool,
|
||||
.setting_name = "",
|
||||
.game_name = "DANCE aROUND",
|
||||
.category = "Advanced Game Options",
|
||||
};
|
||||
c[launcher::Options::spice2x_LightsOverallBrightness] = {
|
||||
.title = "Lights Brightness Adjustment",
|
||||
.name = "sp2x-lights-brightness",
|
||||
|
||||
@@ -150,6 +150,7 @@ namespace launcher {
|
||||
LoadQKSModule,
|
||||
LoadMFGModule,
|
||||
LoadPCModule,
|
||||
LoadUDNModule,
|
||||
LoadMusecaModule,
|
||||
PathToModules,
|
||||
ScreenshotFolder,
|
||||
@@ -250,6 +251,8 @@ namespace launcher {
|
||||
PCArgs,
|
||||
PCNoIO,
|
||||
PCKnobMode,
|
||||
UDNArgs,
|
||||
UDNNoIO,
|
||||
spice2x_LightsOverallBrightness,
|
||||
spice2x_WindowBorder,
|
||||
spice2x_WindowSize,
|
||||
|
||||
@@ -732,6 +732,8 @@ void eamuse_autodetect_game() {
|
||||
eamuse_set_game("Mahjong Fight Girl");
|
||||
else if (avs::game::is_model("XIF"))
|
||||
eamuse_set_game("Polaris Chord");
|
||||
else if (avs::game::is_model("UDN"))
|
||||
eamuse_set_game("DANCE aROUND");
|
||||
else if (avs::game::is_model("NDD"))
|
||||
eamuse_set_game("Silent Scope: Bone Eater");
|
||||
else {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
namespace rawinput {
|
||||
|
||||
static MidiNoteAlgorithm MIDI_NOTE_ALGORITHM = MidiNoteAlgorithm::V2;
|
||||
bool DISABLE_MIDI = false;
|
||||
}
|
||||
|
||||
rawinput::MidiNoteAlgorithm rawinput::get_midi_algorithm() {
|
||||
@@ -37,6 +38,10 @@ void rawinput::set_midi_algorithm(rawinput::MidiNoteAlgorithm new_algo) {
|
||||
|
||||
void rawinput::RawInputManager::midi_scan_start() {
|
||||
|
||||
if (rawinput::DISABLE_MIDI) {
|
||||
return;
|
||||
}
|
||||
|
||||
// single-flight: only one scan runs at a time. if one is already running, set
|
||||
// the pending flag so it rescans once more when it finishes - MIDI hotplug
|
||||
// events fire while the slow enumeration is still going and must not be lost.
|
||||
|
||||
@@ -32,6 +32,7 @@ namespace rawinput {
|
||||
extern bool DUMP_HID_DEVICES_TO_LOG;
|
||||
|
||||
extern uint32_t MIDI_NOTE_SUSTAIN;
|
||||
extern bool DISABLE_MIDI;
|
||||
|
||||
extern bool NAIVE_REQUIRE_FOCUS;
|
||||
extern bool RAWINPUT_REQUIRE_FOCUS;
|
||||
|
||||
@@ -337,6 +337,18 @@ typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_register_d3d9_func)(
|
||||
void *userdata
|
||||
);
|
||||
|
||||
// hook_library (v0.5 and up)
|
||||
//
|
||||
// maps an exact LoadLibrary name to a module that the plugin has already loaded
|
||||
//
|
||||
// library_name: exact ANSI library name or path used by the target
|
||||
// module: module handle returned by LoadLibrary; keep it loaded until shutdown
|
||||
|
||||
typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_hook_library_func)(
|
||||
const char *library_name,
|
||||
void *module
|
||||
);
|
||||
|
||||
typedef struct SPICE_SDK_V0 {
|
||||
uint32_t size;
|
||||
|
||||
@@ -369,6 +381,8 @@ typedef struct SPICE_SDK_V0 {
|
||||
|
||||
spice_sdk_register_d3d9_func *register_d3d9;
|
||||
|
||||
spice_sdk_hook_library_func *hook_library;
|
||||
|
||||
} SPICE_SDK_V0;
|
||||
|
||||
typedef void (__cdecl spice_sdk_destroy_callback_func)(
|
||||
|
||||
@@ -1899,6 +1899,57 @@ typedef enum SPICE_SDK_POLARIS_CHORD_LIGHTS {
|
||||
PC_Light_Button12_B,
|
||||
} SPICE_SDK_POLARIS_CHORD_LIGHTS;
|
||||
|
||||
// DANCE aROUND
|
||||
|
||||
typedef enum SPICE_SDK_DANCE_AROUND_BUTTONS {
|
||||
UDN_Button_Service,
|
||||
UDN_Button_Test,
|
||||
UDN_Button_CoinMech,
|
||||
UDN_Button_Start,
|
||||
UDN_Button_Up,
|
||||
UDN_Button_Down,
|
||||
UDN_Button_Left,
|
||||
UDN_Button_Right,
|
||||
} SPICE_SDK_DANCE_AROUND_BUTTONS;
|
||||
|
||||
typedef enum SPICE_SDK_DANCE_AROUND_LIGHTS {
|
||||
UDN_Light_CardReaderR,
|
||||
UDN_Light_CardReaderG,
|
||||
UDN_Light_CardReaderB,
|
||||
UDN_Light_Start,
|
||||
UDN_Light_Up,
|
||||
UDN_Light_Down,
|
||||
UDN_Light_Left,
|
||||
UDN_Light_Right,
|
||||
UDN_Light_TitleR,
|
||||
UDN_Light_TitleG,
|
||||
UDN_Light_TitleB,
|
||||
UDN_Light_PillarRightTopR,
|
||||
UDN_Light_PillarRightTopG,
|
||||
UDN_Light_PillarRightTopB,
|
||||
UDN_Light_PillarRightBottomR,
|
||||
UDN_Light_PillarRightBottomG,
|
||||
UDN_Light_PillarRightBottomB,
|
||||
UDN_Light_StageLeftR,
|
||||
UDN_Light_StageLeftG,
|
||||
UDN_Light_StageLeftB,
|
||||
UDN_Light_CabinetLeftR,
|
||||
UDN_Light_CabinetLeftG,
|
||||
UDN_Light_CabinetLeftB,
|
||||
UDN_Light_PillarLeftTopR,
|
||||
UDN_Light_PillarLeftTopG,
|
||||
UDN_Light_PillarLeftTopB,
|
||||
UDN_Light_PillarLeftBottomR,
|
||||
UDN_Light_PillarLeftBottomG,
|
||||
UDN_Light_PillarLeftBottomB,
|
||||
UDN_Light_StageRightR,
|
||||
UDN_Light_StageRightG,
|
||||
UDN_Light_StageRightB,
|
||||
UDN_Light_CabinetRightR,
|
||||
UDN_Light_CabinetRightG,
|
||||
UDN_Light_CabinetRightB,
|
||||
} SPICE_SDK_DANCE_AROUND_LIGHTS;
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "d3d9.h"
|
||||
#include "avs/game.h"
|
||||
#include "games/io.h"
|
||||
#include "hooks/libraryhook.h"
|
||||
#include "launcher/launcher.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "overlay/notifications.h"
|
||||
@@ -38,6 +39,7 @@ static spice_sdk_insert_coin_func sdk_insert_coin;
|
||||
static spice_sdk_get_module_info_func sdk_get_module_info;
|
||||
static spice_sdk_get_plugin_directory_func sdk_get_plugin_directory;
|
||||
static spice_sdk_register_d3d9_func sdk_register_d3d9;
|
||||
static spice_sdk_hook_library_func sdk_hook_library;
|
||||
|
||||
// DLLs
|
||||
static int sdk_modules_count = 0;
|
||||
@@ -204,6 +206,11 @@ sdk_init(
|
||||
}
|
||||
// end of 0.4
|
||||
|
||||
if (v0->size >= RTL_SIZEOF_THROUGH_FIELD(SPICE_SDK_V0, hook_library)) {
|
||||
v0->hook_library = sdk_hook_library;
|
||||
}
|
||||
// end of 0.5
|
||||
|
||||
// any newer minor iterations will need to check the size
|
||||
|
||||
{
|
||||
@@ -698,6 +705,30 @@ sdk_add_toast(
|
||||
return SPICE_SDK_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
SPICE_SDK_STATUS_CODE
|
||||
__cdecl
|
||||
sdk_hook_library(
|
||||
const char *library_name,
|
||||
void *module
|
||||
)
|
||||
{
|
||||
std::shared_lock lock(sdk_global_mutex);
|
||||
if (sdk_shutting_down || !sdk_initialized) {
|
||||
return SPICE_SDK_STATUS_TOO_LATE;
|
||||
}
|
||||
|
||||
if (!library_name || !library_name[0]) {
|
||||
return SPICE_SDK_STATUS_INVALID_ARGUMENT_1;
|
||||
}
|
||||
if (!module) {
|
||||
return SPICE_SDK_STATUS_INVALID_ARGUMENT_2;
|
||||
}
|
||||
|
||||
libraryhook_hook_library(library_name, static_cast<HMODULE>(module));
|
||||
// launcher::signal::attach enables library hooks before SDK entry points run.
|
||||
return SPICE_SDK_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
SPICE_SDK_STATUS_CODE
|
||||
__cdecl
|
||||
sdk_insert_coin(
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#include "execexe.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/utils.h"
|
||||
@@ -22,6 +25,23 @@ namespace execexe {
|
||||
static bool port_opened = false;
|
||||
static std::function<void()> deferred_function = nullptr;
|
||||
|
||||
template<typename T>
|
||||
static T resolve_export_thunk(T entry) {
|
||||
// Some execexe builds expose ordinal-only five-byte near jumps backed
|
||||
// only by INT3 padding. MinHook cannot construct a trampoline from
|
||||
// those tiny export stubs, while the jump destination has a normal
|
||||
// prologue.
|
||||
const auto bytes = reinterpret_cast<const uint8_t *>(entry);
|
||||
if (!bytes || bytes[0] != 0xE9) {
|
||||
return entry;
|
||||
}
|
||||
|
||||
int32_t displacement = 0;
|
||||
std::memcpy(&displacement, bytes + 1, sizeof(displacement));
|
||||
return reinterpret_cast<T>(
|
||||
const_cast<uint8_t *>(bytes + 5 + displacement));
|
||||
}
|
||||
|
||||
static HANDLE WINAPI execexe_CreateFileA_hook(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
|
||||
LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition,
|
||||
DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) {
|
||||
@@ -92,6 +112,13 @@ namespace execexe {
|
||||
execexe_CreateFileW = libutils::get_proc<decltype(&CreateFileW)>(execexe_module, MAKEINTRESOURCE(11));
|
||||
execexe_PreLoadLibraries = libutils::get_proc<decltype(execexe_PreLoadLibraries)>(execexe_module, MAKEINTRESOURCE(48));
|
||||
|
||||
if (avs::game::is_model("UDN")) {
|
||||
execexe_CloseHandle = resolve_export_thunk(execexe_CloseHandle);
|
||||
execexe_CreateFileA = resolve_export_thunk(execexe_CreateFileA);
|
||||
execexe_CreateFileW = resolve_export_thunk(execexe_CreateFileW);
|
||||
execexe_PreLoadLibraries = resolve_export_thunk(execexe_PreLoadLibraries);
|
||||
}
|
||||
|
||||
auto module_path = libutils::module_file_name(nullptr);
|
||||
module_path = module_path.replace_extension("");
|
||||
module_path = module_path.replace_filename(module_path.filename().wstring() + L"_Data");
|
||||
@@ -105,6 +132,30 @@ namespace execexe {
|
||||
log_fatal("execexe", "deferred init function is already set");
|
||||
}
|
||||
deferred_function = std::move(init_func);
|
||||
|
||||
// DANCE aROUND calls PreLoadLibraries through kamunity's ordinal
|
||||
// import. Hook that call site directly: execexe can be loaded in the
|
||||
// low address range immediately next to the AVS heap, where MinHook
|
||||
// may be unable to reserve a nearby trampoline page.
|
||||
if (avs::game::is_model("UDN")) {
|
||||
const auto kamunity_module = GetModuleHandleW(L"kamunity.dll");
|
||||
const auto imported_pre_load_libraries = kamunity_module ? detour::iat_try_ordinal(
|
||||
"execexe.dll",
|
||||
48,
|
||||
execexe_PreLoadLibraries_hook,
|
||||
kamunity_module) : nullptr;
|
||||
if (imported_pre_load_libraries) {
|
||||
execexe_PreLoadLibraries = reinterpret_cast<decltype(execexe_PreLoadLibraries)>(
|
||||
imported_pre_load_libraries);
|
||||
log_info("execexe", "hooked PreLoadLibraries through kamunity import");
|
||||
return;
|
||||
}
|
||||
|
||||
detour::trampoline(execexe_PreLoadLibraries,
|
||||
execexe_PreLoadLibraries_hook, &execexe_PreLoadLibraries);
|
||||
return;
|
||||
}
|
||||
|
||||
detour::trampoline("execexe.dll", MAKEINTRESOURCE(48),
|
||||
execexe_PreLoadLibraries_hook, &execexe_PreLoadLibraries);
|
||||
}
|
||||
@@ -117,6 +168,16 @@ namespace execexe {
|
||||
|
||||
port_name = portName;
|
||||
acio = acioHandle;
|
||||
if (avs::game::is_model("UDN")) {
|
||||
detour::trampoline_try(execexe_CloseHandle,
|
||||
execexe_CloseHandle_hook, &execexe_CloseHandle);
|
||||
detour::trampoline_try(execexe_CreateFileA,
|
||||
execexe_CreateFileA_hook, &execexe_CreateFileA);
|
||||
detour::trampoline_try(execexe_CreateFileW,
|
||||
execexe_CreateFileW_hook, &execexe_CreateFileW);
|
||||
return;
|
||||
}
|
||||
|
||||
detour::trampoline_try("execexe.dll", MAKEINTRESOURCE(7),
|
||||
execexe_CloseHandle_hook, &execexe_CloseHandle);
|
||||
detour::trampoline_try("execexe.dll", MAKEINTRESOURCE(9),
|
||||
@@ -183,6 +244,10 @@ namespace execexe {
|
||||
HMODULE module = get_module(dll);
|
||||
FARPROC proc = get_proc(module, func);
|
||||
|
||||
if (avs::game::is_model("UDN")) {
|
||||
// The existing detour helper reads *orig as the target to enable.
|
||||
*orig = reinterpret_cast<void *>(proc);
|
||||
}
|
||||
return detour::trampoline(
|
||||
reinterpret_cast<void *>(proc),
|
||||
hook,
|
||||
@@ -201,6 +266,9 @@ namespace execexe {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (avs::game::is_model("UDN")) {
|
||||
*orig = reinterpret_cast<void *>(proc);
|
||||
}
|
||||
return detour::trampoline(
|
||||
reinterpret_cast<void *>(proc),
|
||||
hook,
|
||||
|
||||
Reference in New Issue
Block a user