Merge branch 'spice2x' into spice22x
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
#include "bbc.h"
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/shared/lcdhandle.h"
|
||||
#include "hooks/devicehook.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
@@ -14,6 +16,9 @@ namespace games::bbc {
|
||||
void BBCGame::attach() {
|
||||
Game::attach();
|
||||
|
||||
devicehook_init();
|
||||
devicehook_add(new games::shared::LCDHandle());
|
||||
|
||||
// window patch
|
||||
if (GRAPHICS_WINDOWED) {
|
||||
unsigned char pattern[] = {
|
||||
@@ -34,4 +39,6 @@ namespace games::bbc {
|
||||
log_warning("bbc", "windowed mode failed");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -40,6 +40,16 @@ std::vector<Button> &games::bbc::get_buttons() {
|
||||
return buttons;
|
||||
}
|
||||
|
||||
std::string games::bbc::get_buttons_help() {
|
||||
// keep to max 100 characters wide
|
||||
return
|
||||
" green \n"
|
||||
" red blue \n"
|
||||
"\n"
|
||||
"Green button is a spinning disk that can be pressed."
|
||||
;
|
||||
}
|
||||
|
||||
std::vector<Analog> &games::bbc::get_analogs() {
|
||||
static std::vector<Analog> analogs;
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@ namespace games::bbc {
|
||||
|
||||
// getters
|
||||
std::vector<Button> &get_buttons();
|
||||
std::string get_buttons_help();
|
||||
std::vector<Analog> &get_analogs();
|
||||
std::vector<Light> &get_lights();
|
||||
}
|
||||
|
||||
@@ -4,9 +4,19 @@
|
||||
#include "util/libutils.h"
|
||||
#include "acio2emu/handle.h"
|
||||
#include "acioemu/handle.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "util/detour.h"
|
||||
#include "node.h"
|
||||
|
||||
namespace games::bc {
|
||||
|
||||
static decltype(ShowCursor) *ShowCursor_orig = nullptr;
|
||||
|
||||
static int WINAPI ShowCursor_hook(BOOL bShow) {
|
||||
// log_misc("bsac", "ShowCursor {}", bShow);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void BCGame::attach() {
|
||||
Game::attach();
|
||||
|
||||
@@ -22,6 +32,12 @@ namespace games::bc {
|
||||
auto iob = new acio2emu::IOBHandle(L"COM3");
|
||||
iob->register_node(std::make_unique<TBSNode>());
|
||||
|
||||
if (GRAPHICS_SHOW_CURSOR) {
|
||||
detour::trampoline_try(
|
||||
"user32.dll", "ShowCursor",
|
||||
(void*)ShowCursor_hook, (void**)&ShowCursor_orig);
|
||||
}
|
||||
|
||||
devicehook_init();
|
||||
devicehook_add(iob);
|
||||
devicehook_add(new acioemu::ACIOHandle(L"COM1"));
|
||||
|
||||
@@ -26,3 +26,24 @@ std::vector<Button> &games::bc::get_buttons() {
|
||||
|
||||
return buttons;
|
||||
}
|
||||
|
||||
std::string games::bc::get_buttons_help() {
|
||||
// keep to max 100 characters wide
|
||||
return "";
|
||||
}
|
||||
|
||||
std::vector<Analog> &games::bc::get_analogs() {
|
||||
static std::vector<Analog> analogs;
|
||||
|
||||
if (analogs.empty()) {
|
||||
analogs = GameAPI::Analogs::getAnalogs("Busou Shinki: Armored Princess Battle Conductor");
|
||||
|
||||
GameAPI::Analogs::sortAnalogs(
|
||||
&analogs,
|
||||
"Stick X",
|
||||
"Stick Y"
|
||||
);
|
||||
}
|
||||
|
||||
return analogs;
|
||||
}
|
||||
@@ -23,5 +23,14 @@ namespace games::bc {
|
||||
};
|
||||
}
|
||||
|
||||
namespace Analogs {
|
||||
enum {
|
||||
StickX,
|
||||
StickY,
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<Button> &get_buttons();
|
||||
std::string get_buttons_help();
|
||||
std::vector<Analog> &get_analogs();
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ namespace games::bc {
|
||||
|
||||
bool read_input(std::vector<uint8_t> &buffer) {
|
||||
auto &buttons = get_buttons();
|
||||
auto &analogs = get_analogs();
|
||||
coins_ += eamuse_coin_consume_stock();
|
||||
|
||||
buffer.reserve(buffer.size() + 10);
|
||||
@@ -57,6 +58,9 @@ namespace games::bc {
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Right])) {
|
||||
joy -= INT16_MAX;
|
||||
}
|
||||
if (analogs[Analogs::StickX].isSet()) {
|
||||
joy = 65535 - (uint16_t) (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::StickX]) * 65535);
|
||||
}
|
||||
buffer.push_back(static_cast<uint8_t>(joy >> 8));
|
||||
buffer.push_back(static_cast<uint8_t>(joy));
|
||||
|
||||
@@ -67,6 +71,9 @@ namespace games::bc {
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Up])) {
|
||||
joy -= INT16_MAX;
|
||||
}
|
||||
if (analogs[Analogs::StickY].isSet()) {
|
||||
joy = 65535 - (uint16_t) (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::StickY]) * 65535);
|
||||
}
|
||||
buffer.push_back(static_cast<uint8_t>(joy >> 8));
|
||||
buffer.push_back(static_cast<uint8_t>(joy));
|
||||
|
||||
|
||||
@@ -0,0 +1,624 @@
|
||||
#include "bi2x_hook.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "games/io.h"
|
||||
#include "io.h"
|
||||
#include "util/tapeled.h"
|
||||
|
||||
namespace games::ccj {
|
||||
|
||||
/*
|
||||
* class definitions
|
||||
*/
|
||||
|
||||
struct AIO_SCI_COMM {
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB2 {
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_TBS {
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_WRFIRM {
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB__NODEINFO {
|
||||
uint8_t data[0xA3];
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_AC1__INPUTDATA {
|
||||
uint8_t data[247];
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_AC1__OUTPUTDATA {
|
||||
uint8_t data[48];
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_TBS__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 bHPDetect;
|
||||
uint16_t StickY;
|
||||
uint16_t StickX;
|
||||
uint8_t bStickBtn;
|
||||
uint8_t bTrigger1;
|
||||
uint8_t bTrigger2;
|
||||
uint8_t bButton0;
|
||||
uint8_t bButton1;
|
||||
uint8_t bButton2;
|
||||
uint8_t bButton3;
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_TBS__DEVSTATUS {
|
||||
uint8_t InputCounter;
|
||||
uint8_t OutputCounter;
|
||||
uint8_t IoResetCounter;
|
||||
uint8_t TapeLedCounter;
|
||||
AIO_IOB2_BI2X_TBS__INPUT Input;
|
||||
AIO_IOB2_BI2X_AC1__INPUTDATA InputData;
|
||||
AIO_IOB2_BI2X_AC1__OUTPUTDATA OutputData;
|
||||
};
|
||||
|
||||
static void write_iccr_led(Lights::ccj_lights_t light, uint8_t value);
|
||||
|
||||
/*
|
||||
* typedefs
|
||||
*/
|
||||
|
||||
// libaio-iob2_video.dll
|
||||
typedef AIO_IOB2_BI2X_TBS* (__fastcall *aioIob2Bi2xTBS_Create_t)(AIO_NMGR_IOB2 *i_pNodeMgr, uint32_t i_DevId);
|
||||
typedef void (__fastcall *aioIob2Bi2xTBS_GetDeviceStatus_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl,
|
||||
AIO_IOB2_BI2X_TBS__DEVSTATUS *o_DevStatus);
|
||||
typedef void (__fastcall *aioIob2Bi2xTBS_IoReset_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_bfIoReset);
|
||||
typedef void (__fastcall *aioIob2Bi2xAC1_IoReset_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_bfIoReset);
|
||||
typedef void (__fastcall *aioIob2Bi2xTBS_SetWatchDogTimer_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint8_t i_Count);
|
||||
typedef void (__fastcall *aioIob2Bi2xAC1_SetWatchDogTimer_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint8_t i_Count);
|
||||
typedef void (__fastcall *aioIob2Bi2xTBS_ControlCoinBlocker_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_Slot,
|
||||
bool i_bOpen);
|
||||
typedef void (__fastcall *aioIob2Bi2xTBS_AddCounter_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_Counter,
|
||||
uint32_t i_Count);
|
||||
typedef void (__fastcall *aioIob2Bi2xTBS_SetAmpVolume_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_Amp,
|
||||
uint32_t i_Volume);
|
||||
typedef void (__fastcall *aioIob2Bi2xTBS_EnableUsbCharger_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl, bool i_bEnable);
|
||||
typedef void (__fastcall *aioIob2Bi2xTBS_SetIrLed_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl, bool i_bOn);
|
||||
typedef void (__fastcall *aioIob2Bi2xTBS_SetButton0Lamp_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl, bool i_bOn);
|
||||
typedef void (__fastcall *aioIob2Bi2xTBS_SetIccrLed_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_RGB);
|
||||
typedef void (__fastcall *aioIob2Bi2xTBS_SetStickLed_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_RGB);
|
||||
typedef void (__fastcall *aioIob2Bi2xTBS_SetTapeLedData_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_TapeLed, uint8_t *i_pData);
|
||||
typedef AIO_SCI_COMM* (__fastcall *aioIob2Bi2x_OpenSciUsbCdc_t)(uint32_t i_SerialNumber);
|
||||
typedef AIO_IOB2_BI2X_WRFIRM* (__fastcall *aioIob2Bi2x_CreateWriteFirmContext_t)(uint32_t i_SerialNumber,
|
||||
uint32_t i_bfIob);
|
||||
typedef void (__fastcall *aioIob2Bi2x_DestroyWriteFirmContext_t)(AIO_IOB2_BI2X_WRFIRM *i_pWrFirm);
|
||||
typedef int32_t (__fastcall *aioIob2Bi2x_WriteFirmGetState_t)(AIO_IOB2_BI2X_WRFIRM *i_pWrFirm);
|
||||
typedef bool (__fastcall *aioIob2Bi2x_WriteFirmIsCompleted_t)(int32_t i_State);
|
||||
typedef bool (__fastcall *aioIob2Bi2x_WriteFirmIsError_t)(int32_t i_State);
|
||||
|
||||
// libaio-iob.dll
|
||||
typedef AIO_NMGR_IOB2* (__fastcall *aioNMgrIob2_Create_t)(AIO_SCI_COMM *i_pSci, uint32_t i_bfMode);
|
||||
typedef void (__fastcall *aioNMgrIob_BeginManage_t)(AIO_NMGR_IOB2 *i_pNodeMgr);
|
||||
typedef void (__fastcall *aioNCtlIob_GetNodeInfo_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl,
|
||||
AIO_NMGR_IOB__NODEINFO *o_NodeInfo);
|
||||
|
||||
// libaio.dll
|
||||
typedef void (__fastcall *aioNodeMgr_Destroy_t)(AIO_NMGR_IOB2 *i_pNodeMgr);
|
||||
typedef int32_t (__fastcall *aioNodeMgr_GetState_t)(AIO_NMGR_IOB2 *i_pNodeMgr);
|
||||
typedef bool (__fastcall *aioNodeMgr_IsReady_t)(AIO_NMGR_IOB2 *i_pNodeMgr, int32_t i_State);
|
||||
typedef bool (__fastcall *aioNodeMgr_IsError_t)(AIO_NMGR_IOB2 *i_pNodeMgr, int32_t i_State);
|
||||
typedef void (__fastcall *aioNodeCtl_Destroy_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl);
|
||||
typedef int32_t (__fastcall *aioNodeCtl_GetState_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl);
|
||||
typedef bool (__fastcall *aioNodeCtl_IsReady_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl, int32_t i_State);
|
||||
typedef bool (__fastcall *aioNodeCtl_IsError_t)(AIO_IOB2_BI2X_TBS *i_pNodeCtl, int32_t i_State);
|
||||
|
||||
/*
|
||||
* function pointers
|
||||
*/
|
||||
|
||||
// libaio-iob2_video.dll
|
||||
static aioIob2Bi2xTBS_Create_t aioIob2Bi2xTBS_Create_orig = nullptr;
|
||||
static aioIob2Bi2xTBS_GetDeviceStatus_t aioIob2Bi2xTBS_GetDeviceStatus_orig = nullptr;
|
||||
static aioIob2Bi2xTBS_IoReset_t aioIob2Bi2xTBS_IoReset_orig = nullptr;
|
||||
static aioIob2Bi2xAC1_IoReset_t aioIob2Bi2xAC1_IoReset_orig = nullptr;
|
||||
static aioIob2Bi2xTBS_SetWatchDogTimer_t aioIob2Bi2xTBS_SetWatchDogTimer_orig = nullptr;
|
||||
static aioIob2Bi2xAC1_SetWatchDogTimer_t aioIob2Bi2xAC1_SetWatchDogTimer_orig = nullptr;
|
||||
static aioIob2Bi2xTBS_ControlCoinBlocker_t aioIob2Bi2xTBS_ControlCoinBlocker_orig = nullptr;
|
||||
static aioIob2Bi2xTBS_AddCounter_t aioIob2Bi2xTBS_AddCounter_orig = nullptr;
|
||||
static aioIob2Bi2xTBS_SetAmpVolume_t aioIob2Bi2xTBS_SetAmpVolume_orig = nullptr;
|
||||
static aioIob2Bi2xTBS_EnableUsbCharger_t aioIob2Bi2xTBS_EnableUsbCharger_orig = nullptr;
|
||||
static aioIob2Bi2xTBS_SetIrLed_t aioIob2Bi2xTBS_SetIrLed_orig = nullptr;
|
||||
static aioIob2Bi2xTBS_SetButton0Lamp_t aioIob2Bi2xTBS_SetButton0Lamp_orig = nullptr;
|
||||
static aioIob2Bi2xTBS_SetIccrLed_t aioIob2Bi2xTBS_SetIccrLed_orig = nullptr;
|
||||
static aioIob2Bi2xTBS_SetStickLed_t aioIob2Bi2xTBS_SetStickLed_orig = nullptr;
|
||||
static aioIob2Bi2xTBS_SetTapeLedData_t aioIob2Bi2xTBS_SetTapeLedData_orig = nullptr;
|
||||
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;
|
||||
|
||||
// libaio-iob.dll
|
||||
static aioNMgrIob2_Create_t aioNMgrIob2_Create_orig = nullptr;
|
||||
static aioNMgrIob_BeginManage_t aioNMgrIob_BeginManage_orig = nullptr;
|
||||
static aioNCtlIob_GetNodeInfo_t aioNCtlIob_GetNodeInfo_orig = nullptr;
|
||||
|
||||
// libaio.dll
|
||||
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 aioNodeMgr_IsError_t aioNodeMgr_IsError_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 aioNodeCtl_IsError_t aioNodeCtl_IsError_orig = nullptr;
|
||||
|
||||
/*
|
||||
* variables
|
||||
*/
|
||||
|
||||
static AIO_SCI_COMM *aioSciComm;
|
||||
static AIO_NMGR_IOB2 *aioNmgrIob2;
|
||||
static AIO_IOB2_BI2X_TBS *aioIob2Bi2xTbs;
|
||||
static AIO_IOB2_BI2X_WRFIRM *aioIob2Bi2xWrfirm;
|
||||
|
||||
static uint8_t count = 0;
|
||||
|
||||
/*
|
||||
* implementations
|
||||
*/
|
||||
|
||||
static AIO_IOB2_BI2X_TBS* __fastcall aioIob2Bi2xTBS_Create(
|
||||
AIO_NMGR_IOB2 *i_pNodeMgr, uint32_t i_DevId) {
|
||||
|
||||
if (i_pNodeMgr == aioNmgrIob2) {
|
||||
log_info("bi2x_hook", "node created");
|
||||
aioIob2Bi2xTbs = new AIO_IOB2_BI2X_TBS;
|
||||
return aioIob2Bi2xTbs;
|
||||
} else {
|
||||
return aioIob2Bi2xTBS_Create_orig(i_pNodeMgr, i_DevId);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xTBS_GetDeviceStatus(
|
||||
AIO_IOB2_BI2X_TBS *i_pNodeCtl, AIO_IOB2_BI2X_TBS__DEVSTATUS *o_DevStatus) {
|
||||
|
||||
RI_MGR->devices_flush_output();
|
||||
|
||||
if (i_pNodeCtl != aioIob2Bi2xTbs) {
|
||||
return aioIob2Bi2xTBS_GetDeviceStatus_orig(i_pNodeCtl, o_DevStatus);
|
||||
}
|
||||
|
||||
memset(o_DevStatus, 0x00, sizeof(AIO_IOB2_BI2X_TBS__DEVSTATUS));
|
||||
|
||||
o_DevStatus->Input.DevIoCounter = count;
|
||||
count++;
|
||||
|
||||
o_DevStatus->Input.StickX = 32768;
|
||||
o_DevStatus->Input.StickY = 32768;
|
||||
|
||||
auto &analogs = get_analogs();
|
||||
if (analogs[Analogs::Joystick_X].isSet() || analogs[Analogs::Joystick_Y].isSet()) {
|
||||
o_DevStatus->Input.StickX =
|
||||
65535 - (uint16_t) (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::Joystick_X]) * 65535);
|
||||
o_DevStatus->Input.StickY =
|
||||
65535 - (uint16_t) (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::Joystick_Y]) * 65535);
|
||||
}
|
||||
|
||||
auto &buttons = get_buttons();
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test]))
|
||||
o_DevStatus->Input.bTest = 1;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service]))
|
||||
o_DevStatus->Input.bService = 1;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::CoinMech]))
|
||||
o_DevStatus->Input.bCoinSw = 1;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Joystick_Up]))
|
||||
o_DevStatus->Input.StickY = 65535;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Joystick_Down]))
|
||||
o_DevStatus->Input.StickY = 0;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Joystick_Left]))
|
||||
o_DevStatus->Input.StickX = 65535;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Joystick_Right]))
|
||||
o_DevStatus->Input.StickX = 0;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button_Dash]))
|
||||
o_DevStatus->Input.bTrigger1 = 1;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button_Special]))
|
||||
o_DevStatus->Input.bButton0 = 1;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button_Action]))
|
||||
o_DevStatus->Input.bButton1 = 1;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button_Jump]))
|
||||
o_DevStatus->Input.bButton2 = 1;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button_Slide]))
|
||||
o_DevStatus->Input.bButton3 = 1;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Headphones]))
|
||||
o_DevStatus->Input.bHPDetect = 1;
|
||||
|
||||
o_DevStatus->Input.CoinCount = eamuse_coin_get_stock();
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xAC1_IoReset(
|
||||
AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_bfIoReset) {
|
||||
|
||||
if (i_pNodeCtl == aioIob2Bi2xTbs) {
|
||||
} else {
|
||||
return aioIob2Bi2xAC1_IoReset_orig(i_pNodeCtl, i_bfIoReset);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xTBS_IoReset(
|
||||
AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_bfIoReset) {
|
||||
|
||||
if (i_pNodeCtl == aioIob2Bi2xTbs) {
|
||||
return aioIob2Bi2xAC1_IoReset(i_pNodeCtl, i_bfIoReset);
|
||||
} else {
|
||||
return aioIob2Bi2xTBS_IoReset_orig(i_pNodeCtl, i_bfIoReset);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xAC1_SetWatchDogTimer(
|
||||
AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint8_t i_Count) {
|
||||
|
||||
if (i_pNodeCtl == aioIob2Bi2xTbs) {
|
||||
} else {
|
||||
return aioIob2Bi2xAC1_SetWatchDogTimer_orig(i_pNodeCtl, i_Count);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xTBS_SetWatchDogTimer(
|
||||
AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint8_t i_Count) {
|
||||
|
||||
if (i_pNodeCtl == aioIob2Bi2xTbs) {
|
||||
return aioIob2Bi2xAC1_SetWatchDogTimer(i_pNodeCtl, i_Count);
|
||||
} else {
|
||||
return aioIob2Bi2xTBS_SetWatchDogTimer_orig(i_pNodeCtl, i_Count);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xTBS_ControlCoinBlocker(
|
||||
AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_Slot, bool i_bOpen) {
|
||||
|
||||
if (i_pNodeCtl == aioIob2Bi2xTbs) {
|
||||
eamuse_coin_set_block(!i_bOpen);
|
||||
} else {
|
||||
return aioIob2Bi2xTBS_ControlCoinBlocker_orig(i_pNodeCtl, i_Slot, i_bOpen);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xTBS_AddCounter(
|
||||
AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_Counter, uint32_t i_Count) {
|
||||
|
||||
if (i_pNodeCtl == aioIob2Bi2xTbs && i_Count == 0) {
|
||||
eamuse_coin_set_stock((uint16_t) i_Count);
|
||||
} else {
|
||||
return aioIob2Bi2xTBS_AddCounter_orig(i_pNodeCtl, i_Counter, i_Count);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xTBS_SetAmpVolume(
|
||||
AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_Amp, uint32_t i_Volume) {
|
||||
|
||||
if (i_pNodeCtl != aioIob2Bi2xTbs) {
|
||||
return aioIob2Bi2xTBS_SetAmpVolume_orig(i_pNodeCtl, i_Amp, i_Volume);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xTBS_EnableUsbCharger(
|
||||
AIO_IOB2_BI2X_TBS *i_pNodeCtl, bool i_bEnable) {
|
||||
|
||||
if (i_pNodeCtl != aioIob2Bi2xTbs) {
|
||||
return aioIob2Bi2xTBS_EnableUsbCharger_orig(i_pNodeCtl, i_bEnable);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xTBS_SetIrLed(AIO_IOB2_BI2X_TBS *i_pNodeCtl, bool i_bOn) {
|
||||
if (i_pNodeCtl != aioIob2Bi2xTbs) {
|
||||
return aioIob2Bi2xTBS_SetIrLed_orig(i_pNodeCtl, i_bOn);
|
||||
}
|
||||
|
||||
// handle ir led
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xTBS_SetButton0Lamp(AIO_IOB2_BI2X_TBS *i_pNodeCtl, bool i_bOn) {
|
||||
if (i_pNodeCtl != aioIob2Bi2xTbs) {
|
||||
return aioIob2Bi2xTBS_SetButton0Lamp_orig(i_pNodeCtl, i_bOn);
|
||||
}
|
||||
|
||||
auto &lights = get_lights();
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights.at(Lights::SpecialButton), (i_bOn ? 1.f : 0.f));
|
||||
}
|
||||
|
||||
static void write_iccr_led(Lights::ccj_lights_t light, uint8_t value) {
|
||||
auto &lights = get_lights();
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights.at(light), value / 255);
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xTBS_SetIccrLed(AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_RGB) {
|
||||
if (i_pNodeCtl != aioIob2Bi2xTbs) {
|
||||
return aioIob2Bi2xTBS_SetIccrLed_orig(i_pNodeCtl, i_RGB);
|
||||
}
|
||||
|
||||
write_iccr_led(Lights::CardReader_B, i_RGB);
|
||||
write_iccr_led(Lights::CardReader_G, i_RGB >> 8);
|
||||
write_iccr_led(Lights::CardReader_R, i_RGB >> 16);
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xTBS_SetStickLed(AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_RGB) {
|
||||
if (i_pNodeCtl != aioIob2Bi2xTbs) {
|
||||
return aioIob2Bi2xTBS_SetStickLed_orig(i_pNodeCtl, i_RGB);
|
||||
}
|
||||
|
||||
// handle stick led
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xTBS_SetTapeLedData(
|
||||
AIO_IOB2_BI2X_TBS *i_pNodeCtl, uint32_t i_TapeLed, uint8_t *i_pData) {
|
||||
|
||||
if (i_pNodeCtl != aioIob2Bi2xTbs) {
|
||||
return aioIob2Bi2xTBS_SetTapeLedData_orig(i_pNodeCtl, i_TapeLed, i_pData);
|
||||
}
|
||||
|
||||
/*
|
||||
* index mapping
|
||||
* 0 - title panel - 144 bytes - 48 colors
|
||||
* 1 - side panel - 147 bytes - 49 colors
|
||||
*
|
||||
* data is stored in RGB order, 3 bytes per color
|
||||
*
|
||||
* TODO: expose this data via API
|
||||
*/
|
||||
static struct TapeLedMapping {
|
||||
size_t data_size;
|
||||
int r, g, b;
|
||||
|
||||
TapeLedMapping(size_t data_size, int r, int g, int b)
|
||||
: data_size(data_size), r(r), g(g), b(b) {}
|
||||
|
||||
} mapping[] = {
|
||||
{ 48, Lights::TitlePanel_R, Lights::TitlePanel_G, Lights::TitlePanel_B },
|
||||
{ 49, Lights::SidePanel_R, Lights::SidePanel_G, Lights::SidePanel_B },
|
||||
};
|
||||
|
||||
if (tapeledutils::is_enabled() && i_TapeLed < std::size(mapping)) {
|
||||
auto &map = mapping[i_TapeLed];
|
||||
|
||||
// pick a color to use
|
||||
const auto rgb = tapeledutils::pick_color_from_led_tape(i_pData, map.data_size);
|
||||
|
||||
// program the lights into API
|
||||
auto &lights = get_lights();
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[map.r], rgb.r);
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[map.g], rgb.g);
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[map.b], rgb.b);
|
||||
}
|
||||
}
|
||||
|
||||
static AIO_SCI_COMM* __fastcall aioIob2Bi2x_OpenSciUsbCdc(uint32_t i_SerialNumber) {
|
||||
aioSciComm = new AIO_SCI_COMM;
|
||||
return aioSciComm;
|
||||
}
|
||||
|
||||
static AIO_IOB2_BI2X_WRFIRM* __fastcall aioIob2Bi2x_CreateWriteFirmContext(
|
||||
uint32_t i_SerialNumber, uint32_t i_bfIob) {
|
||||
|
||||
aioIob2Bi2xWrfirm = new AIO_IOB2_BI2X_WRFIRM;
|
||||
return aioIob2Bi2xWrfirm;
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2x_DestroyWriteFirmContext(AIO_IOB2_BI2X_WRFIRM *i_pWrFirm) {
|
||||
if (i_pWrFirm == aioIob2Bi2xWrfirm) {
|
||||
delete aioIob2Bi2xWrfirm;
|
||||
aioIob2Bi2xWrfirm = nullptr;
|
||||
} else {
|
||||
return aioIob2Bi2x_DestroyWriteFirmContext_orig(i_pWrFirm);
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t __fastcall aioIob2Bi2x_WriteFirmGetState(AIO_IOB2_BI2X_WRFIRM *i_pWrFirm) {
|
||||
if (i_pWrFirm == aioIob2Bi2xWrfirm) {
|
||||
return 8;
|
||||
} else {
|
||||
return aioIob2Bi2x_WriteFirmGetState_orig(i_pWrFirm);
|
||||
}
|
||||
}
|
||||
|
||||
static bool __fastcall aioIob2Bi2x_WriteFirmIsCompleted(int32_t i_State) {
|
||||
if (aioIob2Bi2xWrfirm != nullptr)
|
||||
return true;
|
||||
return aioIob2Bi2x_WriteFirmIsCompleted_orig(i_State);
|
||||
}
|
||||
|
||||
static bool __fastcall aioIob2Bi2x_WriteFirmIsError(int32_t i_State) {
|
||||
if (aioIob2Bi2xWrfirm != nullptr)
|
||||
return false;
|
||||
return aioIob2Bi2x_WriteFirmIsError_orig(i_State);
|
||||
}
|
||||
|
||||
static AIO_NMGR_IOB2* __fastcall aioNMgrIob2_Create(AIO_SCI_COMM *i_pSci, uint32_t i_bfMode) {
|
||||
if (i_pSci == aioSciComm) {
|
||||
aioNmgrIob2 = new AIO_NMGR_IOB2;
|
||||
return aioNmgrIob2;
|
||||
} else {
|
||||
return aioNMgrIob2_Create_orig(i_pSci, i_bfMode);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioNMgrIob_BeginManage(AIO_NMGR_IOB2 *i_pNodeMgr) {
|
||||
if (i_pNodeMgr == aioNmgrIob2) {
|
||||
} else {
|
||||
return aioNMgrIob_BeginManage_orig(i_pNodeMgr);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioNCtlIob_GetNodeInfo(
|
||||
AIO_IOB2_BI2X_TBS *i_pNodeCtl, AIO_NMGR_IOB__NODEINFO *o_NodeInfo) {
|
||||
|
||||
if (i_pNodeCtl == aioIob2Bi2xTbs) {
|
||||
memset(o_NodeInfo, 0, sizeof(AIO_NMGR_IOB__NODEINFO));
|
||||
} else {
|
||||
return aioNCtlIob_GetNodeInfo_orig(i_pNodeCtl, o_NodeInfo);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioNodeMgr_Destroy(AIO_NMGR_IOB2 *i_pNodeMgr) {
|
||||
if (i_pNodeMgr == aioNmgrIob2) {
|
||||
delete aioNmgrIob2;
|
||||
aioNmgrIob2 = nullptr;
|
||||
} else {
|
||||
return aioNodeMgr_Destroy_orig(i_pNodeMgr);
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t __fastcall aioNodeMgr_GetState(AIO_NMGR_IOB2 *i_pNodeMgr) {
|
||||
if (i_pNodeMgr == aioNmgrIob2) {
|
||||
return 1;
|
||||
} else {
|
||||
return aioNodeMgr_GetState_orig(i_pNodeMgr);
|
||||
}
|
||||
}
|
||||
|
||||
static bool __fastcall aioNodeMgr_IsReady(AIO_NMGR_IOB2 *i_pNodeMgr, int32_t i_State) {
|
||||
if (i_pNodeMgr == aioNmgrIob2) {
|
||||
return true;
|
||||
} else {
|
||||
return aioNodeMgr_IsReady_orig(i_pNodeMgr, i_State);
|
||||
}
|
||||
}
|
||||
|
||||
static bool __fastcall aioNodeMgr_IsError(AIO_NMGR_IOB2 *i_pNodeMgr, int32_t i_State) {
|
||||
if (i_pNodeMgr == aioNmgrIob2) {
|
||||
return false;
|
||||
} else {
|
||||
return aioNodeMgr_IsError_orig(i_pNodeMgr, i_State);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioNodeCtl_Destroy(AIO_IOB2_BI2X_TBS *i_pNodeCtl) {
|
||||
if (i_pNodeCtl == aioIob2Bi2xTbs) {
|
||||
delete aioIob2Bi2xTbs;
|
||||
aioIob2Bi2xTbs = nullptr;
|
||||
} else {
|
||||
return aioNodeCtl_Destroy_orig(i_pNodeCtl);
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t __fastcall aioNodeCtl_GetState(AIO_IOB2_BI2X_TBS *i_pNodeCtl) {
|
||||
if (i_pNodeCtl == aioIob2Bi2xTbs) {
|
||||
return 1;
|
||||
} else {
|
||||
return aioNodeCtl_GetState_orig(i_pNodeCtl);
|
||||
}
|
||||
}
|
||||
|
||||
static bool __fastcall aioNodeCtl_IsReady(AIO_IOB2_BI2X_TBS *i_pNodeCtl, int32_t i_State) {
|
||||
if (i_pNodeCtl == aioIob2Bi2xTbs) {
|
||||
return true;
|
||||
} else {
|
||||
return aioNodeCtl_IsReady_orig(i_pNodeCtl, i_State);
|
||||
}
|
||||
}
|
||||
|
||||
static bool __fastcall aioNodeCtl_IsError(AIO_IOB2_BI2X_TBS *i_pNodeCtl, int32_t i_State) {
|
||||
if (i_pNodeCtl == aioIob2Bi2xTbs) {
|
||||
return false;
|
||||
} else {
|
||||
return aioNodeCtl_IsError_orig(i_pNodeCtl, i_State);
|
||||
}
|
||||
}
|
||||
|
||||
void bi2x_hook_init() {
|
||||
// avoid double init
|
||||
static bool initialized = false;
|
||||
if (initialized) {
|
||||
return;
|
||||
} else {
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
// announce
|
||||
log_info("bi2x_hook", "init");
|
||||
|
||||
// libaio-iob2_video.dll
|
||||
const auto libaioIob2VideoDll = "libaio-iob2_video.dll";
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xTBS_Create",
|
||||
aioIob2Bi2xTBS_Create, &aioIob2Bi2xTBS_Create_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xTBS_GetDeviceStatus",
|
||||
aioIob2Bi2xTBS_GetDeviceStatus, &aioIob2Bi2xTBS_GetDeviceStatus_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xTBS_IoReset",
|
||||
aioIob2Bi2xTBS_IoReset, &aioIob2Bi2xTBS_IoReset_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_IoReset",
|
||||
aioIob2Bi2xAC1_IoReset, &aioIob2Bi2xAC1_IoReset_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xTBS_SetWatchDogTimer",
|
||||
aioIob2Bi2xTBS_SetWatchDogTimer, &aioIob2Bi2xTBS_SetWatchDogTimer_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_SetWatchDogTimer",
|
||||
aioIob2Bi2xAC1_SetWatchDogTimer, &aioIob2Bi2xAC1_SetWatchDogTimer_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xTBS_ControlCoinBlocker",
|
||||
aioIob2Bi2xTBS_ControlCoinBlocker, &aioIob2Bi2xTBS_ControlCoinBlocker_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xTBS_AddCounter",
|
||||
aioIob2Bi2xTBS_AddCounter, &aioIob2Bi2xTBS_AddCounter_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xTBS_SetAmpVolume",
|
||||
aioIob2Bi2xTBS_SetAmpVolume, &aioIob2Bi2xTBS_SetAmpVolume_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xTBS_EnableUsbCharger",
|
||||
aioIob2Bi2xTBS_EnableUsbCharger, &aioIob2Bi2xTBS_EnableUsbCharger_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xTBS_SetIrLed",
|
||||
aioIob2Bi2xTBS_SetIrLed, &aioIob2Bi2xTBS_SetIrLed_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xTBS_SetButton0Lamp",
|
||||
aioIob2Bi2xTBS_SetButton0Lamp, &aioIob2Bi2xTBS_SetButton0Lamp_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xTBS_SetIccrLed",
|
||||
aioIob2Bi2xTBS_SetIccrLed, &aioIob2Bi2xTBS_SetIccrLed_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xTBS_SetStickLed",
|
||||
aioIob2Bi2xTBS_SetStickLed, &aioIob2Bi2xTBS_SetStickLed_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xTBS_SetTapeLedData",
|
||||
aioIob2Bi2xTBS_SetTapeLedData, &aioIob2Bi2xTBS_SetTapeLedData_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2x_OpenSciUsbCdc",
|
||||
aioIob2Bi2x_OpenSciUsbCdc, &aioIob2Bi2x_OpenSciUsbCdc_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2x_CreateWriteFirmContext",
|
||||
aioIob2Bi2x_CreateWriteFirmContext, &aioIob2Bi2x_CreateWriteFirmContext_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2x_DestroyWriteFirmContext",
|
||||
aioIob2Bi2x_DestroyWriteFirmContext, &aioIob2Bi2x_DestroyWriteFirmContext_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2x_WriteFirmGetState",
|
||||
aioIob2Bi2x_WriteFirmGetState, &aioIob2Bi2x_WriteFirmGetState_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2x_WriteFirmIsCompleted",
|
||||
aioIob2Bi2x_WriteFirmIsCompleted, &aioIob2Bi2x_WriteFirmIsCompleted_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2x_WriteFirmIsError",
|
||||
aioIob2Bi2x_WriteFirmIsError, &aioIob2Bi2x_WriteFirmIsError_orig);
|
||||
|
||||
// libaio-iob.dll
|
||||
const auto libaioIobDll = "libaio-iob.dll";
|
||||
detour::trampoline_try(libaioIobDll, "aioNMgrIob2_Create",
|
||||
aioNMgrIob2_Create, &aioNMgrIob2_Create_orig);
|
||||
detour::trampoline_try(libaioIobDll, "aioNMgrIob_BeginManage",
|
||||
aioNMgrIob_BeginManage, &aioNMgrIob_BeginManage_orig);
|
||||
detour::trampoline_try(libaioIobDll, "aioNCtlIob_GetNodeInfo",
|
||||
aioNCtlIob_GetNodeInfo, &aioNCtlIob_GetNodeInfo_orig);
|
||||
|
||||
// libaio.dll
|
||||
const auto libaioDll = "libaio.dll";
|
||||
detour::trampoline_try(libaioDll, "aioNodeMgr_Destroy",
|
||||
aioNodeMgr_Destroy, &aioNodeMgr_Destroy_orig);
|
||||
detour::trampoline_try(libaioDll, "aioNodeMgr_GetState",
|
||||
aioNodeMgr_GetState, &aioNodeMgr_GetState_orig);
|
||||
detour::trampoline_try(libaioDll, "aioNodeMgr_IsReady",
|
||||
aioNodeMgr_IsReady, &aioNodeMgr_IsReady_orig);
|
||||
detour::trampoline_try(libaioDll, "aioNodeMgr_IsError",
|
||||
aioNodeMgr_IsError, &aioNodeMgr_IsError_orig);
|
||||
detour::trampoline_try(libaioDll, "aioNodeCtl_Destroy",
|
||||
aioNodeCtl_Destroy, &aioNodeCtl_Destroy_orig);
|
||||
detour::trampoline_try(libaioDll, "aioNodeCtl_GetState",
|
||||
aioNodeCtl_GetState, &aioNodeCtl_GetState_orig);
|
||||
detour::trampoline_try(libaioDll, "aioNodeCtl_IsReady",
|
||||
aioNodeCtl_IsReady, &aioNodeCtl_IsReady_orig);
|
||||
detour::trampoline_try(libaioDll, "aioNodeCtl_IsError",
|
||||
aioNodeCtl_IsError, &aioNodeCtl_IsError_orig);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
namespace games::ccj {
|
||||
void bi2x_hook_init();
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
#include "ccj.h"
|
||||
|
||||
#include <format>
|
||||
#include "util/libutils.h"
|
||||
#include "util/fileutils.h"
|
||||
#include "util/unity_player.h"
|
||||
#include "util/utils.h"
|
||||
#include "util/detour.h"
|
||||
#include "acioemu/handle.h"
|
||||
#include "misc/wintouchemu.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "bi2x_hook.h"
|
||||
#include "trackball.h"
|
||||
|
||||
namespace games::ccj {
|
||||
// nomatchselect: disables debug menu that says "Select Match Mode"
|
||||
// q1: disables v-sync
|
||||
std::string CCJ_INJECT_ARGS = "-nomatchselect";
|
||||
|
||||
static acioemu::ACIOHandle *acioHandle = nullptr;
|
||||
static std::string commandLine;
|
||||
|
||||
static decltype(AddVectoredExceptionHandler) *AddVectoredExceptionHandler_orig = nullptr;
|
||||
static decltype(CreateFileW) *execexe_CreateFileW_orig = nullptr;
|
||||
static decltype(ShowCursor) *ShowCursor_orig = nullptr;
|
||||
static decltype(GetCommandLineA) *GetCommandLineA_orig = nullptr;
|
||||
|
||||
static HANDLE WINAPI execexe_CreateFileW_hook(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
|
||||
LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition,
|
||||
DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) {
|
||||
const auto fileName = ws2s(lpFileName);
|
||||
if (fileName == "COM1" && acioHandle->open(lpFileName)) {
|
||||
SetLastError(0);
|
||||
return (HANDLE) acioHandle;
|
||||
} else {
|
||||
return execexe_CreateFileW_orig(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes,
|
||||
dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
|
||||
}
|
||||
}
|
||||
|
||||
static int WINAPI ShowCursor_hook(BOOL bShow) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static PVOID WINAPI AddVectoredExceptionHandler_hook(ULONG First, PVECTORED_EXCEPTION_HANDLER Handler) {
|
||||
return INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
static LPSTR WINAPI GetCommandLineA_hook() {
|
||||
return (LPSTR)commandLine.c_str();
|
||||
}
|
||||
|
||||
void CCJGame::attach() {
|
||||
Game::attach();
|
||||
|
||||
// create required files
|
||||
fileutils::dir_create_recursive("dev/raw/log");
|
||||
fileutils::bin_write("dev/raw/bootio", nullptr, 0);
|
||||
fileutils::bin_write("dev/raw/log/output_log.txt", nullptr, 0);
|
||||
|
||||
// preload libraries
|
||||
libutils::load_library("execexe.dll");
|
||||
libutils::load_library("libaio.dll");
|
||||
libutils::load_library("libaio-iob.dll");
|
||||
libutils::load_library("libaio-iob2_video.dll");
|
||||
libutils::load_library("win10actlog.dll");
|
||||
|
||||
detour::trampoline_try("execexe.dll", MAKEINTRESOURCE(11),
|
||||
(void*)execexe_CreateFileW_hook,(void**)&execexe_CreateFileW_orig);
|
||||
|
||||
// insert BI2X hooks
|
||||
bi2x_hook_init();
|
||||
|
||||
// insert trackball hooks
|
||||
trackball_hook_init();
|
||||
|
||||
// add card reader
|
||||
acioHandle = new acioemu::ACIOHandle(L"COM1");
|
||||
devicehook_init_trampoline();
|
||||
devicehook_add(acioHandle);
|
||||
}
|
||||
|
||||
void CCJGame::post_attach() {
|
||||
Game::post_attach();
|
||||
|
||||
detour::trampoline_try("kernel32.dll", "AddVectoredExceptionHandler",
|
||||
(void*)AddVectoredExceptionHandler_hook,(void**)&AddVectoredExceptionHandler_orig);
|
||||
detour::trampoline_try("kernel32.dll", "GetCommandLineA",
|
||||
(void*)GetCommandLineA_hook, (void**)&GetCommandLineA_orig);
|
||||
|
||||
if (GRAPHICS_SHOW_CURSOR) {
|
||||
detour::trampoline_try("user32.dll", "ShowCursor",
|
||||
(void*)ShowCursor_hook, (void**)&ShowCursor_orig);
|
||||
}
|
||||
|
||||
commandLine =
|
||||
std::format("{} {}{}",
|
||||
GetCommandLineA_orig(),
|
||||
CCJ_INJECT_ARGS,
|
||||
unity_utils::get_unity_player_args());
|
||||
log_info("ccj", "unity player args: ```{}```", commandLine);
|
||||
|
||||
trackball_thread_start();
|
||||
}
|
||||
|
||||
void CCJGame::detach() {
|
||||
Game::detach();
|
||||
|
||||
devicehook_dispose();
|
||||
trackball_thread_stop();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "games/game.h"
|
||||
|
||||
namespace games::ccj {
|
||||
extern std::string CCJ_INJECT_ARGS;
|
||||
|
||||
class CCJGame : public games::Game {
|
||||
public:
|
||||
CCJGame() : Game("Chase Chase Jokers") {}
|
||||
|
||||
virtual void attach() override;
|
||||
virtual void post_attach() override;
|
||||
virtual void detach() override;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
#include "io.h"
|
||||
|
||||
std::vector<Button> &games::ccj::get_buttons() {
|
||||
static std::vector<Button> buttons;
|
||||
|
||||
if (buttons.empty()) {
|
||||
buttons = GameAPI::Buttons::getButtons("Chase Chase Jokers");
|
||||
|
||||
GameAPI::Buttons::sortButtons(
|
||||
&buttons,
|
||||
"Service",
|
||||
"Test",
|
||||
"Coin Mech",
|
||||
"Joystick Up",
|
||||
"Joystick Down",
|
||||
"Joystick Left",
|
||||
"Joystick Right",
|
||||
"Dash",
|
||||
"Action",
|
||||
"Jump",
|
||||
"Slide",
|
||||
"Special",
|
||||
"Headphones",
|
||||
"Trackball Up",
|
||||
"Trackball Down",
|
||||
"Trackball Left",
|
||||
"Trackball Right"
|
||||
);
|
||||
}
|
||||
|
||||
return buttons;
|
||||
}
|
||||
|
||||
std::string games::ccj::get_buttons_help() {
|
||||
// keep to max 100 characters wide
|
||||
return
|
||||
" Dash Action Jump Slide\n"
|
||||
" Joystick Trackball Special"
|
||||
;
|
||||
}
|
||||
|
||||
std::vector<Analog> &games::ccj::get_analogs() {
|
||||
static std::vector<Analog> analogs;
|
||||
|
||||
if (analogs.empty()) {
|
||||
analogs = GameAPI::Analogs::getAnalogs("Chase Chase Jokers");
|
||||
|
||||
GameAPI::Analogs::sortAnalogs(
|
||||
&analogs,
|
||||
"Joystick X",
|
||||
"Joystick Y",
|
||||
"Trackball DX",
|
||||
"Trackball DY"
|
||||
);
|
||||
}
|
||||
|
||||
return analogs;
|
||||
}
|
||||
|
||||
std::vector<Light> &games::ccj::get_lights() {
|
||||
static std::vector<Light> lights;
|
||||
|
||||
if (lights.empty()) {
|
||||
lights = GameAPI::Lights::getLights("Chase Chase Jokers");
|
||||
GameAPI::Lights::sortLights(
|
||||
&lights,
|
||||
"Title Panel R",
|
||||
"Title Panel G",
|
||||
"Title Panel B",
|
||||
"Side Panel R",
|
||||
"Side Panel G",
|
||||
"Side Panel B",
|
||||
"Card Reader R",
|
||||
"Card Reader G",
|
||||
"Card Reader B",
|
||||
"Special Button"
|
||||
);
|
||||
}
|
||||
|
||||
return lights;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "cfg/api.h"
|
||||
|
||||
namespace games::ccj {
|
||||
namespace Buttons {
|
||||
enum {
|
||||
Service,
|
||||
Test,
|
||||
CoinMech,
|
||||
Joystick_Up,
|
||||
Joystick_Down,
|
||||
Joystick_Left,
|
||||
Joystick_Right,
|
||||
Button_Dash,
|
||||
Button_Action,
|
||||
Button_Jump,
|
||||
Button_Slide,
|
||||
Button_Special,
|
||||
Headphones,
|
||||
Trackball_Up,
|
||||
Trackball_Down,
|
||||
Trackball_Left,
|
||||
Trackball_Right
|
||||
};
|
||||
}
|
||||
|
||||
namespace Analogs {
|
||||
enum {
|
||||
Joystick_X,
|
||||
Joystick_Y,
|
||||
Trackball_DX,
|
||||
Trackball_DY
|
||||
};
|
||||
}
|
||||
|
||||
// all lights in correct order
|
||||
namespace Lights {
|
||||
typedef enum {
|
||||
TitlePanel_R,
|
||||
TitlePanel_G,
|
||||
TitlePanel_B,
|
||||
SidePanel_R,
|
||||
SidePanel_G,
|
||||
SidePanel_B,
|
||||
CardReader_R,
|
||||
CardReader_G,
|
||||
CardReader_B,
|
||||
SpecialButton,
|
||||
} ccj_lights_t;
|
||||
}
|
||||
|
||||
// getters
|
||||
std::vector<Button> &get_buttons();
|
||||
std::string get_buttons_help();
|
||||
std::vector<Analog> &get_analogs();
|
||||
std::vector<Light> &get_lights();
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
#include "trackball.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "games/io.h"
|
||||
#include "util/utils.h"
|
||||
#include "io.h"
|
||||
|
||||
namespace games::ccj {
|
||||
bool MOUSE_TRACKBALL = false;
|
||||
bool MOUSE_TRACKBALL_USE_TOGGLE = false;
|
||||
uint8_t TRACKBALL_SENSITIVITY = 10;
|
||||
|
||||
static HANDLE fakeHandle = (HANDLE)0xDEADBEEF;
|
||||
static HWND hWnd = nullptr;
|
||||
static WNDPROC wndProc = nullptr;
|
||||
static std::thread *tbThread = nullptr;
|
||||
static bool tbThreadRunning = false;
|
||||
|
||||
static const wchar_t *fakeDeviceName = L"VID_1241&PID_1111";
|
||||
static const wchar_t *windowName = L"ChaseProject";
|
||||
|
||||
static decltype(GetRawInputDeviceList) *GetRawInputDeviceList_orig = nullptr;
|
||||
static decltype(GetRawInputDeviceInfoW) *GetRawInputDeviceInfoW_orig = nullptr;
|
||||
static decltype(SetWindowLongPtrW) *SetWindowLongPtrW_orig = nullptr;
|
||||
static decltype(GetRawInputData) *GetRawInputData_orig = nullptr;
|
||||
static decltype(RegisterRawInputDevices) *RegisterRawInputDevices_orig = nullptr;
|
||||
|
||||
static UINT WINAPI GetRawInputDeviceList_hook(PRAWINPUTDEVICELIST pRawInputDeviceList, PUINT puiNumDevices,
|
||||
UINT cbSize) {
|
||||
auto result = GetRawInputDeviceList_orig(pRawInputDeviceList, puiNumDevices, cbSize);
|
||||
if (result == 0xFFFFFFFF)
|
||||
return result;
|
||||
|
||||
if (pRawInputDeviceList == NULL) {
|
||||
(*puiNumDevices)++;
|
||||
} else if (result < *puiNumDevices) {
|
||||
pRawInputDeviceList[result] = {fakeHandle, 0};
|
||||
result++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static UINT WINAPI GetRawInputDeviceInfoW_hook(HANDLE hDevice, UINT uiCommand, LPVOID pData, PUINT pcbSize) {
|
||||
if (hDevice != fakeHandle || uiCommand != RIDI_DEVICENAME)
|
||||
return GetRawInputDeviceInfoW_orig(hDevice, uiCommand, pData, pcbSize);
|
||||
|
||||
const auto requiredLen = (wcslen(fakeDeviceName) + 1) * sizeof(wchar_t);
|
||||
|
||||
if (*pcbSize < requiredLen) {
|
||||
*pcbSize = requiredLen;
|
||||
return 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
if (pData == NULL) {
|
||||
*pcbSize = requiredLen;
|
||||
return 0;
|
||||
}
|
||||
|
||||
wcscpy((wchar_t*)pData, fakeDeviceName);
|
||||
return requiredLen;
|
||||
}
|
||||
|
||||
static LONG_PTR WINAPI SetWindowLongPtrW_hook(HWND _hWnd, int nIndex, LONG_PTR dwNewLong) {
|
||||
wchar_t buffer[256];
|
||||
if (nIndex != GWLP_WNDPROC || GetWindowTextW(_hWnd, buffer, 256) == 0 || !wcswcs(buffer, windowName))
|
||||
return SetWindowLongPtrW_orig(_hWnd, nIndex, dwNewLong);
|
||||
|
||||
hWnd = _hWnd;
|
||||
wndProc = (WNDPROC)dwNewLong;
|
||||
return SetWindowLongPtrW_orig(_hWnd, nIndex, dwNewLong);
|
||||
}
|
||||
|
||||
static UINT WINAPI GetRawInputData_hook(HRAWINPUT hRawInput, UINT uiCommand, LPVOID pData, PUINT pcbSize, UINT cbSizeHeader) {
|
||||
if (hRawInput != fakeHandle)
|
||||
return GetRawInputData_orig(hRawInput, uiCommand, pData, pcbSize, cbSizeHeader);
|
||||
|
||||
if (pData == NULL) {
|
||||
if (uiCommand == RID_HEADER)
|
||||
*pcbSize = sizeof(RAWINPUTHEADER);
|
||||
else
|
||||
*pcbSize = sizeof(RAWINPUT);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const RAWINPUTHEADER header = { RIM_TYPEMOUSE, sizeof(RAWINPUT), fakeHandle, 0 };
|
||||
|
||||
if (uiCommand == RID_HEADER) {
|
||||
if (*pcbSize < sizeof(RAWINPUTHEADER)) {
|
||||
SetLastError(ERROR_INSUFFICIENT_BUFFER);
|
||||
return 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
*((RAWINPUTHEADER*)pData) = header;
|
||||
return sizeof(RAWINPUTHEADER);
|
||||
} else if (uiCommand == RID_INPUT) {
|
||||
if (*pcbSize < sizeof(RAWINPUT)) {
|
||||
SetLastError(ERROR_INSUFFICIENT_BUFFER);
|
||||
return 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
RAWMOUSE rawMouse {};
|
||||
|
||||
if (MOUSE_TRACKBALL) {
|
||||
static bool active = false;
|
||||
static bool lastState = false;
|
||||
|
||||
static std::chrono::time_point<std::chrono::steady_clock> lastModified = std::chrono::steady_clock::now();
|
||||
static std::chrono::milliseconds debounceDuration(100);
|
||||
auto currentTime = std::chrono::steady_clock::now();
|
||||
bool pressed = get_async_secondary_mouse() & 0x8000;
|
||||
bool focused = GetForegroundWindow() == hWnd;
|
||||
|
||||
if (focused && MOUSE_TRACKBALL_USE_TOGGLE && pressed && (currentTime - lastModified > debounceDuration)) {
|
||||
active = !active;
|
||||
lastModified = currentTime;
|
||||
}
|
||||
|
||||
if (focused && ((MOUSE_TRACKBALL_USE_TOGGLE && active) || (!MOUSE_TRACKBALL_USE_TOGGLE && pressed))) {
|
||||
POINT cursor;
|
||||
RECT client;
|
||||
|
||||
GetClientRect(hWnd, &client);
|
||||
int sx = client.right - client.left;
|
||||
int sy = client.bottom - client.top;
|
||||
|
||||
GetCursorPos(&cursor);
|
||||
ScreenToClient(hWnd, &cursor);
|
||||
|
||||
static int lastX = cursor.x;
|
||||
static int lastY = cursor.y;
|
||||
|
||||
if (!lastState) {
|
||||
lastX = cursor.x;
|
||||
lastY = cursor.y;
|
||||
lastState = true;
|
||||
}
|
||||
|
||||
rawMouse.usFlags = MOUSE_MOVE_RELATIVE;
|
||||
rawMouse.lLastX = (int)((float)(cursor.x - lastX) * (float)TRACKBALL_SENSITIVITY / 20.0f);
|
||||
rawMouse.lLastY = (int)((float)(lastY - cursor.y) * (float)TRACKBALL_SENSITIVITY / 20.0f);
|
||||
|
||||
bool updateCursor = false;
|
||||
|
||||
if (cursor.x <= 0) {
|
||||
updateCursor = true;
|
||||
cursor.x = sx - 5;
|
||||
} else if (cursor.x >= sx - 1) {
|
||||
updateCursor = true;
|
||||
cursor.x = 5;
|
||||
}
|
||||
|
||||
if (cursor.y <= 0) {
|
||||
updateCursor = true;
|
||||
cursor.y = sy - 5;
|
||||
} else if (cursor.y >= sy - 1) {
|
||||
updateCursor = true;
|
||||
cursor.y = 5;
|
||||
}
|
||||
|
||||
lastX = cursor.x;
|
||||
lastY = cursor.y;
|
||||
|
||||
if (updateCursor) {
|
||||
ClientToScreen(hWnd, &cursor);
|
||||
SetCursorPos(cursor.x, cursor.y);
|
||||
}
|
||||
} else if (lastState && !active) {
|
||||
lastState = false;
|
||||
}
|
||||
} else {
|
||||
rawMouse.usFlags = MOUSE_MOVE_RELATIVE;
|
||||
|
||||
auto &analogs = get_analogs();
|
||||
if (analogs[Analogs::Trackball_DX].isSet() || analogs[Analogs::Trackball_DY].isSet()) {
|
||||
float x = GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::Trackball_DX]) * 2.0f - 1.0f;
|
||||
float y = GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::Trackball_DY]) * 2.0f - 1.0f;
|
||||
rawMouse.lLastX = (long) (x * (float) TRACKBALL_SENSITIVITY);
|
||||
rawMouse.lLastY = (long) (-y * (float) TRACKBALL_SENSITIVITY);
|
||||
}
|
||||
|
||||
auto &buttons = get_buttons();
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Trackball_Up]))
|
||||
rawMouse.lLastY = TRACKBALL_SENSITIVITY;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Trackball_Down]))
|
||||
rawMouse.lLastY = -TRACKBALL_SENSITIVITY;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Trackball_Left]))
|
||||
rawMouse.lLastX = -TRACKBALL_SENSITIVITY;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Trackball_Right]))
|
||||
rawMouse.lLastX = TRACKBALL_SENSITIVITY;
|
||||
}
|
||||
|
||||
*((RAWINPUT*)pData) = { header, { rawMouse } };
|
||||
return sizeof(RAWINPUT);
|
||||
}
|
||||
|
||||
return 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
static BOOL WINAPI RegisterRawInputDevices_hook(PCRAWINPUTDEVICE pRawInputDevices, UINT uiNumDevices, UINT cbSize) {
|
||||
if (uiNumDevices == 2 && pRawInputDevices[1].usUsage == HID_USAGE_GENERIC_GAMEPAD)
|
||||
uiNumDevices = 1;
|
||||
|
||||
return RegisterRawInputDevices_orig(pRawInputDevices, uiNumDevices, cbSize);
|
||||
}
|
||||
|
||||
|
||||
void trackball_hook_init() {
|
||||
// avoid double init
|
||||
static bool initialized = false;
|
||||
if (initialized) {
|
||||
return;
|
||||
} else {
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
// announce
|
||||
log_info("trackball", "init");
|
||||
|
||||
// user32
|
||||
const auto user32Dll = "user32.dll";
|
||||
detour::trampoline_try(user32Dll, "GetRawInputDeviceList",
|
||||
GetRawInputDeviceList_hook, &GetRawInputDeviceList_orig);
|
||||
detour::trampoline_try(user32Dll, "GetRawInputDeviceInfoW",
|
||||
GetRawInputDeviceInfoW_hook, &GetRawInputDeviceInfoW_orig);
|
||||
detour::trampoline_try(user32Dll, "SetWindowLongPtrW",
|
||||
SetWindowLongPtrW_hook, &SetWindowLongPtrW_orig);
|
||||
detour::trampoline_try(user32Dll, "GetRawInputData",
|
||||
GetRawInputData_hook, &GetRawInputData_orig);
|
||||
detour::trampoline_try(user32Dll, "RegisterRawInputDevices",
|
||||
RegisterRawInputDevices_hook, &RegisterRawInputDevices_orig);
|
||||
}
|
||||
|
||||
void trackball_thread_start() {
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
tbThreadRunning = true;
|
||||
|
||||
log_info("trackball", "thread start, use mouse: {}, toggle: {}", MOUSE_TRACKBALL, MOUSE_TRACKBALL_USE_TOGGLE);
|
||||
|
||||
tbThread = new std::thread([&] {
|
||||
while (tbThreadRunning) {
|
||||
if (hWnd && wndProc) {
|
||||
wndProc(hWnd, WM_INPUT, RIM_INPUT, (LPARAM)fakeHandle);
|
||||
}
|
||||
|
||||
if (!tbThreadRunning)
|
||||
break;
|
||||
|
||||
std::this_thread::sleep_for(10ms);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void trackball_thread_stop() {
|
||||
tbThreadRunning = false;
|
||||
if (tbThread)
|
||||
tbThread->join();
|
||||
|
||||
log_info("trackball", "thread stop");
|
||||
|
||||
delete tbThread;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace games::ccj {
|
||||
extern bool MOUSE_TRACKBALL;
|
||||
extern bool MOUSE_TRACKBALL_USE_TOGGLE;
|
||||
extern uint8_t TRACKBALL_SENSITIVITY;
|
||||
|
||||
void trackball_hook_init();
|
||||
void trackball_thread_start();
|
||||
void trackball_thread_stop();
|
||||
}
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "util/libutils.h"
|
||||
#include "util/fileutils.h"
|
||||
#include "util/detour.h"
|
||||
#include "cfg/configurator.h"
|
||||
|
||||
#include "io.h"
|
||||
|
||||
@@ -18,6 +19,8 @@
|
||||
#include "p3io/sate.h"
|
||||
#include "p3io/usbmem.h"
|
||||
|
||||
#include "p4io/p4io.h"
|
||||
|
||||
using namespace acioemu;
|
||||
|
||||
namespace games::ddr {
|
||||
@@ -25,6 +28,8 @@ namespace games::ddr {
|
||||
// settings
|
||||
bool SDMODE = false;
|
||||
|
||||
uint8_t DDR_TAPELEDS[TAPELED_DEVICE_COUNT][50][3] {};
|
||||
|
||||
static decltype(SendMessage) *SendMessage_real = nullptr;
|
||||
|
||||
static SHORT WINAPI GetAsyncKeyState_hook(int vKey) {
|
||||
@@ -47,6 +52,43 @@ namespace games::ddr {
|
||||
DDRGame::DDRGame() : Game("Dance Dance Revolution") {
|
||||
}
|
||||
|
||||
void DDRGame::pre_attach() {
|
||||
if (!cfg::CONFIGURATOR_STANDALONE && avs::game::is_model("TDX")) {
|
||||
log_fatal(
|
||||
"ddr",
|
||||
"BAD MODEL NAME ERROR\n\n\n"
|
||||
"!!! model name set to TDX, this is WRONG and will break your game !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! spice2x does not yet support TDX, use MDX instead. !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! model name set to TDX, this is WRONG and will break your game !!!\n\n\n"
|
||||
);
|
||||
}
|
||||
|
||||
if (!cfg::CONFIGURATOR_STANDALONE && avs::game::DEST[0] == 'U') {
|
||||
log_warning(
|
||||
"ddr",
|
||||
"U-REGION WARNING\n\n\n"
|
||||
"!!! !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! <dest> is set to U region !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! While this is legal, unless you have compatible data, this !!!\n"
|
||||
"!!! will most likely crash your game or fail to boot. !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! It is recommended that you stick with J region. !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! !!!\n\n\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void DDRGame::attach() {
|
||||
Game::attach();
|
||||
|
||||
@@ -60,6 +102,8 @@ namespace games::ddr {
|
||||
// add fake devices
|
||||
if (avs::game::DLL_NAME == "arkmdxbio2.dll") {
|
||||
devicehook_add(new acioemu::ACIOHandle(L"COM1"));
|
||||
} else if(avs::game::DLL_NAME == "arkmdxp4.dll") {
|
||||
devicehook_add(new DDRP4IOHandle());
|
||||
} else {
|
||||
devicehook_add(new DDRFOOTHandle());
|
||||
devicehook_add(new DDRSATEHandle());
|
||||
@@ -89,6 +133,17 @@ namespace games::ddr {
|
||||
memcpy(settings2.property_devicedesc, settings_property, strlen(settings_property) + 1);
|
||||
memcpy(settings2.interface_detail, settings_detail, sizeof(settings_detail));
|
||||
|
||||
const char settings_detail_p4io[] = R"(\\.\P4IO)";
|
||||
|
||||
// settings p4io
|
||||
SETUPAPI_SETTINGS settingsp4io {};
|
||||
settingsp4io.class_guid[0] = 0x8B7250A5;
|
||||
settingsp4io.class_guid[1] = 0x46C94F61;
|
||||
settingsp4io.class_guid[2] = 0x68E63A84;
|
||||
settingsp4io.class_guid[3] = 0x206A4706;
|
||||
memcpy(settingsp4io.property_devicedesc, settings_property, strlen(settings_property) + 1);
|
||||
memcpy(settingsp4io.interface_detail, settings_detail_p4io, sizeof(settings_detail_p4io));
|
||||
|
||||
// init SETUP API
|
||||
setupapihook_init(avs::game::DLL_INSTANCE);
|
||||
|
||||
@@ -100,6 +155,7 @@ namespace games::ddr {
|
||||
// add settings
|
||||
setupapihook_add(settings1);
|
||||
setupapihook_add(settings2);
|
||||
setupapihook_add(settingsp4io);
|
||||
|
||||
// misc hooks
|
||||
detour::iat_try("GetAsyncKeyState", GetAsyncKeyState_hook, avs::game::DLL_INSTANCE);
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "games/game.h"
|
||||
#include <cstdint>
|
||||
|
||||
namespace games::ddr {
|
||||
|
||||
// settings
|
||||
extern bool SDMODE;
|
||||
|
||||
// Buffers to store RGB data for tape LEDs on gold cabinets
|
||||
const size_t TAPELED_DEVICE_COUNT = 11;
|
||||
extern uint8_t DDR_TAPELEDS[TAPELED_DEVICE_COUNT][50][3];
|
||||
|
||||
class DDRGame : public games::Game {
|
||||
public:
|
||||
DDRGame();
|
||||
virtual void pre_attach() override;
|
||||
virtual void attach() override;
|
||||
virtual void detach() override;
|
||||
};
|
||||
|
||||
+92
-1
@@ -79,7 +79,98 @@ std::vector<Light> &games::ddr::get_lights() {
|
||||
"HD P2 Speaker F B",
|
||||
"HD P2 Speaker W R",
|
||||
"HD P2 Speaker W G",
|
||||
"HD P2 Speaker W B"
|
||||
"HD P2 Speaker W B",
|
||||
|
||||
"WHITE Speaker Top R",
|
||||
"WHITE Speaker Top G",
|
||||
"WHITE Speaker Top B",
|
||||
"WHITE Speaker Bottom R",
|
||||
"WHITE Speaker Bottom G",
|
||||
"WHITE Speaker Bottom B",
|
||||
"WHITE Woofer R",
|
||||
"WHITE Woofer G",
|
||||
"WHITE Woofer B",
|
||||
|
||||
"GOLD P1 Menu Start",
|
||||
"GOLD P1 Menu Up",
|
||||
"GOLD P1 Menu Down",
|
||||
"GOLD P1 Menu Left",
|
||||
"GOLD P1 Menu Right",
|
||||
|
||||
"GOLD P2 Menu Start",
|
||||
"GOLD P2 Menu Up",
|
||||
"GOLD P2 Menu Down",
|
||||
"GOLD P2 Menu Left",
|
||||
"GOLD P2 Menu Right",
|
||||
|
||||
"GOLD Title Panel Left",
|
||||
"GOLD Title Panel Center",
|
||||
"GOLD Title Panel Right",
|
||||
|
||||
"GOLD P1 Woofer Corner",
|
||||
"GOLD P2 Woofer Corner",
|
||||
|
||||
"GOLD P1 Card Unit R",
|
||||
"GOLD P1 Card Unit G",
|
||||
"GOLD P1 Card Unit B",
|
||||
|
||||
"GOLD P2 Card Unit R",
|
||||
"GOLD P2 Card Unit G",
|
||||
"GOLD P2 Card Unit B",
|
||||
|
||||
"GOLD Top Panel Avg R",
|
||||
"GOLD Top Panel Avg G",
|
||||
"GOLD Top Panel Avg B",
|
||||
|
||||
"GOLD Monitor Side Left Avg R",
|
||||
"GOLD Monitor Side Left Avg G",
|
||||
"GOLD Monitor Side Left Avg B",
|
||||
|
||||
"GOLD Monitor Side Right Avg R",
|
||||
"GOLD Monitor Side Right Avg G",
|
||||
"GOLD Monitor Side Right Avg B",
|
||||
|
||||
"GOLD P1 Foot Up Avg R",
|
||||
"GOLD P1 Foot Up Avg G",
|
||||
"GOLD P1 Foot Up Avg B",
|
||||
|
||||
"GOLD P1 Foot Down Avg R",
|
||||
"GOLD P1 Foot Down Avg G",
|
||||
"GOLD P1 Foot Down Avg B",
|
||||
|
||||
"GOLD P1 Foot Left Avg R",
|
||||
"GOLD P1 Foot Left Avg G",
|
||||
"GOLD P1 Foot Left Avg B",
|
||||
|
||||
"GOLD P1 Foot Right Avg R",
|
||||
"GOLD P1 Foot Right Avg G",
|
||||
"GOLD P1 Foot Right Avg B",
|
||||
|
||||
"GOLD P2 Foot Up Avg R",
|
||||
"GOLD P2 Foot Up Avg G",
|
||||
"GOLD P2 Foot Up Avg B",
|
||||
|
||||
"GOLD P2 Foot Down Avg R",
|
||||
"GOLD P2 Foot Down Avg G",
|
||||
"GOLD P2 Foot Down Avg B",
|
||||
|
||||
"GOLD P2 Foot Left Avg R",
|
||||
"GOLD P2 Foot Left Avg G",
|
||||
"GOLD P2 Foot Left Avg B",
|
||||
|
||||
"GOLD P2 Foot Right Avg R",
|
||||
"GOLD P2 Foot Right Avg G",
|
||||
"GOLD P2 Foot Right Avg B",
|
||||
|
||||
"GOLD P1 Stage Corner Up-Left",
|
||||
"GOLD P1 Stage Corner Up-Right",
|
||||
"GOLD P1 Stage Corner Down-Left",
|
||||
"GOLD P1 Stage Corner Down-Right",
|
||||
|
||||
"GOLD P2 Stage Corner Up-Left",
|
||||
"GOLD P2 Stage Corner Up-Right",
|
||||
"GOLD P2 Stage Corner Down-Left",
|
||||
"GOLD P2 Stage Corner Down-Right"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -40,39 +40,138 @@ namespace games::ddr {
|
||||
P1_FOOT_UP,
|
||||
P1_FOOT_RIGHT,
|
||||
P1_FOOT_DOWN,
|
||||
|
||||
P2_FOOT_LEFT,
|
||||
P2_FOOT_UP,
|
||||
P2_FOOT_RIGHT,
|
||||
P2_FOOT_DOWN,
|
||||
|
||||
SPOT_RED,
|
||||
SPOT_BLUE,
|
||||
TOP_SPOT_RED,
|
||||
TOP_SPOT_BLUE,
|
||||
|
||||
P1_HALOGEN_UPPER,
|
||||
P1_HALOGEN_LOWER,
|
||||
P2_HALOGEN_UPPER,
|
||||
P2_HALOGEN_LOWER,
|
||||
|
||||
P1_BUTTON,
|
||||
P2_BUTTON,
|
||||
NEON,
|
||||
|
||||
HD_P1_START,
|
||||
HD_P1_LEFT_RIGHT,
|
||||
HD_P1_UP_DOWN,
|
||||
|
||||
HD_P2_START,
|
||||
HD_P2_LEFT_RIGHT,
|
||||
HD_P2_UP_DOWN,
|
||||
|
||||
HD_P1_SPEAKER_F_R,
|
||||
HD_P1_SPEAKER_F_G,
|
||||
HD_P1_SPEAKER_F_B,
|
||||
HD_P1_SPEAKER_W_R,
|
||||
HD_P1_SPEAKER_W_G,
|
||||
HD_P1_SPEAKER_W_B,
|
||||
|
||||
HD_P2_SPEAKER_F_R,
|
||||
HD_P2_SPEAKER_F_G,
|
||||
HD_P2_SPEAKER_F_B,
|
||||
HD_P2_SPEAKER_W_R,
|
||||
HD_P2_SPEAKER_W_G,
|
||||
HD_P2_SPEAKER_W_B,
|
||||
|
||||
WHITE_SPEAKER_TOP_R,
|
||||
WHITE_SPEAKER_TOP_G,
|
||||
WHITE_SPEAKER_TOP_B,
|
||||
WHITE_SPEAKER_BOTTOM_R,
|
||||
WHITE_SPEAKER_BOTTOM_G,
|
||||
WHITE_SPEAKER_BOTTOM_B,
|
||||
WHITE_WOOFER_R,
|
||||
WHITE_WOOFER_G,
|
||||
WHITE_WOOFER_B,
|
||||
|
||||
GOLD_P1_MENU_START,
|
||||
GOLD_P1_MENU_UP,
|
||||
GOLD_P1_MENU_DOWN,
|
||||
GOLD_P1_MENU_LEFT,
|
||||
GOLD_P1_MENU_RIGHT,
|
||||
|
||||
GOLD_P2_MENU_START,
|
||||
GOLD_P2_MENU_UP,
|
||||
GOLD_P2_MENU_DOWN,
|
||||
GOLD_P2_MENU_LEFT,
|
||||
GOLD_P2_MENU_RIGHT,
|
||||
|
||||
GOLD_TITLE_PANEL_LEFT,
|
||||
GOLD_TITLE_PANEL_CENTER,
|
||||
GOLD_TITLE_PANEL_RIGHT,
|
||||
|
||||
GOLD_P1_WOOFER_CORNER,
|
||||
GOLD_P2_WOOFER_CORNER,
|
||||
|
||||
GOLD_P1_CARD_UNIT_R,
|
||||
GOLD_P1_CARD_UNIT_G,
|
||||
GOLD_P1_CARD_UNIT_B,
|
||||
|
||||
GOLD_P2_CARD_UNIT_R,
|
||||
GOLD_P2_CARD_UNIT_G,
|
||||
GOLD_P2_CARD_UNIT_B,
|
||||
|
||||
GOLD_TOP_PANEL_AVG_R,
|
||||
GOLD_TOP_PANEL_AVG_G,
|
||||
GOLD_TOP_PANEL_AVG_B,
|
||||
|
||||
GOLD_MONITOR_SIDE_LEFT_AVG_R,
|
||||
GOLD_MONITOR_SIDE_LEFT_AVG_G,
|
||||
GOLD_MONITOR_SIDE_LEFT_AVG_B,
|
||||
|
||||
GOLD_MONITOR_SIDE_RIGHT_AVG_R,
|
||||
GOLD_MONITOR_SIDE_RIGHT_AVG_G,
|
||||
GOLD_MONITOR_SIDE_RIGHT_AVG_B,
|
||||
|
||||
GOLD_P1_FOOT_UP_AVG_R,
|
||||
GOLD_P1_FOOT_UP_AVG_G,
|
||||
GOLD_P1_FOOT_UP_AVG_B,
|
||||
|
||||
GOLD_P1_FOOT_DOWN_AVG_R,
|
||||
GOLD_P1_FOOT_DOWN_AVG_G,
|
||||
GOLD_P1_FOOT_DOWN_AVG_B,
|
||||
|
||||
GOLD_P1_FOOT_LEFT_AVG_R,
|
||||
GOLD_P1_FOOT_LEFT_AVG_G,
|
||||
GOLD_P1_FOOT_LEFT_AVG_B,
|
||||
|
||||
GOLD_P1_FOOT_RIGHT_AVG_R,
|
||||
GOLD_P1_FOOT_RIGHT_AVG_G,
|
||||
GOLD_P1_FOOT_RIGHT_AVG_B,
|
||||
|
||||
GOLD_P2_FOOT_UP_AVG_R,
|
||||
GOLD_P2_FOOT_UP_AVG_G,
|
||||
GOLD_P2_FOOT_UP_AVG_B,
|
||||
|
||||
GOLD_P2_FOOT_DOWN_AVG_R,
|
||||
GOLD_P2_FOOT_DOWN_AVG_G,
|
||||
GOLD_P2_FOOT_DOWN_AVG_B,
|
||||
|
||||
GOLD_P2_FOOT_LEFT_AVG_R,
|
||||
GOLD_P2_FOOT_LEFT_AVG_G,
|
||||
GOLD_P2_FOOT_LEFT_AVG_B,
|
||||
|
||||
GOLD_P2_FOOT_RIGHT_AVG_R,
|
||||
GOLD_P2_FOOT_RIGHT_AVG_G,
|
||||
GOLD_P2_FOOT_RIGHT_AVG_B,
|
||||
|
||||
GOLD_P1_STAGE_UP_LEFT,
|
||||
GOLD_P1_STAGE_UP_RIGHT,
|
||||
GOLD_P1_STAGE_DOWN_LEFT,
|
||||
GOLD_P1_STAGE_DOWN_RIGHT,
|
||||
|
||||
GOLD_P2_STAGE_UP_LEFT,
|
||||
GOLD_P2_STAGE_UP_RIGHT,
|
||||
GOLD_P2_STAGE_DOWN_LEFT,
|
||||
GOLD_P2_STAGE_DOWN_RIGHT,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,352 @@
|
||||
#include "p4io.h"
|
||||
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
#include "../io.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#ifndef FILE_DEVICE_UNKNOWN
|
||||
#define FILE_DEVICE_UNKNOWN 0x00000022
|
||||
#endif
|
||||
|
||||
// Packet header seems to be
|
||||
// AA cmdId Seq maybe_length?
|
||||
|
||||
namespace {
|
||||
#pragma pack(push, 1)
|
||||
enum P4IOIoCtl : uint16_t {
|
||||
P4IO_IOCTL_GET_INPUTS = 0x801,
|
||||
P4IO_IOCTL_GET_VERSION_FUNC = 0x803,
|
||||
};
|
||||
|
||||
enum class P4IOCmd : uint8_t {
|
||||
Nop = 0x00,
|
||||
GetTypeVerProd = 0x01,
|
||||
UpdateLights = 0x12,
|
||||
CoinHandler = 0x18,
|
||||
Unk_1C = 0x1C,
|
||||
IoSciOpen = 0x20,
|
||||
SciTransfer = 0x21,
|
||||
IoSciClose = 0x24,
|
||||
ReadSecPlug_01 = 0x40,
|
||||
ReadSecPlug_02 = 0x41,
|
||||
};
|
||||
|
||||
struct P4IOHeader {
|
||||
uint8_t magic;
|
||||
P4IOCmd cmd;
|
||||
uint8_t seq;
|
||||
uint8_t length;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
const uint8_t blackPlugData[0x20] = {
|
||||
0xC9, 0xFE, 0xC1, 0x5B, 0x8D, 0x8A, 0xE7, 0xA8,
|
||||
0x92, 0xB8, 0x1A, 0x86, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77
|
||||
};
|
||||
|
||||
const uint8_t whitePlugData[0x20] = {
|
||||
0x18, 0x98, 0x12, 0x71, 0xB3, 0xA2, 0x20, 0x08,
|
||||
0x82, 0x20, 0x08, 0x82, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDA
|
||||
};
|
||||
}
|
||||
|
||||
namespace games::ddr {
|
||||
bool DDRP4IOHandle::open(LPCWSTR lpFileName) {
|
||||
if (wcscmp(lpFileName, L"\\\\.\\P4IO\\p4io") != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_acio_emu) {
|
||||
m_acio_emu = new acioemu::ACIOEmu();
|
||||
m_acio_emu->add_device(new acioemu::ICCADevice(false, true, 2));
|
||||
m_acio_emu->add_device(new HDXSDevice());
|
||||
}
|
||||
|
||||
log_info("ddr", "Opened P4IO");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int DDRP4IOHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) {
|
||||
DWORD transferSize = std::min(nNumberOfBytesToRead, (DWORD)m_read_buf.size());
|
||||
memcpy(lpBuffer, m_read_buf.data(), transferSize);
|
||||
m_read_buf.erase(m_read_buf.begin(), m_read_buf.begin()+transferSize);
|
||||
|
||||
return transferSize;
|
||||
}
|
||||
|
||||
int DDRP4IOHandle::write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) {
|
||||
const P4IOHeader *header = reinterpret_cast<const P4IOHeader*>(lpBuffer);
|
||||
const uint8_t *data = reinterpret_cast<const uint8_t*>(reinterpret_cast<const uint8_t*>(lpBuffer)+sizeof(P4IOHeader));
|
||||
|
||||
if(nNumberOfBytesToWrite < sizeof(P4IOHeader)
|
||||
|| header->magic != 0xAA
|
||||
|| nNumberOfBytesToWrite < sizeof(P4IOHeader)+header->length)
|
||||
{
|
||||
log_warning("ddr", "p4io has the wrong header: {}", bin2hex(reinterpret_cast<const uint8_t*>(lpBuffer),
|
||||
(int)nNumberOfBytesToWrite));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> responseBuffer(64);
|
||||
P4IOHeader *respHeader = reinterpret_cast<P4IOHeader*>(responseBuffer.data());
|
||||
respHeader->magic = 0xAA;
|
||||
respHeader->cmd = header->cmd;
|
||||
respHeader->seq = header->seq;
|
||||
uint8_t *respData = responseBuffer.data()+sizeof(P4IOHeader);
|
||||
|
||||
switch(header->cmd)
|
||||
{
|
||||
case P4IOCmd::Nop:
|
||||
case P4IOCmd::Unk_1C:
|
||||
case P4IOCmd::IoSciOpen:
|
||||
case P4IOCmd::IoSciClose:
|
||||
{
|
||||
m_read_buf.insert(m_read_buf.end(), responseBuffer.begin(), responseBuffer.end());
|
||||
return nNumberOfBytesToWrite;
|
||||
}
|
||||
break;
|
||||
case P4IOCmd::GetTypeVerProd: {
|
||||
respHeader->length = 0x2C;
|
||||
|
||||
// type
|
||||
memcpy(&respData[0], "JDX", 4);
|
||||
|
||||
// version
|
||||
respData[5] = 69;
|
||||
respData[6] = 69;
|
||||
respData[7] = 69;
|
||||
|
||||
// prod
|
||||
memcpy(&respData[8], "prod", 4);
|
||||
|
||||
// date
|
||||
memcpy(&respData[0xC], "2024-03-12", 10);
|
||||
|
||||
// time
|
||||
memcpy(&respData[0x1C], "06:39:21", 8);
|
||||
|
||||
m_read_buf_mutex.lock();
|
||||
m_read_buf.insert(m_read_buf.end(), responseBuffer.begin(), responseBuffer.end());
|
||||
m_read_buf_mutex.unlock();
|
||||
return nNumberOfBytesToWrite;
|
||||
}
|
||||
break;
|
||||
case P4IOCmd::UpdateLights: {
|
||||
static const size_t lightMapping[] = {
|
||||
Lights::WHITE_SPEAKER_TOP_G,
|
||||
Lights::WHITE_SPEAKER_TOP_R,
|
||||
Lights::WHITE_SPEAKER_TOP_B,
|
||||
Lights::WHITE_SPEAKER_BOTTOM_G,
|
||||
Lights::WHITE_SPEAKER_BOTTOM_R,
|
||||
Lights::WHITE_SPEAKER_BOTTOM_B,
|
||||
Lights::WHITE_WOOFER_G,
|
||||
Lights::WHITE_WOOFER_R,
|
||||
Lights::WHITE_WOOFER_B,
|
||||
Lights::HD_P1_START,
|
||||
Lights::HD_P1_UP_DOWN,
|
||||
Lights::HD_P1_LEFT_RIGHT
|
||||
};
|
||||
|
||||
auto &lights = get_lights();
|
||||
|
||||
for(size_t i = 0; i < std::size(lightMapping); ++i) {
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[lightMapping[i]], data[i] == 0 ? 0.f : 1.f);
|
||||
}
|
||||
|
||||
RI_MGR->devices_flush_output();
|
||||
|
||||
m_read_buf_mutex.lock();
|
||||
m_read_buf.insert(m_read_buf.end(), responseBuffer.begin(), responseBuffer.end());
|
||||
m_read_buf_mutex.unlock();
|
||||
return nNumberOfBytesToWrite;
|
||||
}
|
||||
break;
|
||||
case P4IOCmd::CoinHandler: {
|
||||
respHeader->length = 0x04;
|
||||
|
||||
// TODO:
|
||||
m_read_buf_mutex.lock();
|
||||
m_read_buf.insert(m_read_buf.end(), responseBuffer.begin(), responseBuffer.end());
|
||||
m_read_buf_mutex.unlock();
|
||||
return nNumberOfBytesToWrite;
|
||||
}
|
||||
break;
|
||||
case P4IOCmd::SciTransfer: {
|
||||
// request is 1 byte port #, followed by data
|
||||
|
||||
if(header->length > 1 && data[0] == 0) {
|
||||
for(size_t i = 0; i < header->length-1u; ++i) {
|
||||
m_acio_emu->write(data[1+i]);
|
||||
}
|
||||
}
|
||||
|
||||
respHeader->length = 2;
|
||||
|
||||
//response is 1 byte port #, 1 byte error flag, followed by data
|
||||
if(header->length >= 1 && data[0] == 0) {
|
||||
uint8_t readBytes = 0;
|
||||
|
||||
while(readBytes < 58) {
|
||||
const auto b = m_acio_emu->read();
|
||||
|
||||
if(!b.has_value()) {
|
||||
break;
|
||||
}
|
||||
|
||||
respData[2+readBytes] = b.value();
|
||||
++readBytes;
|
||||
}
|
||||
|
||||
respHeader->length = 2+readBytes;
|
||||
}
|
||||
|
||||
m_read_buf_mutex.lock();
|
||||
m_read_buf.insert(m_read_buf.end(), responseBuffer.begin(), responseBuffer.end());
|
||||
m_read_buf_mutex.unlock();
|
||||
return nNumberOfBytesToWrite;
|
||||
}
|
||||
break;
|
||||
case P4IOCmd::ReadSecPlug_01: {
|
||||
// maybe just a presense check
|
||||
|
||||
respHeader->length = 0x09;
|
||||
memset(&respData[1], 0xAA, 8);
|
||||
|
||||
m_read_buf_mutex.lock();
|
||||
m_read_buf.insert(m_read_buf.end(), responseBuffer.begin(), responseBuffer.end());
|
||||
m_read_buf_mutex.unlock();
|
||||
return nNumberOfBytesToWrite;
|
||||
}
|
||||
break;
|
||||
case P4IOCmd::ReadSecPlug_02: {
|
||||
respHeader->length = 0x21;
|
||||
|
||||
if(data[0] == 0) {
|
||||
memcpy(&respData[1], blackPlugData, sizeof(blackPlugData));
|
||||
} else {
|
||||
memcpy(&respData[1], whitePlugData, sizeof(whitePlugData));
|
||||
}
|
||||
|
||||
m_read_buf_mutex.lock();
|
||||
m_read_buf.insert(m_read_buf.end(), responseBuffer.begin(), responseBuffer.end());
|
||||
m_read_buf_mutex.unlock();
|
||||
return nNumberOfBytesToWrite;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int DDRP4IOHandle::device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer,
|
||||
DWORD nOutBufferSize) {
|
||||
if(((dwIoControlCode>>16)&0xFFFF) == FILE_DEVICE_UNKNOWN) {
|
||||
switch((dwIoControlCode>>2)&0x3FFF) {
|
||||
case P4IO_IOCTL_GET_INPUTS: {
|
||||
memset(lpOutBuffer, 0, 16);
|
||||
auto controls = (uint32_t*) lpOutBuffer;
|
||||
|
||||
const std::pair<size_t, uint8_t> buttonMapping[] =
|
||||
{
|
||||
{Buttons::SERVICE, 25}, //Good
|
||||
{Buttons::TEST, 28}, //Good
|
||||
{Buttons::COIN_MECH, 24}, //Good
|
||||
{Buttons::P1_START, 0}, //Good
|
||||
{Buttons::P1_PANEL_UP, 5}, //Good
|
||||
{Buttons::P1_PANEL_DOWN, 6}, //Good
|
||||
{Buttons::P1_PANEL_LEFT, 7}, //Goood
|
||||
{Buttons::P1_PANEL_RIGHT, 16}, //Good
|
||||
{Buttons::P1_MENU_UP, 1}, //Good
|
||||
{Buttons::P1_MENU_DOWN, 2}, //Good
|
||||
{Buttons::P1_MENU_LEFT, 3}, //Good
|
||||
{Buttons::P1_MENU_RIGHT, 4}, //Good
|
||||
{Buttons::P2_START, 8}, //Good
|
||||
{Buttons::P2_PANEL_UP, 13}, //Good
|
||||
{Buttons::P2_PANEL_DOWN, 14}, //Good
|
||||
{Buttons::P2_PANEL_LEFT, 15}, //Good
|
||||
{Buttons::P2_PANEL_RIGHT, 20}, //Good
|
||||
{Buttons::P2_MENU_UP, 9}, //Good
|
||||
{Buttons::P2_MENU_DOWN, 10}, //Good
|
||||
{Buttons::P2_MENU_LEFT, 11}, //Good
|
||||
{Buttons::P2_MENU_RIGHT, 12}, //Good
|
||||
};
|
||||
|
||||
auto &buttons = get_buttons();
|
||||
for(const auto &mapping : buttonMapping)
|
||||
{
|
||||
if(GameAPI::Buttons::getState(RI_MGR, buttons[mapping.first]))
|
||||
{
|
||||
controls[mapping.second/32] |= 1<<(mapping.second%32);
|
||||
}
|
||||
}
|
||||
|
||||
return 16;
|
||||
}
|
||||
break;
|
||||
case P4IO_IOCTL_GET_VERSION_FUNC: {
|
||||
if(nOutBufferSize > strlen("spicy p4io") + 1) {
|
||||
strcpy((char*)lpOutBuffer, "spicy p4io");
|
||||
return 11;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool DDRP4IOHandle::close() {
|
||||
log_info("ddr", "Closed P4IO");
|
||||
return true;
|
||||
}
|
||||
|
||||
DDRP4IOHandle::HDXSDevice::HDXSDevice() {
|
||||
this->node_count = 1;
|
||||
}
|
||||
|
||||
bool DDRP4IOHandle::HDXSDevice::parse_msg(
|
||||
acioemu::MessageData* msg_in,
|
||||
circular_buffer<uint8_t> *response_buffer) {
|
||||
switch(msg_in->cmd.code)
|
||||
{
|
||||
case acioemu::ACIO_CMD_GET_VERSION: {
|
||||
auto msg = this->create_msg(msg_in, acioemu::MSG_VERSION_SIZE);
|
||||
this->set_version(msg, 0x204, 0, 1, 6, 0, "HDXS");
|
||||
write_msg(msg, response_buffer);
|
||||
delete msg;
|
||||
}
|
||||
break;
|
||||
case acioemu::ACIO_CMD_KEEPALIVE: {
|
||||
auto msg = this->create_msg(msg_in, 0);
|
||||
write_msg(msg, response_buffer);
|
||||
delete msg;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case acioemu::ACIO_CMD_STARTUP:
|
||||
case acioemu::ACIO_CMD_CLEAR:
|
||||
case 0x110:
|
||||
case 0x128:
|
||||
{
|
||||
auto msg = this->create_msg_status(msg_in, 0x00);
|
||||
write_msg(msg, response_buffer);
|
||||
delete msg;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
log_info("ddr", "HDXS unhandled {:03X}", msg_in->cmd.code);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include "acioemu/acioemu.h"
|
||||
#include "hooks/devicehook.h"
|
||||
#include <mutex>
|
||||
|
||||
namespace games::ddr {
|
||||
class DDRP4IOHandle : public CustomHandle {
|
||||
private:
|
||||
acioemu::ACIOEmu *m_acio_emu;
|
||||
std::vector<uint8_t> m_read_buf;
|
||||
std::mutex m_read_buf_mutex;
|
||||
|
||||
class HDXSDevice : public acioemu::ACIODeviceEmu {
|
||||
public:
|
||||
HDXSDevice();
|
||||
|
||||
bool parse_msg(
|
||||
acioemu::MessageData* msg_in,
|
||||
circular_buffer<uint8_t> *response_buffer
|
||||
) override;
|
||||
};
|
||||
|
||||
public:
|
||||
bool open(LPCWSTR lpFileName) override;
|
||||
|
||||
int read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) override;
|
||||
|
||||
int write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) override;
|
||||
|
||||
int device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer,
|
||||
DWORD nOutBufferSize) override;
|
||||
|
||||
bool close() override;
|
||||
};
|
||||
}
|
||||
+52
-140
@@ -8,7 +8,6 @@
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/memutils.h"
|
||||
#include "misc/vrutil.h"
|
||||
|
||||
#pragma pack(push)
|
||||
|
||||
@@ -148,15 +147,11 @@ namespace games::drs {
|
||||
return 1;
|
||||
}
|
||||
|
||||
char DRS_TAPELED[38 * 49][3] {};
|
||||
bool VR_STARTED = false;
|
||||
linalg::aliases::float3 VR_SCALE(100, 100, 1.f);
|
||||
linalg::aliases::float3 VR_OFFSET(0.f, 0.f, -0.1f);
|
||||
float VR_ROTATION = 0.f;
|
||||
VRFoot VR_FOOTS[2] {
|
||||
{1},
|
||||
{2},
|
||||
};
|
||||
char DRS_TAPELED[DRS_TAPELED_COLS_TOTAL][3] {};
|
||||
bool DISABLE_TOUCH = false;
|
||||
bool TRANSPOSE_TOUCH = false;
|
||||
|
||||
std::vector<TouchEvent> TOUCH_EVENTS;
|
||||
|
||||
inline DWORD scale_double_to_xy(double val) {
|
||||
return static_cast<DWORD>(val * 32768);
|
||||
@@ -221,138 +216,54 @@ namespace games::drs {
|
||||
touch_callback(&dev, game_touches.get(), (int) event_count, 0, user_data);
|
||||
}
|
||||
|
||||
uint32_t VRFoot::get_index() {
|
||||
if (index == ~0u) {
|
||||
if (id == 1) return vrutil::INDEX_LEFT;
|
||||
if (id == 2) return vrutil::INDEX_RIGHT;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
void start_touch() {
|
||||
std::thread t([] {
|
||||
log_info("drs", "starting touch input thread");
|
||||
|
||||
linalg::aliases::float3 VRFoot::to_world(linalg::aliases::float3 pos) {
|
||||
pos = linalg::aliases::float3(-pos.z, pos.x, pos.y);
|
||||
const float deg_to_rad = (float) (1 / 180.f * M_PI);
|
||||
auto s = sinf(VR_ROTATION * deg_to_rad);
|
||||
auto c = cosf(VR_ROTATION * deg_to_rad);
|
||||
pos = linalg::aliases::float3(pos.x * c - pos.y * s,
|
||||
pos.x * s + pos.y * c,
|
||||
pos.z);
|
||||
return pos * VR_SCALE + VR_OFFSET;
|
||||
}
|
||||
// main loop
|
||||
while (TRUE) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
|
||||
void start_vr() {
|
||||
if (vrutil::STATUS != vrutil::VRStatus::Running) return;
|
||||
if (!VR_STARTED) {
|
||||
VR_STARTED = true;
|
||||
std::thread t([] {
|
||||
log_info("drs", "starting VR thread");
|
||||
|
||||
// dance floor plane
|
||||
const linalg::aliases::float3 plane_normal(0, 0, 1);
|
||||
const linalg::aliases::float3 plane_point(0, 0, 0);
|
||||
const float touch_width = 1.f;
|
||||
const float touch_height = 1.f;
|
||||
|
||||
// main loop
|
||||
while (vrutil::STATUS == vrutil::VRStatus::Running) {
|
||||
|
||||
// iterate foots
|
||||
for (auto &foot : VR_FOOTS) {
|
||||
vr::TrackedDevicePose_t pose;
|
||||
vr::VRControllerState_t state;
|
||||
vrutil::get_con_pose(foot.get_index(), &pose, &state);
|
||||
|
||||
// only accept valid poses
|
||||
if (pose.bPoseIsValid) {
|
||||
auto length = std::max(foot.length, 0.001f);
|
||||
|
||||
// get components
|
||||
auto translation = foot.to_world(vrutil::get_translation(
|
||||
pose.mDeviceToAbsoluteTracking));
|
||||
auto direction = -linalg::qzdir(linalg::qmul(
|
||||
vrutil::get_rotation(pose.mDeviceToAbsoluteTracking.m),
|
||||
foot.rotation));
|
||||
direction = linalg::aliases::float3 {
|
||||
-direction.z, direction.x, direction.y
|
||||
};
|
||||
|
||||
// get intersection point
|
||||
auto intersection = vrutil::intersect_point(
|
||||
direction, translation,
|
||||
plane_normal, plane_point);
|
||||
auto distance = linalg::distance(
|
||||
translation, intersection);
|
||||
|
||||
// update event details
|
||||
foot.height = std::max(0.f, translation.z - intersection.z);
|
||||
foot.event.id = foot.id;
|
||||
foot.event.x = (intersection.x / 38.f) * touch_width;
|
||||
foot.event.y = (intersection.y / 49.f) * touch_height;
|
||||
if (translation.z > intersection.z) {
|
||||
foot.event.width = foot.size_base
|
||||
+ foot.size_scale * std::max(0.f, 1.f - distance / length);
|
||||
} else {
|
||||
foot.event.width = foot.size_base + foot.size_scale;
|
||||
}
|
||||
foot.event.height = foot.event.width;
|
||||
|
||||
// check if controller points down
|
||||
if (direction.z < 0) {
|
||||
|
||||
// check if plane in range
|
||||
if (distance <= length) {
|
||||
|
||||
// check previous event
|
||||
switch (foot.event.type) {
|
||||
case DRS_UP:
|
||||
|
||||
// generate down event
|
||||
foot.event.type = DRS_DOWN;
|
||||
break;
|
||||
|
||||
case DRS_DOWN:
|
||||
case DRS_MOVE:
|
||||
|
||||
// generate move event
|
||||
foot.event.type = DRS_MOVE;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// send event
|
||||
drs::fire_touches(&foot.event, 1);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// foot not intersecting with plane
|
||||
switch (foot.event.type) {
|
||||
case DRS_DOWN:
|
||||
case DRS_MOVE:
|
||||
|
||||
// generate up event
|
||||
foot.event.type = DRS_UP;
|
||||
drs::fire_touches(&foot.event, 1);
|
||||
break;
|
||||
|
||||
case DRS_UP:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
TOUCH_EVENTS.clear();
|
||||
touch_get_events(TOUCH_EVENTS);
|
||||
for (auto &te : TOUCH_EVENTS) {
|
||||
drs_touch_t drs_event;
|
||||
switch (te.type) {
|
||||
case TOUCH_DOWN:
|
||||
drs_event.type = DRS_DOWN;
|
||||
break;
|
||||
case TOUCH_UP:
|
||||
drs_event.type = DRS_UP;
|
||||
break;
|
||||
case TOUCH_MOVE:
|
||||
drs_event.type = DRS_MOVE;
|
||||
break;
|
||||
}
|
||||
drs_event.id = te.id;
|
||||
|
||||
// slow down
|
||||
vrutil::scan();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
// DRS uses 100x100 (px) as default foot size, so use that
|
||||
const float w = 100.1/1920.f;
|
||||
const float h = 100.1/1080.f;
|
||||
drs_event.width = w;
|
||||
drs_event.height = h;
|
||||
|
||||
const float x = te.x / 1920.f;
|
||||
const float y = te.y / 1080.f;
|
||||
// note that only x-y are transposed, not w-h
|
||||
if (TRANSPOSE_TOUCH) {
|
||||
drs_event.x = y;
|
||||
drs_event.y = x;
|
||||
} else {
|
||||
drs_event.x = x;
|
||||
drs_event.y = y;
|
||||
}
|
||||
drs::fire_touches(&drs_event, 1);
|
||||
}
|
||||
VR_STARTED = false;
|
||||
return nullptr;
|
||||
});
|
||||
t.detach();
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
});
|
||||
t.detach();
|
||||
}
|
||||
|
||||
DRSGame::DRSGame() : Game("DANCERUSH") {
|
||||
@@ -397,9 +308,10 @@ namespace games::drs {
|
||||
detour::iat("?InitTouch@TouchSDK@@QEAAHPEAU_DeviceInfo@@HP6AXU2@PEBU_TouchPointData@@HHPEBX@ZP6AX1_N3@ZPEAX@Z",
|
||||
(void *) &TouchSDK_InitTouch, avs::game::DLL_INSTANCE);
|
||||
|
||||
// VR
|
||||
if (vrutil::STATUS == vrutil::VRStatus::Running) {
|
||||
start_vr();
|
||||
if (!DISABLE_TOUCH) {
|
||||
start_touch();
|
||||
} else {
|
||||
log_info("drs", "no native input method detected");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+13
-19
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "games/game.h"
|
||||
#include "external/linalg.h"
|
||||
#include "touch/touch.h"
|
||||
|
||||
namespace games::drs {
|
||||
|
||||
@@ -11,7 +12,7 @@ namespace games::drs {
|
||||
DRS_MOVE = 2,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
typedef struct drs_touch {
|
||||
int type = DRS_UP;
|
||||
int id = 0;
|
||||
double x = 0.0;
|
||||
@@ -20,27 +21,20 @@ namespace games::drs {
|
||||
double height = 1;
|
||||
} drs_touch_t;
|
||||
|
||||
struct VRFoot {
|
||||
uint32_t id;
|
||||
uint32_t index = ~0u;
|
||||
float length = 3.1f;
|
||||
float size_base = 0.05f;
|
||||
float size_scale = 0.1f;
|
||||
float height = 3.f;
|
||||
linalg::aliases::float4 rotation {0, 0, 0, 1};
|
||||
drs_touch_t event {};
|
||||
uint32_t get_index();
|
||||
linalg::aliases::float3 to_world(linalg::aliases::float3 pos);
|
||||
};
|
||||
#define DRS_TAPELED_ROWS 49
|
||||
#define DRS_TAPELED_COLS 38
|
||||
#define DRS_TAPELED_COLS_TOTAL (DRS_TAPELED_ROWS * DRS_TAPELED_COLS)
|
||||
|
||||
extern char DRS_TAPELED[38 * 49][3];
|
||||
extern linalg::aliases::float3 VR_SCALE;
|
||||
extern linalg::aliases::float3 VR_OFFSET;
|
||||
extern float VR_ROTATION;
|
||||
extern VRFoot VR_FOOTS[2];
|
||||
// each r,g,b value is stored in a char but 215 is the highest you'll see from the game at 100% brightness in the test menu
|
||||
#define DRS_TAPELED_MAX_VAL 215
|
||||
|
||||
extern char DRS_TAPELED[DRS_TAPELED_COLS_TOTAL][3];
|
||||
extern std::vector<TouchEvent> TOUCH_EVENTS;
|
||||
extern bool DISABLE_TOUCH;
|
||||
extern bool TRANSPOSE_TOUCH;
|
||||
|
||||
void fire_touches(drs_touch_t *events, size_t event_count);
|
||||
void start_vr();
|
||||
void start_touch();
|
||||
|
||||
class DRSGame : public games::Game {
|
||||
public:
|
||||
|
||||
+30
-6
@@ -27,15 +27,39 @@ std::vector<Button> &games::drs::get_buttons() {
|
||||
return buttons;
|
||||
}
|
||||
|
||||
std::string games::drs::get_buttons_help() {
|
||||
// keep to max 100 characters wide
|
||||
return
|
||||
"Motion camera required for DOWN movement.\n"
|
||||
"Touchscreen supported for dance floor."
|
||||
;
|
||||
}
|
||||
|
||||
std::vector<Light> &games::drs::get_lights() {
|
||||
static std::vector<Light> lights;
|
||||
|
||||
//if (lights.empty()) {
|
||||
//lights = GameAPI::Lights::getLights("DANCERUSH");
|
||||
//GameAPI::Lights::sortLights(
|
||||
// &lights,
|
||||
//);
|
||||
//}
|
||||
if (lights.empty()) {
|
||||
lights = GameAPI::Lights::getLights("DANCERUSH");
|
||||
GameAPI::Lights::sortLights(
|
||||
&lights,
|
||||
"P1 Start",
|
||||
"P1 Menu Up",
|
||||
"P1 Menu Down",
|
||||
"P1 Menu Left",
|
||||
"P1 Menu Right",
|
||||
"P2 Start",
|
||||
"P2 Menu Up",
|
||||
"P2 Menu Down",
|
||||
"P2 Menu Left",
|
||||
"P2 Menu Right",
|
||||
"Card Reader R",
|
||||
"Card Reader G",
|
||||
"Card Reader B",
|
||||
"Title Panel R",
|
||||
"Title Panel G",
|
||||
"Title Panel B"
|
||||
);
|
||||
}
|
||||
|
||||
return lights;
|
||||
}
|
||||
|
||||
@@ -27,10 +27,27 @@ namespace games::drs {
|
||||
// all lights in correct order
|
||||
namespace Lights {
|
||||
enum {
|
||||
P1_START,
|
||||
P1_MENU_UP,
|
||||
P1_MENU_DOWN,
|
||||
P1_MENU_LEFT,
|
||||
P1_MENU_RIGHT,
|
||||
P2_START,
|
||||
P2_MENU_UP,
|
||||
P2_MENU_DOWN,
|
||||
P2_MENU_LEFT,
|
||||
P2_MENU_RIGHT,
|
||||
CARD_READER_R,
|
||||
CARD_READER_G,
|
||||
CARD_READER_B,
|
||||
TITLE_PANEL_R,
|
||||
TITLE_PANEL_G,
|
||||
TITLE_PANEL_B,
|
||||
};
|
||||
}
|
||||
|
||||
// getters
|
||||
std::vector<Button> &get_buttons();
|
||||
std::string get_buttons_help();
|
||||
std::vector<Light> &get_lights();
|
||||
}
|
||||
|
||||
@@ -20,6 +20,15 @@ std::vector<Button> &games::ftt::get_buttons() {
|
||||
return buttons;
|
||||
}
|
||||
|
||||
std::string games::ftt::get_buttons_help() {
|
||||
// keep to max 100 characters wide
|
||||
return
|
||||
" Pad1 Pad2 Pad3 Pad4"
|
||||
"\n"
|
||||
"Drum pads are velocity-sensitive."
|
||||
;
|
||||
}
|
||||
|
||||
std::vector<Analog> &games::ftt::get_analogs() {
|
||||
static std::vector<Analog> analogs;
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ namespace games::ftt {
|
||||
|
||||
// getters
|
||||
std::vector<Button> &get_buttons();
|
||||
std::string get_buttons_help();
|
||||
std::vector<Analog> &get_analogs();
|
||||
std::vector<Light> &get_lights();
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include "cfg/configurator.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "util/cpuutils.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/logging.h"
|
||||
@@ -38,6 +40,8 @@ namespace games::gitadora {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef SPICE64
|
||||
|
||||
/*
|
||||
* Two Channel Audio Mode
|
||||
* We proxy bmsd2_boot_hook and modify the last parameter which is apparently the channel count.
|
||||
@@ -49,6 +53,8 @@ namespace games::gitadora {
|
||||
return bmsd2_boot_orig(a1, a2, a3, 2);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Command Line Arguments
|
||||
* We hook this to override specific values.
|
||||
@@ -152,15 +158,41 @@ namespace games::gitadora {
|
||||
GitaDoraGame::GitaDoraGame() : Game("GitaDora") {
|
||||
}
|
||||
|
||||
void GitaDoraGame::pre_attach() {
|
||||
Game::pre_attach();
|
||||
|
||||
if (!cfg::CONFIGURATOR_STANDALONE) {
|
||||
if (CAB_TYPE.has_value()) {
|
||||
log_info("gitadora", "cab type: {}", CAB_TYPE.value());
|
||||
} else {
|
||||
log_warning("gitadora", "cab type: not set");
|
||||
}
|
||||
|
||||
log_info("gitadora", "applying processor affinity workaround to prevent hangs...");
|
||||
#ifdef SPICE64
|
||||
// workaround for hang on title screen, on systems with many SMT threads
|
||||
// exact cause is unknown; most likely a bad assumption in some video decoder
|
||||
// 0xFF (first 8 LPs) seems to work well for most people
|
||||
cpuutils::set_processor_affinity(0xFF, false);
|
||||
#else
|
||||
// XG versions ran on ancient dual-core AMD systems
|
||||
// having more cores cause random hangs on song select screen
|
||||
cpuutils::set_processor_affinity(0x3, false);
|
||||
|
||||
// check invalid cab type
|
||||
if (CAB_TYPE.has_value() && CAB_TYPE.value() == 3) {
|
||||
log_fatal("gitadora", "Cabinet type 3 (SD2) not supported on XG series");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void GitaDoraGame::attach() {
|
||||
Game::attach();
|
||||
|
||||
// modules
|
||||
HMODULE sharepj_module = libutils::try_module("libshare-pj.dll");
|
||||
HMODULE bmsd_engine_module = libutils::try_module("libbmsd-engine.dll");
|
||||
HMODULE bmsd_module = libutils::try_module("libbmsd.dll");
|
||||
HMODULE bmsd2_module = libutils::try_module("libbmsd2.dll");
|
||||
HMODULE gdme_module = libutils::try_module("libgdme.dll");
|
||||
HMODULE system_module = libutils::try_module("libsystem.dll");
|
||||
|
||||
// patches
|
||||
@@ -170,8 +202,6 @@ namespace games::gitadora {
|
||||
sharepj_module, "eam_network_settings_conflict"));
|
||||
detour::inline_hook((void *) bmsd2_set_windows_volume, libutils::try_proc(
|
||||
bmsd2_module, "bmsd2_set_windows_volume"));
|
||||
detour::inline_hook((void *) bmsd2_set_windows_volume, libutils::try_proc(
|
||||
bmsd2_module, "bmsd2_set_windows_volume"));
|
||||
detour::inline_hook((void *) sys_code_get_cmdline, libutils::try_proc(
|
||||
system_module, "sys_code_get_cmdline"));
|
||||
detour::inline_hook((void *) sys_setting_get_param, libutils::try_proc(
|
||||
@@ -183,6 +213,10 @@ namespace games::gitadora {
|
||||
detour::inline_hook((void *) sys_debug_dip_set_param, libutils::try_proc(
|
||||
system_module, "sys_debug_dip_set_param"));
|
||||
|
||||
#ifdef SPICE64
|
||||
|
||||
HMODULE gdme_module = libutils::try_module("libgdme.dll");
|
||||
|
||||
// window patch
|
||||
if (GRAPHICS_WINDOWED && !replace_pattern(
|
||||
gdme_module,
|
||||
@@ -192,6 +226,9 @@ namespace games::gitadora {
|
||||
log_warning("gitadora", "windowed mode failed");
|
||||
}
|
||||
|
||||
HMODULE bmsd_engine_module = libutils::try_module("libbmsd-engine.dll");
|
||||
HMODULE bmsd_module = libutils::try_module("libbmsd.dll");
|
||||
|
||||
// two channel mod
|
||||
if (TWOCHANNEL) {
|
||||
bmsd2_boot_orig = detour::iat_try("bmsd2_boot", bmsd2_boot_hook, bmsd_module);
|
||||
@@ -202,5 +239,8 @@ namespace games::gitadora {
|
||||
log_warning("gitadora", "two channel mode failed");
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/game.h"
|
||||
|
||||
namespace games::gitadora {
|
||||
@@ -13,6 +14,21 @@ namespace games::gitadora {
|
||||
class GitaDoraGame : public games::Game {
|
||||
public:
|
||||
GitaDoraGame();
|
||||
virtual void attach();
|
||||
virtual void pre_attach() override;
|
||||
virtual void attach() override;
|
||||
};
|
||||
|
||||
static inline bool is_drum() {
|
||||
return (
|
||||
avs::game::is_model({ "J32", "K32", "L32" }) ||
|
||||
(avs::game::is_model("M32") && avs::game::SPEC[0] == 'B')
|
||||
);
|
||||
}
|
||||
static inline bool is_guitar() {
|
||||
return (
|
||||
avs::game::is_model({ "J33", "K33", "L33" }) ||
|
||||
(avs::game::is_model("M32") && avs::game::SPEC[0] == 'A')
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+101
-1
@@ -102,6 +102,27 @@ std::vector<Analog> &games::gitadora::get_analogs() {
|
||||
return analogs;
|
||||
}
|
||||
|
||||
std::string games::gitadora::get_buttons_help() {
|
||||
// keep to max 100 characters wide
|
||||
return
|
||||
"guitar:\n"
|
||||
" < R G B Y P --- Pick ] \n"
|
||||
"\n"
|
||||
"drums:\n"
|
||||
" LeftCymbal RightCymbal\n"
|
||||
" HiHat HiTom LowTom \n"
|
||||
" Snare FloorTom \n"
|
||||
" -------------------------------------------------------\n"
|
||||
" Left Bass\n"
|
||||
" Pedal Pedal\n"
|
||||
"\n"
|
||||
"Drums are NOT velocity-sensitive!\n"
|
||||
"\n"
|
||||
"For MIDI drums with Open/Closed HiHat configurations, bind variations below. "
|
||||
"v2_drum algorithm might work better for those drums."
|
||||
;
|
||||
}
|
||||
|
||||
std::vector<Light> &games::gitadora::get_lights() {
|
||||
static std::vector<Light> lights;
|
||||
|
||||
@@ -110,7 +131,86 @@ std::vector<Light> &games::gitadora::get_lights() {
|
||||
|
||||
GameAPI::Lights::sortLights(&lights,
|
||||
"Guitar P1 Motor",
|
||||
"Guitar P2 Motor"
|
||||
"Guitar P2 Motor",
|
||||
|
||||
"P1 Start",
|
||||
"P1 Menu Up Down (DX)",
|
||||
"P1 Menu Left Right (DX)",
|
||||
"P1 Help (DX)",
|
||||
|
||||
"P2 Start",
|
||||
"P2 Menu Up Down (DX)",
|
||||
"P2 Menu Left Right (DX)",
|
||||
"P2 Help (DX)",
|
||||
|
||||
"Drum Left Cymbal",
|
||||
"Drum Hi-Hat",
|
||||
"Drum Snare",
|
||||
"Drum High Tom",
|
||||
"Drum Low Tom",
|
||||
"Drum Floor Tom",
|
||||
"Drum Right Cymbal",
|
||||
|
||||
"Drum Woofer R",
|
||||
"Drum Woofer G",
|
||||
"Drum Woofer B",
|
||||
|
||||
"Drum Stage R (DX)",
|
||||
"Drum Stage G (DX)",
|
||||
"Drum Stage B (DX)",
|
||||
|
||||
"Spot Left (DX)",
|
||||
"Spot Right (DX)",
|
||||
"Spot Center Left (DX)",
|
||||
"Spot Center Right (DX)",
|
||||
|
||||
"Drum Spot Rear Left (DX)",
|
||||
"Drum Spot Rear Right (DX)",
|
||||
|
||||
"Guitar Lower Left R (DX)",
|
||||
"Guitar Lower Left G (DX)",
|
||||
"Guitar Lower Left B (DX)",
|
||||
"Guitar Lower Right R (DX)",
|
||||
"Guitar Lower Right G (DX)",
|
||||
"Guitar Lower Right B (DX)",
|
||||
|
||||
"Guitar Left Speaker Upper R (DX)",
|
||||
"Guitar Left Speaker Upper G (DX)",
|
||||
"Guitar Left Speaker Upper B (DX)",
|
||||
"Guitar Left Speaker Mid Up Left R (DX)",
|
||||
"Guitar Left Speaker Mid Up Left G (DX)",
|
||||
"Guitar Left Speaker Mid Up Left B (DX)",
|
||||
"Guitar Left Speaker Mid Up Right R (DX)",
|
||||
"Guitar Left Speaker Mid Up Right G (DX)",
|
||||
"Guitar Left Speaker Mid Up Right B (DX)",
|
||||
"Guitar Left Speaker Mid Low Left R (DX)",
|
||||
"Guitar Left Speaker Mid Low Left G (DX)",
|
||||
"Guitar Left Speaker Mid Low Left B (DX)",
|
||||
"Guitar Left Speaker Mid Low Right R (DX)",
|
||||
"Guitar Left Speaker Mid Low Right G (DX)",
|
||||
"Guitar Left Speaker Mid Low Right B (DX)",
|
||||
"Guitar Left Speaker Lower R (DX)",
|
||||
"Guitar Left Speaker Lower G (DX)",
|
||||
"Guitar Left Speaker Lower B (DX)",
|
||||
|
||||
"Guitar Right Speaker Upper R (DX)",
|
||||
"Guitar Right Speaker Upper G (DX)",
|
||||
"Guitar Right Speaker Upper B (DX)",
|
||||
"Guitar Right Speaker Mid Up Left R (DX)",
|
||||
"Guitar Right Speaker Mid Up Left G (DX)",
|
||||
"Guitar Right Speaker Mid Up Left B (DX)",
|
||||
"Guitar Right Speaker Mid Up Right R (DX)",
|
||||
"Guitar Right Speaker Mid Up Right G (DX)",
|
||||
"Guitar Right Speaker Mid Up Right B (DX)",
|
||||
"Guitar Right Speaker Mid Low Left R (DX)",
|
||||
"Guitar Right Speaker Mid Low Left G (DX)",
|
||||
"Guitar Right Speaker Mid Low Left B (DX)",
|
||||
"Guitar Right Speaker Mid Low Right R (DX)",
|
||||
"Guitar Right Speaker Mid Low Right G (DX)",
|
||||
"Guitar Right Speaker Mid Low Right B (DX)",
|
||||
"Guitar Right Speaker Lower R (DX)",
|
||||
"Guitar Right Speaker Lower G (DX)",
|
||||
"Guitar Right Speaker Lower B (DX)"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+96
-2
@@ -65,6 +65,7 @@ namespace games::gitadora {
|
||||
DrumHelp,
|
||||
DrumButtonExtra1,
|
||||
DrumButtonExtra2,
|
||||
// note: this is the order they appear in the test menu
|
||||
DrumHiHat,
|
||||
DrumHiHatClosed,
|
||||
DrumHiHatHalfOpen,
|
||||
@@ -95,14 +96,107 @@ namespace games::gitadora {
|
||||
|
||||
// all lights in correct order
|
||||
namespace Lights {
|
||||
enum {
|
||||
typedef enum {
|
||||
// vibration motors (DX only)
|
||||
GuitarP1Motor,
|
||||
GuitarP2Motor,
|
||||
};
|
||||
|
||||
// P1
|
||||
P1MenuStart,
|
||||
// DX only
|
||||
P1MenuUpDown,
|
||||
P1MenuLeftRight,
|
||||
P1MenuHelp,
|
||||
|
||||
// P2
|
||||
P2MenuStart,
|
||||
// DX only
|
||||
P2MenuUpDown,
|
||||
P2MenuLeftRight,
|
||||
P2MenuHelp,
|
||||
|
||||
// drums
|
||||
DrumLeftCymbal,
|
||||
DrumHiHat,
|
||||
DrumSnare,
|
||||
DrumHighTom,
|
||||
DrumLowTom,
|
||||
DrumFloorTom,
|
||||
DrumRightCymbal,
|
||||
|
||||
// drum woofer
|
||||
DrumWooferR,
|
||||
DrumWooferG,
|
||||
DrumWooferB,
|
||||
|
||||
// drum stage, DX only
|
||||
DrumStageR,
|
||||
DrumStageG,
|
||||
DrumStageB,
|
||||
|
||||
// main spotlights, DX only
|
||||
SpotFrontLeft,
|
||||
SpotFrontRight,
|
||||
SpotCenterLeft,
|
||||
SpotCenterRight,
|
||||
|
||||
// drum rear spotlights, DX only
|
||||
DrumSpotRearLeft,
|
||||
DrumSpotRearRight,
|
||||
|
||||
// guitar center lower RGB, DX only
|
||||
GuitarLowerLeftR,
|
||||
GuitarLowerLeftG,
|
||||
GuitarLowerLeftB,
|
||||
GuitarLowerRightR,
|
||||
GuitarLowerRightG,
|
||||
GuitarLowerRightB,
|
||||
|
||||
// guitar left speaker, DX only
|
||||
GuitarLeftSpeakerUpperR,
|
||||
GuitarLeftSpeakerUpperG,
|
||||
GuitarLeftSpeakerUpperB,
|
||||
GuitarLeftSpeakerMidUpLeftR,
|
||||
GuitarLeftSpeakerMidUpLeftG,
|
||||
GuitarLeftSpeakerMidUpLeftB,
|
||||
GuitarLeftSpeakerMidUpRightR,
|
||||
GuitarLeftSpeakerMidUpRightG,
|
||||
GuitarLeftSpeakerMidUpRightB,
|
||||
GuitarLeftSpeakerMidLowLeftR,
|
||||
GuitarLeftSpeakerMidLowLeftG,
|
||||
GuitarLeftSpeakerMidLowLeftB,
|
||||
GuitarLeftSpeakerMidLowRightR,
|
||||
GuitarLeftSpeakerMidLowRightG,
|
||||
GuitarLeftSpeakerMidLowRightB,
|
||||
GuitarLeftSpeakerLowerR,
|
||||
GuitarLeftSpeakerLowerG,
|
||||
GuitarLeftSpeakerLowerB,
|
||||
|
||||
// guitar right speaker, DX only
|
||||
GuitarRightSpeakerUpperR,
|
||||
GuitarRightSpeakerUpperG,
|
||||
GuitarRightSpeakerUpperB,
|
||||
GuitarRightSpeakerMidUpLeftR,
|
||||
GuitarRightSpeakerMidUpLeftG,
|
||||
GuitarRightSpeakerMidUpLeftB,
|
||||
GuitarRightSpeakerMidUpRightR,
|
||||
GuitarRightSpeakerMidUpRightG,
|
||||
GuitarRightSpeakerMidUpRightB,
|
||||
GuitarRightSpeakerMidLowLeftR,
|
||||
GuitarRightSpeakerMidLowLeftG,
|
||||
GuitarRightSpeakerMidLowLeftB,
|
||||
GuitarRightSpeakerMidLowRightR,
|
||||
GuitarRightSpeakerMidLowRightG,
|
||||
GuitarRightSpeakerMidLowRightB,
|
||||
GuitarRightSpeakerLowerR,
|
||||
GuitarRightSpeakerLowerG,
|
||||
GuitarRightSpeakerLowerB
|
||||
} gitadora_lights_t;
|
||||
}
|
||||
|
||||
// getters
|
||||
std::vector<Button> &get_buttons();
|
||||
std::string get_buttons_help();
|
||||
std::vector<Analog> &get_analogs();
|
||||
std::vector<Light> &get_lights();
|
||||
}
|
||||
|
||||
@@ -27,6 +27,14 @@ std::vector<Button> &games::hpm::get_buttons() {
|
||||
return buttons;
|
||||
}
|
||||
|
||||
std::string games::hpm::get_buttons_help() {
|
||||
// keep to max 100 characters wide
|
||||
return
|
||||
" start \n"
|
||||
" red(1) blue(2) yellow(3) green(4) \n"
|
||||
;
|
||||
}
|
||||
|
||||
std::vector<Light> &games::hpm::get_lights() {
|
||||
static std::vector<Light> lights;
|
||||
|
||||
|
||||
@@ -44,5 +44,6 @@ namespace games::hpm {
|
||||
|
||||
// getters
|
||||
std::vector<Button> &get_buttons();
|
||||
std::string get_buttons_help();
|
||||
std::vector<Light> &get_lights();
|
||||
}
|
||||
|
||||
@@ -32,6 +32,11 @@ bool games::iidx::IIDXFMSerialHandle::FMSerialDevice::parse_msg(
|
||||
delete msg;
|
||||
break;
|
||||
}
|
||||
case 0x0153:
|
||||
if (DISABLE_ESPEC_IO) {
|
||||
return false;
|
||||
}
|
||||
[[fallthrough]]; // to 0x0152
|
||||
case 0x0152: { // STATUS
|
||||
|
||||
// check input data length
|
||||
|
||||
@@ -32,6 +32,11 @@ bool games::iidx::BI2XSerialHandle::BI2XDevice::parse_msg(
|
||||
delete msg;
|
||||
break;
|
||||
}
|
||||
case 0x0153:
|
||||
if (DISABLE_ESPEC_IO) {
|
||||
return false;
|
||||
}
|
||||
[[fallthrough]]; // to 0x0152
|
||||
case 0x0152: { // STATUS
|
||||
|
||||
// check input data length
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "launcher/options.h"
|
||||
#include "io.h"
|
||||
#include "iidx.h"
|
||||
#include "util/tapeled.h"
|
||||
|
||||
namespace games::iidx {
|
||||
constexpr bool BI2X_PASSTHROUGH = false;
|
||||
@@ -416,12 +417,9 @@ namespace games::iidx {
|
||||
static struct TapeLedMapping {
|
||||
size_t data_size;
|
||||
int index_r, index_g, index_b;
|
||||
float avg_mult;
|
||||
|
||||
TapeLedMapping(size_t data_size, int index_r, int index_g, int index_b)
|
||||
: data_size(data_size), index_r(index_r), index_g(index_g), index_b(index_b) {
|
||||
avg_mult = 1.f / (data_size * 255);
|
||||
}
|
||||
: data_size(data_size), index_r(index_r), index_g(index_g), index_b(index_b) {}
|
||||
|
||||
} mapping[] = {
|
||||
{ 19, Lights::StageLeftAvgR, Lights::StageLeftAvgG, Lights::StageLeftAvgB },
|
||||
@@ -444,32 +442,20 @@ namespace games::iidx {
|
||||
};
|
||||
|
||||
// check index bounds
|
||||
if (index < std::size(mapping)) {
|
||||
if (tapeledutils::is_enabled() && index < std::size(mapping)) {
|
||||
auto &map = mapping[index];
|
||||
|
||||
// calculate average color
|
||||
size_t avg_ri = 0;
|
||||
size_t avg_gi = 0;
|
||||
size_t avg_bi = 0;
|
||||
for (size_t i = 0; i < map.data_size; i++) {
|
||||
auto color = &data[i * 3];
|
||||
avg_ri += color[0];
|
||||
avg_gi += color[1];
|
||||
avg_bi += color[2];
|
||||
}
|
||||
float avg_r = avg_ri * map.avg_mult;
|
||||
float avg_g = avg_gi * map.avg_mult;
|
||||
float avg_b = avg_bi * map.avg_mult;
|
||||
// pick a color to use
|
||||
const auto rgb = tapeledutils::pick_color_from_led_tape(data, map.data_size);
|
||||
|
||||
// set average color
|
||||
// program the lights into API
|
||||
auto &lights = get_lights();
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_r], avg_r);
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_g], avg_g);
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_b], avg_b);
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_r], rgb.r);
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_g], rgb.g);
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_b], rgb.b);
|
||||
}
|
||||
|
||||
if (This != custom_node) {
|
||||
|
||||
// send tape data to real device
|
||||
return AIO_IOB2_BI2X_TDJ__SetTapeLedData_orig(This, index, data);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,668 @@
|
||||
#include "camera.h"
|
||||
|
||||
#if SPICE64
|
||||
|
||||
#include <d3d9.h>
|
||||
#include "mf_wrappers.h"
|
||||
#include "avs/game.h"
|
||||
#include "external/rapidjson/document.h"
|
||||
#include "external/rapidjson/pointer.h"
|
||||
#include "external/rapidjson/prettywriter.h"
|
||||
#include "games/iidx/iidx.h"
|
||||
#include "games/io.h"
|
||||
#include "launcher/options.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/fileutils.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/sigscan.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
static std::filesystem::path dll_path;
|
||||
static HMODULE iidx_module;
|
||||
static uintptr_t addr_hook_a = 0;
|
||||
static uintptr_t addr_hook_b = 0;
|
||||
static uintptr_t addr_textures = 0;
|
||||
static uintptr_t addr_device_offset = 0;
|
||||
|
||||
typedef void **(__fastcall *camera_hook_a_t)(PBYTE);
|
||||
typedef LPDIRECT3DTEXTURE9 (*camera_hook_b_t)(void*, int);
|
||||
static camera_hook_a_t camera_hook_a_orig = nullptr;
|
||||
static camera_hook_b_t camera_hook_b_orig = nullptr;
|
||||
auto static hook_a_init = std::once_flag {};
|
||||
|
||||
static LPDIRECT3DDEVICE9EX device;
|
||||
static LPDIRECT3DTEXTURE9 *texture_a = nullptr;
|
||||
static LPDIRECT3DTEXTURE9 *texture_b = nullptr;
|
||||
|
||||
struct PredefinedHook {
|
||||
std::string pe_identifier;
|
||||
uintptr_t hook_a;
|
||||
uintptr_t hook_b;
|
||||
uintptr_t hook_textures;
|
||||
uintptr_t hook_device_offset;
|
||||
};
|
||||
|
||||
PredefinedHook g_predefinedHooks[] =
|
||||
{
|
||||
// 27 003
|
||||
{ "5f713b52_6d4090", 0x005971b0, 0x005fb2c0, 0x04e49898, 0x000000d0 },
|
||||
// 27 010
|
||||
{ "5f713946_7f52b0", 0x006b89e0, 0x0071c950, 0x04fd08b8, 0x000000d0 },
|
||||
};
|
||||
|
||||
const DWORD g_predefinedHooksLength = ARRAYSIZE(g_predefinedHooks);
|
||||
|
||||
namespace games::iidx {
|
||||
|
||||
static IIDXLocalCamera *top_camera = nullptr; // camera id #0
|
||||
static IIDXLocalCamera *front_camera = nullptr; // camera id #1
|
||||
std::vector<IIDXLocalCamera*> LOCAL_CAMERA_LIST = {};
|
||||
static IDirect3DDeviceManager9 *s_pD3DManager = nullptr;
|
||||
std::filesystem::path CAMERA_CONFIG_PATH;
|
||||
bool CAMERA_READY = false;
|
||||
|
||||
bool parse_cmd_params() {
|
||||
const auto remove_spaces = [](const char& c) {
|
||||
return c == ' ';
|
||||
};
|
||||
|
||||
log_misc("iidx:camhook", "Using user-supplied addresses (-iidxcamhookoffset)");
|
||||
|
||||
auto s = TDJ_CAMERA_OVERRIDE.value();
|
||||
s.erase(std::remove_if(s.begin(), s.end(), remove_spaces), s.end());
|
||||
|
||||
if (sscanf(
|
||||
s.c_str(),
|
||||
"%i,%i,%i,%i",
|
||||
(int*)&addr_hook_a, (int*)&addr_hook_b, (int*)&addr_textures, (int*)&addr_device_offset) == 4) {
|
||||
|
||||
return true;
|
||||
|
||||
} else {
|
||||
log_fatal("iidx:camhook", "failed to parse -iidxcamhookoffset");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool find_camera_hooks() {
|
||||
std::string dll_name = avs::game::DLL_NAME;
|
||||
dll_path = MODULE_PATH / dll_name;
|
||||
|
||||
iidx_module = libutils::try_module(dll_path);
|
||||
if (!iidx_module) {
|
||||
log_warning("iidx:camhook", "failed to hook game DLL");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
uint32_t time_date_stamp = 0;
|
||||
uint32_t address_of_entry_point = 0;
|
||||
|
||||
// there are three different ways we try to hook the camera entry points
|
||||
// 1) user-provided override via cmd line
|
||||
// 2) predefined offsets using PE header matching (needed for iidx27 and few others)
|
||||
// 3) signature match (should work for most iidx28-31)
|
||||
|
||||
// method 1: user provided
|
||||
if (TDJ_CAMERA_OVERRIDE.has_value() && parse_cmd_params()) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// method 2: predefined offsets
|
||||
get_pe_identifier(dll_path, &time_date_stamp, &address_of_entry_point);
|
||||
auto pe = fmt::format("{:x}_{:x}", time_date_stamp, address_of_entry_point);
|
||||
log_info("iidx:camhook", "Locating predefined hook addresses for LDJ-{}", pe);
|
||||
|
||||
for (DWORD i = 0; i < g_predefinedHooksLength; i++) {
|
||||
if (pe.compare(g_predefinedHooks[i].pe_identifier) == 0) {
|
||||
log_misc("iidx:camhook", "Found predefined addresses");
|
||||
addr_hook_a = g_predefinedHooks[i].hook_a;
|
||||
addr_hook_b = g_predefinedHooks[i].hook_b;
|
||||
addr_textures = g_predefinedHooks[i].hook_textures;
|
||||
addr_device_offset = g_predefinedHooks[i].hook_device_offset;
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
// method 3: signature match
|
||||
log_info("iidx:camhook", "Did not find predefined hook address, try signature match");
|
||||
|
||||
// --- addr_hook_a ---
|
||||
uint8_t *addr_hook_a_ptr = reinterpret_cast<uint8_t *>(find_pattern(
|
||||
iidx_module,
|
||||
"488BC4565741564883EC5048C740D8FEFFFFFF",
|
||||
"XXXXXXXXXXXXXXXXXXX",
|
||||
0, 0));
|
||||
|
||||
if (addr_hook_a_ptr == nullptr) {
|
||||
log_warning("iidx:camhook", "failed to find hook: addr_hook_a");
|
||||
log_warning("iidx:camhook", "hint: this feature REQUIRES a compatible DLL!");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
addr_hook_a = (uint64_t) (addr_hook_a_ptr - (uint8_t*) iidx_module);
|
||||
|
||||
// addr_hook_b
|
||||
uint8_t* addr_hook_b_ptr = reinterpret_cast<uint8_t *>(find_pattern(
|
||||
iidx_module,
|
||||
"E8000000004885C07439E8",
|
||||
"X????XXXXXX",
|
||||
0, 0));
|
||||
|
||||
if (addr_hook_b_ptr == nullptr) {
|
||||
log_warning("iidx:camhook", "failed to find hook: addr_hook_b");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// displace with the content of wildcard bytes
|
||||
int32_t disp_b = *((int32_t *) (addr_hook_b_ptr + 1));
|
||||
|
||||
addr_hook_b = (addr_hook_b_ptr - (uint8_t*) iidx_module) + disp_b + 5;
|
||||
|
||||
// --- addr_textures ---
|
||||
uint8_t* addr_textures_ptr = reinterpret_cast<uint8_t *>(find_pattern(
|
||||
iidx_module,
|
||||
"E800000000488BC8488BD34883C420",
|
||||
"X????XXXXXXXXXX",
|
||||
0, 0));
|
||||
|
||||
if (addr_textures_ptr == nullptr) {
|
||||
log_warning("iidx:camhook", "failed to find hook: addr_textures (part 1)");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// displace with the content of wildcard bytes
|
||||
int32_t disp_textures = *((int32_t *) (addr_textures_ptr + 1));
|
||||
addr_textures_ptr += disp_textures + 5;
|
||||
|
||||
uint32_t search_from = (addr_textures_ptr - (uint8_t*) iidx_module);
|
||||
|
||||
addr_textures_ptr = reinterpret_cast<uint8_t *>(find_pattern_from(
|
||||
iidx_module,
|
||||
"488D0D",
|
||||
"XXX",
|
||||
0, 0, search_from));
|
||||
|
||||
if (addr_textures_ptr == nullptr) {
|
||||
log_warning("iidx:camhook", "failed to find hook: addr_textures (part 2)");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// displace again with the next integer
|
||||
disp_textures = *((int32_t *) (addr_textures_ptr + 3));
|
||||
|
||||
addr_textures = (addr_textures_ptr - (uint8_t*) iidx_module) + disp_textures + 7;
|
||||
|
||||
// addr_device_offset
|
||||
search_from = (addr_hook_a_ptr - (uint8_t*) iidx_module);
|
||||
uint8_t *addr_device_ptr = reinterpret_cast<uint8_t *>(find_pattern_from(
|
||||
iidx_module,
|
||||
"488B89",
|
||||
"XXX",
|
||||
3, 0, search_from));
|
||||
addr_device_offset = *addr_device_ptr;
|
||||
|
||||
if (addr_textures_ptr == nullptr) {
|
||||
log_warning("iidx:camhook", "failed to find hook: addr_textures (part 3)");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void **__fastcall camera_hook_a(PBYTE a1) {
|
||||
std::call_once(hook_a_init, [&]{
|
||||
device = *reinterpret_cast<LPDIRECT3DDEVICE9EX*>(a1 + addr_device_offset);
|
||||
auto const textures = *reinterpret_cast<LPDIRECT3DTEXTURE9**>((uint8_t*)iidx_module + addr_textures);
|
||||
|
||||
texture_a = textures;
|
||||
texture_b = textures + 2;
|
||||
|
||||
init_local_camera();
|
||||
});
|
||||
return camera_hook_a_orig(a1);
|
||||
}
|
||||
|
||||
static LPDIRECT3DTEXTURE9 camera_hook_b(void* a1, int idx) {
|
||||
if (idx == 0 && top_camera) {
|
||||
return top_camera->GetTexture();
|
||||
}
|
||||
if (idx == 1 && front_camera) {
|
||||
return front_camera->GetTexture();
|
||||
}
|
||||
return camera_hook_b_orig(a1, idx);
|
||||
}
|
||||
|
||||
bool create_hooks() {
|
||||
auto *hook_a_ptr = reinterpret_cast<uint16_t *>(
|
||||
reinterpret_cast<intptr_t>(iidx_module) + addr_hook_a);
|
||||
|
||||
if (!detour::trampoline(
|
||||
reinterpret_cast<camera_hook_a_t>(hook_a_ptr),
|
||||
camera_hook_a,
|
||||
&camera_hook_a_orig)) {
|
||||
log_warning("iidx:camhook", "failed to trampoline hook_a");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
auto *hook_b_ptr = reinterpret_cast<uint16_t *>(
|
||||
reinterpret_cast<intptr_t>(iidx_module) + addr_hook_b);
|
||||
|
||||
if (!detour::trampoline(
|
||||
reinterpret_cast<camera_hook_b_t>(hook_b_ptr),
|
||||
camera_hook_b,
|
||||
&camera_hook_b_orig)) {
|
||||
log_warning("iidx:camhook", "failed to trampoline hook_b");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
HRESULT create_d3d_device_manager() {
|
||||
UINT resetToken = 0;
|
||||
IDirect3DDeviceManager9 *pD3DManager = nullptr;
|
||||
|
||||
HRESULT hr = DXVA2CreateDirect3DDeviceManager9(&resetToken, &pD3DManager);
|
||||
if (FAILED(hr)) { goto done; }
|
||||
|
||||
hr = pD3DManager->ResetDevice(device, resetToken);
|
||||
if (FAILED(hr)) { goto done; }
|
||||
|
||||
s_pD3DManager = pD3DManager;
|
||||
(s_pD3DManager)->AddRef();
|
||||
|
||||
done:
|
||||
if (SUCCEEDED(hr)) {
|
||||
log_misc("iidx:camhook", "Created DeviceManager for DXVA");
|
||||
} else {
|
||||
log_warning("iidx:camhook", "Cannot create DXVA DeviceManager: {}", hr);
|
||||
}
|
||||
|
||||
SafeRelease(&pD3DManager);
|
||||
return hr;
|
||||
}
|
||||
|
||||
bool init_local_camera() {
|
||||
HRESULT hr = S_OK;
|
||||
IMFAttributes *pAttributes = nullptr;
|
||||
IMFActivate **ppDevices = nullptr;
|
||||
uint32_t numDevices;
|
||||
|
||||
// Initialize an attribute store to specify enumeration parameters.
|
||||
hr = WrappedMFCreateAttributes(&pAttributes, 1);
|
||||
|
||||
// Ask for source type = video capture devices.
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = pAttributes->SetGUID(
|
||||
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
|
||||
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID
|
||||
);
|
||||
}
|
||||
|
||||
// Enumerate devices.
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = WrappedMFEnumDeviceSources(pAttributes, &ppDevices, &numDevices);
|
||||
}
|
||||
|
||||
log_info("iidx:camhook", "MFEnumDeviceSources returned {} device(s)", numDevices);
|
||||
if (numDevices > 0) {
|
||||
hr = create_d3d_device_manager();
|
||||
}
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
|
||||
// get options
|
||||
std::string top_camera_id("");
|
||||
std::string front_camera_id("");
|
||||
auto options = games::get_options(eamuse_get_game());
|
||||
if (options->at(launcher::Options::IIDXCamHookTopId).is_active()) {
|
||||
top_camera_id = options->at(launcher::Options::IIDXCamHookTopId).value_text();
|
||||
}
|
||||
if (options->at(launcher::Options::IIDXCamHookFrontId).is_active()) {
|
||||
front_camera_id = options->at(launcher::Options::IIDXCamHookFrontId).value_text();
|
||||
}
|
||||
int preferred_top_index = -1;
|
||||
int preferred_front_index = -1;
|
||||
|
||||
log_info("iidx:camhook", "-------- Listing all cameras from MFEnumDeviceSources --------");
|
||||
|
||||
// print camera names first for user
|
||||
for (size_t i = 0; i < numDevices; i++) {
|
||||
// get camera activation
|
||||
auto pActivate = ppDevices[i];
|
||||
|
||||
// print friendly name
|
||||
PWCHAR friendly = nullptr;
|
||||
UINT32 friendly_len = 0;
|
||||
hr = pActivate->GetAllocatedString(
|
||||
MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME,
|
||||
&friendly,
|
||||
&friendly_len);
|
||||
if (SUCCEEDED(hr) && friendly != nullptr) {
|
||||
log_info("iidx:camhook", "Camera {} Name: {}", i, ws2s(friendly));
|
||||
CoTaskMemFree(friendly);
|
||||
friendly = nullptr;
|
||||
}
|
||||
|
||||
// get symlink name
|
||||
PWCHAR symlink = nullptr;
|
||||
UINT32 symlink_len = 0;
|
||||
hr = pActivate->GetAllocatedString(
|
||||
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
|
||||
&symlink,
|
||||
&symlink_len);
|
||||
if (FAILED(hr) || symlink == nullptr) {
|
||||
log_info("iidx:camhook", "Camera {}: failed to get vidcap symlink name", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto symlink_s = ws2s(symlink);
|
||||
log_info("iidx:camhook", "Camera {} ID: {}", i, symlink_s);
|
||||
|
||||
if (!top_camera_id.empty() && (symlink_s.find(top_camera_id) != std::string::npos)) {
|
||||
preferred_top_index = i;
|
||||
} else if (!front_camera_id.empty() && (symlink_s.find(front_camera_id) != std::string::npos)) {
|
||||
preferred_front_index = i;
|
||||
}
|
||||
|
||||
if (symlink != nullptr) {
|
||||
CoTaskMemFree(symlink);
|
||||
symlink = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
log_info("iidx:camhook", "-------- End of cameras from MFEnumDeviceSources --------");
|
||||
|
||||
// pick out preferred top/front cameras
|
||||
|
||||
log_misc("iidx:camhook", "Adding user-preferred cameras");
|
||||
for (size_t i = 0; i < numDevices && LOCAL_CAMERA_LIST.size() < 2; i++) {
|
||||
auto pActivate = ppDevices[i];
|
||||
if ((int)i == preferred_top_index && top_camera == nullptr) {
|
||||
log_misc("iidx:camhook", "Adding user-preferred top camera {} / '{}'", i, top_camera_id);
|
||||
top_camera = add_top_camera(pActivate);
|
||||
} else if ((int)i == preferred_front_index && front_camera == nullptr) {
|
||||
log_misc("iidx:camhook", "Adding user-preferred front camera {} / '{}'", i, front_camera_id);
|
||||
front_camera = add_front_camera(pActivate);
|
||||
}
|
||||
}
|
||||
|
||||
// fill out the rest of cameras
|
||||
log_misc("iidx:camhook", "Adding other cameras");
|
||||
for (size_t i = 0; i < numDevices && LOCAL_CAMERA_LIST.size() < 2; i++) {
|
||||
auto pActivate = ppDevices[i];
|
||||
|
||||
if ((int)i == preferred_top_index || (int)i == preferred_front_index) {
|
||||
// we already tried these above; don't use them here
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!FLIP_CAMS) {
|
||||
// top camera first, then front
|
||||
if (top_camera == nullptr) {
|
||||
top_camera = add_top_camera(pActivate);
|
||||
} else if (front_camera == nullptr) {
|
||||
front_camera = add_front_camera(pActivate);
|
||||
}
|
||||
} else {
|
||||
// front first, then top
|
||||
if (front_camera == nullptr) {
|
||||
front_camera = add_front_camera(pActivate);
|
||||
} else if (top_camera == nullptr) {
|
||||
top_camera = add_top_camera(pActivate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (LOCAL_CAMERA_LIST.size() == 0) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
camera_config_load();
|
||||
if (top_camera) {
|
||||
top_camera->StartCapture();
|
||||
}
|
||||
if (front_camera) {
|
||||
front_camera->StartCapture();
|
||||
}
|
||||
|
||||
CAMERA_READY = true;
|
||||
|
||||
done:
|
||||
SafeRelease(&pAttributes);
|
||||
|
||||
for (DWORD i = 0; i < numDevices; i++) {
|
||||
SafeRelease(&ppDevices[i]);
|
||||
}
|
||||
CoTaskMemFree(ppDevices);
|
||||
|
||||
return SUCCEEDED(hr);
|
||||
}
|
||||
|
||||
IIDXLocalCamera* add_top_camera(IMFActivate* pActivate) {
|
||||
auto top_camera = new IIDXLocalCamera("top", TDJ_CAMERA_PREFER_16_9, pActivate, s_pD3DManager, device, texture_a);
|
||||
if (top_camera->m_initialized) {
|
||||
LOCAL_CAMERA_LIST.push_back(top_camera);
|
||||
} else {
|
||||
top_camera->Release();
|
||||
top_camera = nullptr;
|
||||
}
|
||||
return top_camera;
|
||||
}
|
||||
|
||||
IIDXLocalCamera* add_front_camera(IMFActivate* pActivate) {
|
||||
auto front_camera = new IIDXLocalCamera("front", TDJ_CAMERA_PREFER_16_9, pActivate, s_pD3DManager, device, texture_b);
|
||||
if (front_camera->m_initialized) {
|
||||
LOCAL_CAMERA_LIST.push_back(front_camera);
|
||||
} else {
|
||||
front_camera->Release();
|
||||
front_camera = nullptr;
|
||||
}
|
||||
return front_camera;
|
||||
}
|
||||
|
||||
bool init_camera_hooks() {
|
||||
bool result = find_camera_hooks();
|
||||
if (result) {
|
||||
log_misc(
|
||||
"iidx:camhook",
|
||||
"found hooks:\n hook_a 0x{:x}\n hook_b 0x{:x}\n textures 0x{:x}\n device_offset 0x{:x}",
|
||||
addr_hook_a, addr_hook_b, addr_textures, addr_device_offset);
|
||||
result = create_hooks();
|
||||
if (result) {
|
||||
init_mf_library();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void camera_release() {
|
||||
if (top_camera) {
|
||||
top_camera->Release();
|
||||
top_camera = nullptr;
|
||||
}
|
||||
if (front_camera) {
|
||||
front_camera->Release();
|
||||
front_camera = nullptr;
|
||||
}
|
||||
SafeRelease(&s_pD3DManager);
|
||||
}
|
||||
|
||||
bool camera_config_load() {
|
||||
CAMERA_CONFIG_PATH =
|
||||
fileutils::get_config_file_path("iidx::camhook", "spicetools_camera_control.json");
|
||||
|
||||
try {
|
||||
// read config file
|
||||
std::string config = fileutils::text_read(CAMERA_CONFIG_PATH);
|
||||
if (!config.empty()) {
|
||||
// parse document
|
||||
rapidjson::Document doc;
|
||||
doc.Parse(config.c_str());
|
||||
|
||||
// check parse error
|
||||
auto error = doc.GetParseError();
|
||||
if (error) {
|
||||
log_warning("iidx:camhook", "config parse error: {}", error);
|
||||
return false;
|
||||
}
|
||||
|
||||
// verify root is a dict
|
||||
if (!doc.IsObject()) {
|
||||
log_warning("iidx:camhook", "config not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string root("/sp2x_cameras");
|
||||
auto numCameras = LOCAL_CAMERA_LIST.size();
|
||||
for (size_t i = 0; i < numCameras; i++) {
|
||||
auto *camera = LOCAL_CAMERA_LIST.at(i);
|
||||
auto symLink = camera->GetSymLink();
|
||||
|
||||
const auto cameraNode = rapidjson::Pointer(root + "/" + symLink).Get(doc);
|
||||
if (cameraNode && cameraNode->IsObject()) {
|
||||
log_info("iidx:camhook", "Parsing config for: {}", symLink);
|
||||
|
||||
// Media type
|
||||
auto mediaTypePointer = rapidjson::Pointer(root + "/" + symLink + "/MediaType").Get(doc);
|
||||
if (mediaTypePointer && mediaTypePointer->IsString()) {
|
||||
std::string mediaType = mediaTypePointer->GetString();
|
||||
if (mediaType.length() > 0) {
|
||||
camera->m_selectedMediaTypeDescription = mediaType;
|
||||
camera->m_useAutoMediaType = false;
|
||||
} else {
|
||||
camera->m_useAutoMediaType = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw mode
|
||||
auto drawModePointer = rapidjson::Pointer(root + "/" + symLink + "/DrawMode").Get(doc);
|
||||
if (drawModePointer && drawModePointer->IsString()) {
|
||||
std::string drawModeString = drawModePointer->GetString();
|
||||
for (int j = 0; j < DRAW_MODE_SIZE; j++) {
|
||||
if (DRAW_MODE_LABELS[j].compare(drawModeString) == 0) {
|
||||
camera->m_drawMode = (LocalCameraDrawMode) j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Flip
|
||||
auto flipHorizontalPointer = rapidjson::Pointer(root + "/" + symLink + "/FlipHorizontal").Get(doc);
|
||||
if (flipHorizontalPointer && flipHorizontalPointer->IsBool()) {
|
||||
camera->m_flipHorizontal = flipHorizontalPointer->GetBool();
|
||||
}
|
||||
|
||||
auto flipVerticalPointer = rapidjson::Pointer(root + "/" + symLink + "/FlipVertical").Get(doc);
|
||||
if (flipVerticalPointer && flipVerticalPointer->IsBool()) {
|
||||
camera->m_flipVertical = flipVerticalPointer->GetBool();
|
||||
}
|
||||
|
||||
// Allow manual control
|
||||
auto manualControlPointer = rapidjson::Pointer(root + "/" + symLink + "/AllowManualControl").Get(doc);
|
||||
if (manualControlPointer && manualControlPointer->IsBool() && manualControlPointer->GetBool()) {
|
||||
camera->m_allowManualControl = true;
|
||||
}
|
||||
|
||||
// Camera control
|
||||
// if m_allowManualControl is false, SetCameraControlProp will fail, but run through this code
|
||||
// anyway to exercise parsing code
|
||||
for (int propIndex = 0; propIndex < CAMERA_CONTROL_PROP_SIZE; propIndex++) {
|
||||
std::string label = CAMERA_CONTROL_LABELS[propIndex];
|
||||
CameraControlProp prop = {};
|
||||
camera->GetCameraControlProp(propIndex, &prop);
|
||||
auto valuePointer = rapidjson::Pointer(root + "/" + symLink + "/" + label + "/value").Get(doc);
|
||||
auto flagsPointer = rapidjson::Pointer(root + "/" + symLink + "/" + label + "/flags").Get(doc);
|
||||
if (valuePointer && valuePointer->IsInt() && flagsPointer && flagsPointer->IsInt()) {
|
||||
prop.value = (long)valuePointer->GetInt();
|
||||
prop.valueFlags = (long)flagsPointer->GetInt();
|
||||
camera->SetCameraControlProp(propIndex, prop.value, prop.valueFlags);
|
||||
}
|
||||
|
||||
}
|
||||
log_misc("iidx:camhook", " >> done");
|
||||
} else {
|
||||
log_misc("iidx:camhook", "No previous config for: {}", symLink);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
log_warning("iidx:camhook", "exception occurred while config: {}", e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool camera_config_save() {
|
||||
log_info("iidx:camhook", "saving config");
|
||||
|
||||
// create document
|
||||
rapidjson::Document doc;
|
||||
std::string config = fileutils::text_read(CAMERA_CONFIG_PATH);
|
||||
if (!config.empty()) {
|
||||
doc.Parse(config.c_str());
|
||||
log_misc("iidx:camhook", "existing config file found");
|
||||
}
|
||||
if (!doc.IsObject()) {
|
||||
log_misc("iidx:camhook", "clearing out config file");
|
||||
doc.SetObject();
|
||||
}
|
||||
|
||||
auto numCameras = LOCAL_CAMERA_LIST.size();
|
||||
for (size_t i = 0; i < numCameras; i++) {
|
||||
auto *camera = LOCAL_CAMERA_LIST.at(i);
|
||||
auto symLink = camera->GetSymLink();
|
||||
std::string root("/sp2x_cameras/" + symLink + "/");
|
||||
|
||||
// Media type
|
||||
if (camera->m_useAutoMediaType) {
|
||||
rapidjson::Pointer(root + "MediaType").Set(doc, "");
|
||||
} else {
|
||||
rapidjson::Pointer(root + "MediaType").Set(doc, camera->m_selectedMediaTypeDescription);
|
||||
}
|
||||
|
||||
// Draw Mode
|
||||
rapidjson::Pointer(root + "DrawMode").Set(doc, DRAW_MODE_LABELS[camera->m_drawMode]);
|
||||
|
||||
// Flip
|
||||
rapidjson::Pointer(root + "FlipHorizontal").Set(doc, camera->m_flipHorizontal);
|
||||
rapidjson::Pointer(root + "FlipVertical").Set(doc, camera->m_flipVertical);
|
||||
|
||||
// Manual control
|
||||
rapidjson::Pointer(root + "AllowManualControl").Set(doc, camera->m_allowManualControl);
|
||||
|
||||
// Camera control
|
||||
for (int propIndex = 0; propIndex < CAMERA_CONTROL_PROP_SIZE; propIndex++) {
|
||||
std::string label = CAMERA_CONTROL_LABELS[propIndex];
|
||||
CameraControlProp prop = {};
|
||||
camera->GetCameraControlProp(propIndex, &prop);
|
||||
rapidjson::Pointer(root + label + "/value").Set(doc, (int)prop.value);
|
||||
rapidjson::Pointer(root + label + "/flags").Set(doc, (int)prop.valueFlags);
|
||||
}
|
||||
}
|
||||
|
||||
// build JSON
|
||||
rapidjson::StringBuffer buffer;
|
||||
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);
|
||||
doc.Accept(writer);
|
||||
|
||||
// save to file
|
||||
if (fileutils::write_config_file("iidx::camhook", CAMERA_CONFIG_PATH, buffer.GetString())) {
|
||||
} else {
|
||||
log_warning("iidx:camhook", "unable to save config file");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool camera_config_reset() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#if SPICE64
|
||||
|
||||
#include <vector>
|
||||
#include "games/iidx/local_camera.h"
|
||||
|
||||
namespace games::iidx {
|
||||
extern std::vector<IIDXLocalCamera*> LOCAL_CAMERA_LIST;
|
||||
extern bool CAMERA_READY;
|
||||
|
||||
bool init_local_camera();
|
||||
bool init_camera_hooks();
|
||||
void camera_release();
|
||||
|
||||
bool camera_config_load();
|
||||
bool camera_config_save();
|
||||
bool camera_config_reset();
|
||||
|
||||
IIDXLocalCamera* add_top_camera(IMFActivate* pActivate);
|
||||
IIDXLocalCamera* add_front_camera(IMFActivate* pActivate);
|
||||
}
|
||||
|
||||
#endif
|
||||
+151
-236
@@ -1,26 +1,20 @@
|
||||
// set version to Windows 7 to enable Media Foundation functions
|
||||
#ifdef _WIN32_WINNT
|
||||
#undef _WIN32_WINNT
|
||||
#endif
|
||||
#define _WIN32_WINNT 0x0601
|
||||
|
||||
// turn on C-style COM objects
|
||||
#define CINTERFACE
|
||||
|
||||
#include "iidx.h"
|
||||
|
||||
#include <mfobjects.h>
|
||||
#include <mfidl.h>
|
||||
|
||||
#include "acioemu/handle.h"
|
||||
#include "avs/core.h"
|
||||
#include "avs/game.h"
|
||||
#include "cfg/configurator.h"
|
||||
#include "games/io.h"
|
||||
|
||||
#include "games/iidx/legacy_camera.h"
|
||||
|
||||
#include "hooks/avshook.h"
|
||||
#include "hooks/cfgmgr32hook.h"
|
||||
#include "hooks/devicehook.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#ifdef SPICE64
|
||||
#include "hooks/graphics/nvenc_hook.h"
|
||||
#endif
|
||||
#include "hooks/setupapihook.h"
|
||||
#include "hooks/sleephook.h"
|
||||
#include "launcher/options.h"
|
||||
@@ -31,6 +25,7 @@
|
||||
#include "util/fileutils.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/memutils.h"
|
||||
#include "util/sigscan.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
#include "bi2a.h"
|
||||
@@ -38,9 +33,6 @@
|
||||
#include "ezusb.h"
|
||||
#include "io.h"
|
||||
|
||||
static VTBL_TYPE(IMFActivate, GetAllocatedString) GetAllocatedString_orig = nullptr;
|
||||
|
||||
static decltype(MFEnumDeviceSources) *MFEnumDeviceSources_orig = nullptr;
|
||||
static decltype(RegCloseKey) *RegCloseKey_orig = nullptr;
|
||||
static decltype(RegEnumKeyA) *RegEnumKeyA_orig = nullptr;
|
||||
static decltype(RegOpenKeyA) *RegOpenKeyA_orig = nullptr;
|
||||
@@ -57,12 +49,19 @@ namespace games::iidx {
|
||||
// settings
|
||||
bool FLIP_CAMS = false;
|
||||
bool DISABLE_CAMS = false;
|
||||
bool TDJ_CAMERA = false;
|
||||
bool TDJ_CAMERA_PREFER_16_9 = true;
|
||||
bool TDJ_MODE = false;
|
||||
bool FORCE_720P = false;
|
||||
bool DISABLE_ESPEC_IO = false;
|
||||
bool NATIVE_TOUCH = false;
|
||||
std::optional<std::string> SOUND_OUTPUT_DEVICE = std::nullopt;
|
||||
std::optional<std::string> SOUND_OUTPUT_DEVICE_IN_EFFECT = std::nullopt;
|
||||
std::optional<std::string> ASIO_DRIVER = std::nullopt;
|
||||
uint8_t DIGITAL_TT_SENS = 4;
|
||||
std::optional<std::string> SUBSCREEN_OVERLAY_SIZE = std::nullopt;
|
||||
std::optional<std::string> SCREEN_MODE = std::nullopt;
|
||||
std::optional<std::string> TDJ_CAMERA_OVERRIDE = std::nullopt;
|
||||
|
||||
// states
|
||||
static HKEY real_asio_reg_handle = nullptr;
|
||||
@@ -101,9 +100,25 @@ namespace games::iidx {
|
||||
*phkResult = DEVICE_ASIO_REG_HANDLE;
|
||||
|
||||
log_info("iidx::asio", "replacing '{}' with '{}'", lpSubKey, ASIO_DRIVER.value());
|
||||
|
||||
return RegOpenKeyExA_orig(real_asio_reg_handle, ASIO_DRIVER.value().c_str(), ulOptions, samDesired,
|
||||
const auto result = RegOpenKeyExA_orig(
|
||||
real_asio_reg_handle,
|
||||
ASIO_DRIVER.value().c_str(),
|
||||
ulOptions,
|
||||
samDesired,
|
||||
&real_asio_device_reg_handle);
|
||||
|
||||
if (result != ERROR_SUCCESS) {
|
||||
log_warning(
|
||||
"iidx::asio",
|
||||
"failed to open registry subkey '{}', error=0x{:x}",
|
||||
ASIO_DRIVER.value().c_str(), result);
|
||||
log_warning(
|
||||
"iidx::asio",
|
||||
"due to improper ASIO setting, game will likely fail to launch; double check -iidxasio and the registry",
|
||||
ASIO_DRIVER.value().c_str(), result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,20 +145,20 @@ namespace games::iidx {
|
||||
return RegEnumKeyA_orig(hKey, dwIndex, lpName, cchName);
|
||||
}
|
||||
|
||||
/*
|
||||
* Dirty Workaround for IO Device
|
||||
* Game gets registry object via SetupDiOpenDevRegKey, then looks up "PortName" via RegQueryValueExA
|
||||
* We ignore the first and just cheat on the second.
|
||||
*/
|
||||
static LONG WINAPI RegQueryValueExA_hook(HKEY hKey, LPCTSTR lpValueName, LPDWORD lpReserved, LPDWORD lpType,
|
||||
LPBYTE lpData, LPDWORD lpcbData)
|
||||
{
|
||||
if (lpValueName != nullptr && lpData != nullptr && lpcbData != nullptr) {
|
||||
|
||||
// ASIO hack
|
||||
if (hKey == DEVICE_ASIO_REG_HANDLE && ASIO_DRIVER.has_value()) {
|
||||
log_info("iidx::asio", "RegQueryValueExA({}, \"{}\")", fmt::ptr((void *) hKey), lpValueName);
|
||||
|
||||
if (_stricmp(lpValueName, "Description") == 0) {
|
||||
memcpy(lpData, ASIO_DRIVER.value().c_str(), ASIO_DRIVER.value().size() + 1);
|
||||
// newer iidx does a comparison against hardcoded string "XONAR SOUND CARD(64)" (same as sdvx)
|
||||
// so what's in the registry must be overridden with "XONAR SOUND CARD(64)"
|
||||
// otherwise you end up with this error: M:BMSoundLib: ASIODriver: No such driver
|
||||
memcpy(lpData, ORIGINAL_ASIO_DEVICE_NAME, strlen(ORIGINAL_ASIO_DEVICE_NAME) + 1);
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
} else {
|
||||
@@ -151,6 +166,11 @@ namespace games::iidx {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Dirty Workaround for IO Device
|
||||
* Game gets registry object via SetupDiOpenDevRegKey, then looks up "PortName" via RegQueryValueExA
|
||||
* We ignore the first and just cheat on the second.
|
||||
*/
|
||||
// check for port name lookup
|
||||
if (_stricmp(lpValueName, "PortName") == 0) {
|
||||
const char port[] = "COM2";
|
||||
@@ -172,195 +192,24 @@ namespace games::iidx {
|
||||
return RegCloseKey_orig(hKey);
|
||||
}
|
||||
|
||||
/*
|
||||
* Camera related stuff
|
||||
*/
|
||||
static std::wstring CAMERA0_ID;
|
||||
static std::wstring CAMERA1_ID;
|
||||
|
||||
static HRESULT WINAPI GetAllocatedString_hook(IMFActivate* This, REFGUID guidKey, LPWSTR *ppwszValue,
|
||||
UINT32 *pcchLength)
|
||||
{
|
||||
// call the original
|
||||
HRESULT result = GetAllocatedString_orig(This, guidKey, ppwszValue, pcchLength);
|
||||
|
||||
// log the cam name
|
||||
log_misc("iidx::cam", "obtained {}", ws2s(*ppwszValue));
|
||||
|
||||
// try first camera
|
||||
wchar_t *pwc = nullptr;
|
||||
if (CAMERA0_ID.length() == 23) {
|
||||
pwc = wcsstr(*ppwszValue, CAMERA0_ID.c_str());
|
||||
}
|
||||
|
||||
// try second camera if first wasn't found
|
||||
if (!pwc && CAMERA1_ID.length() == 23) {
|
||||
pwc = wcsstr(*ppwszValue, CAMERA1_ID.c_str());
|
||||
}
|
||||
|
||||
// check if camera could be identified
|
||||
if (pwc) {
|
||||
|
||||
// fake the USB IDs
|
||||
pwc[4] = L'2';
|
||||
pwc[5] = L'8';
|
||||
pwc[6] = L'8';
|
||||
pwc[7] = L'c';
|
||||
|
||||
pwc[13] = L'0';
|
||||
pwc[14] = L'0';
|
||||
pwc[15] = L'0';
|
||||
pwc[16] = L'2';
|
||||
|
||||
pwc[21] = L'0';
|
||||
pwc[22] = L'0';
|
||||
|
||||
log_misc("iidx::cam", "replaced {}", ws2s(*ppwszValue));
|
||||
}
|
||||
|
||||
// return original result
|
||||
return result;
|
||||
}
|
||||
|
||||
static void hook_camera_vtable(IMFActivate *camera) {
|
||||
|
||||
// hook allocated string method for camera identification
|
||||
memutils::VProtectGuard camera_guard(camera->lpVtbl);
|
||||
camera->lpVtbl->GetAllocatedString = GetAllocatedString_hook;
|
||||
}
|
||||
|
||||
static void hook_camera(size_t no, std::wstring camera_id, std::string camera_instance) {
|
||||
|
||||
// logic based on camera no
|
||||
if (no == 0) {
|
||||
|
||||
// don't hook if camera 0 is already hooked
|
||||
if (CAMERA0_ID.length() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// save the camera ID
|
||||
CAMERA0_ID = camera_id;
|
||||
|
||||
// cfgmgr hook
|
||||
CFGMGR32_HOOK_SETTING camera_setting;
|
||||
camera_setting.device_instance = 0xDEADBEEF;
|
||||
camera_setting.parent_instance = ~camera_setting.device_instance;
|
||||
camera_setting.device_id = "USB\\VEN_1022&DEV_7908";
|
||||
camera_setting.device_node_id = "USB\\VID_288C&PID_0002&MI_00\\?&????????&?&????";
|
||||
if (camera_instance.length() == 17) {
|
||||
for (int i = 0; i < 17; i++) {
|
||||
camera_setting.device_node_id[28 + i] = camera_instance[i];
|
||||
}
|
||||
}
|
||||
cfgmgr32hook_add(camera_setting);
|
||||
|
||||
log_info("iidx::cam", "hooked camera 1 @ {}", ws2s(camera_id));
|
||||
}
|
||||
else if (no == 1) {
|
||||
|
||||
// don't hook if camera 1 is already hooked
|
||||
if (CAMERA1_ID.length() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// save the camera ID
|
||||
CAMERA1_ID = camera_id;
|
||||
|
||||
// cfgmgr hook
|
||||
CFGMGR32_HOOK_SETTING camera_setting;
|
||||
camera_setting.device_instance = 0xBEEFDEAD;
|
||||
camera_setting.parent_instance = ~camera_setting.device_instance;
|
||||
camera_setting.device_id = "USB\\VEN_1022&DEV_7914";
|
||||
camera_setting.device_node_id = "USB\\VID_288C&PID_0002&MI_00\\?&????????&?&????";
|
||||
if (camera_instance.length() == 17) {
|
||||
for (int i = 0; i < 17; i++) {
|
||||
camera_setting.device_node_id[28 + i] = camera_instance[i];
|
||||
}
|
||||
}
|
||||
cfgmgr32hook_add(camera_setting);
|
||||
|
||||
log_info("iidx::cam", "hooked camera 2 @ {}", ws2s(camera_id));
|
||||
}
|
||||
}
|
||||
|
||||
static HRESULT WINAPI MFEnumDeviceSources_hook(IMFAttributes *pAttributes, IMFActivate ***pppSourceActivate,
|
||||
UINT32 *pcSourceActivate) {
|
||||
|
||||
// call original function
|
||||
HRESULT result_orig = MFEnumDeviceSources_orig(pAttributes, pppSourceActivate, pcSourceActivate);
|
||||
|
||||
// check for capture devices
|
||||
if (FAILED(result_orig) || !*pcSourceActivate) {
|
||||
return result_orig;
|
||||
}
|
||||
|
||||
// iterate cameras
|
||||
size_t cam_hook_num = 0;
|
||||
for (size_t cam_num = 0; cam_num < *pcSourceActivate && cam_hook_num < 2; cam_num++) {
|
||||
|
||||
// flip
|
||||
size_t cam_num_flipped = cam_num;
|
||||
if (FLIP_CAMS) {
|
||||
cam_num_flipped = *pcSourceActivate - cam_num - 1;
|
||||
}
|
||||
|
||||
// get camera
|
||||
IMFActivate *camera = (*pppSourceActivate)[cam_num_flipped];
|
||||
|
||||
// save original method for later use
|
||||
if (GetAllocatedString_orig == nullptr) {
|
||||
GetAllocatedString_orig = camera->lpVtbl->GetAllocatedString;
|
||||
}
|
||||
|
||||
// hook allocated string method for camera identification
|
||||
hook_camera_vtable(camera);
|
||||
|
||||
// get camera link
|
||||
LPWSTR camera_link_lpwstr;
|
||||
UINT32 camera_link_length;
|
||||
if (SUCCEEDED(GetAllocatedString_orig(
|
||||
camera,
|
||||
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
|
||||
&camera_link_lpwstr,
|
||||
&camera_link_length))) {
|
||||
|
||||
// cut name to make ID
|
||||
std::wstring camera_link_ws = std::wstring(camera_link_lpwstr);
|
||||
std::wstring camera_id = camera_link_ws.substr(8, 23);
|
||||
|
||||
log_info("iidx::cam", "found video capture device: {}", ws2s(camera_link_ws));
|
||||
|
||||
// only support cameras that start with \\?\usb
|
||||
if (!string_begins_with(camera_link_ws, L"\\\\?\\usb")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// get camera instance
|
||||
std::string camera_link = ws2s(camera_link_ws);
|
||||
std::string camera_instance = camera_link.substr(32, 17);
|
||||
|
||||
// hook the camera
|
||||
hook_camera(cam_hook_num, camera_id, camera_instance);
|
||||
|
||||
// increase camera hook number
|
||||
cam_hook_num++;
|
||||
|
||||
} else {
|
||||
log_warning("iidx::cam", "failed to open camera {}", cam_num_flipped);
|
||||
}
|
||||
}
|
||||
|
||||
// return result
|
||||
return result_orig;
|
||||
}
|
||||
|
||||
static bool log_hook(void *user, const std::string &data, logger::Style style, std::string &out) {
|
||||
if (data.empty() || data[0] != '[') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// get rid of the log spam
|
||||
if (data.find(" I:graphic: adapter mode ") != std::string::npos) {
|
||||
out.clear();
|
||||
return true;
|
||||
} else if (data.find(" W:afputils: CDirectX::SetRenderState ") != std::string::npos) {
|
||||
out.clear();
|
||||
return true;
|
||||
} else if (data.find("\" layer ID 0 is not layer ID.") != std::string::npos) {
|
||||
out.clear();
|
||||
return true;
|
||||
} else if (data.find(" W:touch: missing trigger:") != std::string::npos) {
|
||||
out.clear();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -394,7 +243,6 @@ namespace games::iidx {
|
||||
|
||||
void IIDXGame::attach() {
|
||||
Game::attach();
|
||||
|
||||
// IO boards
|
||||
auto options = games::get_options(eamuse_get_game());
|
||||
if (!options->at(launcher::Options::IIDXBIO2FW).value_bool()) {
|
||||
@@ -440,20 +288,24 @@ namespace games::iidx {
|
||||
if (aio != nullptr) {
|
||||
|
||||
// check TDJ mode
|
||||
TDJ_MODE |= fileutils::text_read("C:\\000rom.txt") == "TDJ-JA";
|
||||
TDJ_MODE |= fileutils::text_read("D:\\001rom.txt") == "TDJ";
|
||||
|
||||
// force TDJ mode
|
||||
if (TDJ_MODE) {
|
||||
|
||||
// ensure game starts in the desired mode
|
||||
hooks::avs::set_rom("/c_drv/000rom.txt", "TDJ-JA");
|
||||
hooks::avs::set_rom("/d_drv/001rom.txt", "TDJ");
|
||||
|
||||
// need to hook `avs2-core.dll` so AVS win32fs operations go through rom hook
|
||||
devicehook_init(avs::core::DLL_INSTANCE);
|
||||
|
||||
wintouchemu::FORCE = true;
|
||||
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
||||
wintouchemu::hook_title_ends("beatmania IIDX", "main", avs::game::DLL_INSTANCE);
|
||||
if (!NATIVE_TOUCH) {
|
||||
wintouchemu::FORCE = true;
|
||||
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
||||
wintouchemu::hook_title_ends("beatmania IIDX", "main", avs::game::DLL_INSTANCE);
|
||||
}
|
||||
|
||||
// prevent crash on TDJ mode without correct DLL
|
||||
if (!GetModuleHandle("nvcuda.dll")) {
|
||||
@@ -498,6 +350,8 @@ namespace games::iidx {
|
||||
}
|
||||
}
|
||||
|
||||
apply_audio_hacks();
|
||||
|
||||
// ASIO device hook
|
||||
RegCloseKey_orig = detour::iat_try(
|
||||
"RegCloseKey", RegCloseKey_hook, avs::game::DLL_INSTANCE);
|
||||
@@ -514,22 +368,7 @@ namespace games::iidx {
|
||||
|
||||
// check if cam hook should be enabled
|
||||
if (!DISABLE_CAMS) {
|
||||
|
||||
// camera media framework hook
|
||||
MFEnumDeviceSources_orig = detour::iat_try(
|
||||
"MFEnumDeviceSources", MFEnumDeviceSources_hook, avs::game::DLL_INSTANCE);
|
||||
|
||||
// camera settings
|
||||
SETUPAPI_SETTINGS settings3 {};
|
||||
settings3.class_guid[0] = 0x00000000;
|
||||
settings3.class_guid[1] = 0x00000000;
|
||||
settings3.class_guid[2] = 0x00000000;
|
||||
settings3.class_guid[3] = 0x00000000;
|
||||
const char property3[] = "USB Composite Device";
|
||||
memcpy(settings3.property_devicedesc, property3, sizeof(property3));
|
||||
settings3.property_address[0] = 1;
|
||||
settings3.property_address[1] = 7;
|
||||
setupapihook_add(settings3);
|
||||
init_legacy_camera_hook(FLIP_CAMS);
|
||||
}
|
||||
|
||||
// init cfgmgr32 hooks
|
||||
@@ -545,18 +384,38 @@ namespace games::iidx {
|
||||
SetEnvironmentVariable("CONNECT_CAMERA", "0");
|
||||
}
|
||||
|
||||
if (SCREEN_MODE.has_value()) {
|
||||
SetEnvironmentVariable("SCREEN_MODE", SCREEN_MODE.value().c_str());
|
||||
}
|
||||
|
||||
// windowed subscreen, enabled by default, unless turned off by user
|
||||
auto options = games::get_options(eamuse_get_game());
|
||||
if (GRAPHICS_WINDOWED && !options->at(launcher::Options::spice2x_IIDXNoSub).value_bool()) {
|
||||
GRAPHICS_IIDX_WSUB = true;
|
||||
}
|
||||
|
||||
#ifdef SPICE64
|
||||
this->detect_sound_output_device();
|
||||
|
||||
#endif
|
||||
|
||||
// check bad model name
|
||||
if (!cfg::CONFIGURATOR_STANDALONE && avs::game::is_model("TDJ")) {
|
||||
log_fatal(
|
||||
"iidx",
|
||||
"BAD MODEL NAME ERROR\n\n\n"
|
||||
"!!! model name set to TDJ, this is WRONG and will break your game !!!\n"
|
||||
"If you are trying to boot IIDX with Lightning Model mode, please do the following instead:\n"
|
||||
" Revert your changes to XML file so it says <model __type=\"str\">LDJ</model>\n"
|
||||
" In SpiceCfg, enable 'IIDX TDJ Mode' or provide -iidxtdj flag in command line\n"
|
||||
" Apply any applicable settings / patches / hex edits\n"
|
||||
"!!! !!!\n"
|
||||
"!!! If you are trying to boot IIDX with Lightning Model mode, !!!\n"
|
||||
"!!! please do the following instead: !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! Revert your changes to XML file so it says !!!\n"
|
||||
"!!! <model __type=\"str\">LDJ</model> !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! In SpiceCfg, enable 'IIDX TDJ Mode' or provide -iidxtdj flag !!!\n"
|
||||
"!!! in command line !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! Apply any applicable settings / patches / hex edits !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! model name set to TDJ, this is WRONG and will break your game !!!\n\n\n"
|
||||
);
|
||||
}
|
||||
@@ -577,6 +436,7 @@ namespace games::iidx {
|
||||
"using user-supplied \"{}\" for SOUND_OUTPUT_DEVICE",
|
||||
SOUND_OUTPUT_DEVICE.value());
|
||||
SetEnvironmentVariable("SOUND_OUTPUT_DEVICE", SOUND_OUTPUT_DEVICE.value().c_str());
|
||||
SOUND_OUTPUT_DEVICE_IN_EFFECT = SOUND_OUTPUT_DEVICE;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -607,12 +467,8 @@ namespace games::iidx {
|
||||
device = "asio";
|
||||
}
|
||||
log_info("iidx", "SOUND_OUTPUT_DEVICE set to {}", device);
|
||||
log_info(
|
||||
"iidx",
|
||||
"SOUND_OUTPUT_DEVICE only affects IIDX27+ and ignored by older games. "
|
||||
"If {} is not what you wanted, you can override it with -iidxsounddevice option",
|
||||
device);
|
||||
SetEnvironmentVariable("SOUND_OUTPUT_DEVICE", device);
|
||||
SOUND_OUTPUT_DEVICE_IN_EFFECT = device;
|
||||
}
|
||||
|
||||
uint32_t get_pad() {
|
||||
@@ -864,4 +720,63 @@ namespace games::iidx {
|
||||
bool is_tdj_fhd() {
|
||||
return TDJ_MODE && avs::game::is_ext(2022101900, MAXINT);
|
||||
}
|
||||
|
||||
void apply_audio_hacks() {
|
||||
|
||||
#ifdef SPICE64
|
||||
|
||||
const auto has_XONAR_SOUND_CARD = find_pattern(
|
||||
avs::game::DLL_INSTANCE,
|
||||
"584F4E415220534F554E442043415244", // XONAR SOUND CARD
|
||||
"XXXXXXXXXXXXXXXX",
|
||||
0,
|
||||
0);
|
||||
|
||||
const auto has_SOUND_OUTPUT_DEVICE = find_pattern(
|
||||
avs::game::DLL_INSTANCE,
|
||||
"534F554E445F4F55545055545F444556494345", // SOUND_OUTPUT_DEVICE
|
||||
"XXXXXXXXXXXXXXXXXXX",
|
||||
0,
|
||||
0);
|
||||
|
||||
// attempt to detect ASIO support
|
||||
// 25-26: has neither (no patch needed - WASAPI Exclusive by default)
|
||||
// 27-30: has both (envvar will be respected, ASIO or WASAPI)
|
||||
// 31+: only has XONAR (ASIO by default, signature patch can be used to force WASAPI - for now)
|
||||
|
||||
if (!has_SOUND_OUTPUT_DEVICE && !has_XONAR_SOUND_CARD) {
|
||||
log_info("iidx", "This game only uses WASAPI audio engine");
|
||||
return;
|
||||
}
|
||||
|
||||
if (has_SOUND_OUTPUT_DEVICE && has_XONAR_SOUND_CARD) {
|
||||
log_info("iidx", "This game accepts SOUND_OUTPUT_DEVICE environment variable");
|
||||
return;
|
||||
}
|
||||
|
||||
log_info("iidx", "This game supports ASIO but does not accept SOUND_OUTPUT_DEVICE environment variable");
|
||||
|
||||
if (SOUND_OUTPUT_DEVICE_IN_EFFECT.has_value() && SOUND_OUTPUT_DEVICE_IN_EFFECT.value() == "wasapi") {
|
||||
intptr_t result = replace_pattern(
|
||||
avs::game::DLL_INSTANCE,
|
||||
"FF5008E8????ECFF83780803740D",
|
||||
"??????BB00000000EB169090????",
|
||||
0, 0);
|
||||
|
||||
if (result == 0) {
|
||||
log_warning(
|
||||
"iidx",
|
||||
"Failed to force WASAPI as audio engine using signature matching. "
|
||||
"Unless patches are applied, game will default to ASIO");
|
||||
} else {
|
||||
log_info(
|
||||
"iidx",
|
||||
"Successfully forced WASAPI as audio engine using signature matching @ 0x{:x}.",
|
||||
result);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,14 +8,22 @@
|
||||
namespace games::iidx {
|
||||
|
||||
// settings
|
||||
|
||||
extern bool FLIP_CAMS;
|
||||
extern bool DISABLE_CAMS;
|
||||
extern bool TDJ_CAMERA;
|
||||
extern bool TDJ_CAMERA_PREFER_16_9;
|
||||
extern std::optional<std::string> TDJ_CAMERA_OVERRIDE;
|
||||
|
||||
extern bool TDJ_MODE;
|
||||
extern bool FORCE_720P;
|
||||
extern bool DISABLE_ESPEC_IO;
|
||||
extern bool NATIVE_TOUCH;
|
||||
extern std::optional<std::string> SOUND_OUTPUT_DEVICE;
|
||||
extern std::optional<std::string> ASIO_DRIVER;
|
||||
extern uint8_t DIGITAL_TT_SENS;
|
||||
extern std::optional<std::string> SUBSCREEN_OVERLAY_SIZE;
|
||||
extern std::optional<std::string> SCREEN_MODE;
|
||||
|
||||
// state
|
||||
extern char IIDXIO_LED_TICKER[10];
|
||||
@@ -45,4 +53,5 @@ namespace games::iidx {
|
||||
unsigned char get_slider(uint8_t slider);
|
||||
const char* get_16seg();
|
||||
bool is_tdj_fhd();
|
||||
void apply_audio_hacks();
|
||||
}
|
||||
|
||||
@@ -45,6 +45,18 @@ std::vector<Button> &games::iidx::get_buttons() {
|
||||
return buttons;
|
||||
}
|
||||
|
||||
std::string games::iidx::get_buttons_help() {
|
||||
// keep to max 100 characters wide
|
||||
return
|
||||
" P1 EFCT P2 \n"
|
||||
" VEFX \n"
|
||||
" 2 4 6 2 4 6 \n"
|
||||
" TT 1 3 5 7 1 3 5 7 TT \n"
|
||||
"\n"
|
||||
"For controllers, use Analog tab for turntable (TT)."
|
||||
;
|
||||
}
|
||||
|
||||
std::vector<Analog> &games::iidx::get_analogs() {
|
||||
static std::vector<Analog> analogs;
|
||||
|
||||
|
||||
@@ -158,6 +158,7 @@ namespace games::iidx {
|
||||
|
||||
// getters
|
||||
std::vector<Button> &get_buttons();
|
||||
std::string get_buttons_help();
|
||||
std::vector<Analog> &get_analogs();
|
||||
std::vector<Light> &get_lights();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
#include "games/iidx/legacy_camera.h"
|
||||
|
||||
// set version to Windows 7 to enable Media Foundation functions
|
||||
#ifdef _WIN32_WINNT
|
||||
#undef _WIN32_WINNT
|
||||
#endif
|
||||
#define _WIN32_WINNT 0x0601
|
||||
|
||||
// turn on C-style COM objects
|
||||
#define CINTERFACE
|
||||
|
||||
#include <mfobjects.h>
|
||||
#include <mfidl.h>
|
||||
#include <string>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "cfg/configurator.h"
|
||||
#include "hooks/cfgmgr32hook.h"
|
||||
#include "hooks/setupapihook.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/memutils.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
static VTBL_TYPE(IMFActivate, GetAllocatedString) GetAllocatedString_orig = nullptr;
|
||||
static decltype(MFEnumDeviceSources) *MFEnumDeviceSources_orig = nullptr;
|
||||
|
||||
static bool should_flip_cams = false;
|
||||
|
||||
namespace games::iidx {
|
||||
|
||||
/*
|
||||
* Camera related stuff
|
||||
*/
|
||||
static std::wstring CAMERA0_ID;
|
||||
static std::wstring CAMERA1_ID;
|
||||
|
||||
static HRESULT WINAPI GetAllocatedString_hook(IMFActivate* This, REFGUID guidKey, LPWSTR *ppwszValue,
|
||||
UINT32 *pcchLength)
|
||||
{
|
||||
// call the original
|
||||
HRESULT result = GetAllocatedString_orig(This, guidKey, ppwszValue, pcchLength);
|
||||
|
||||
// log the cam name
|
||||
log_misc("iidx::cam", "obtained {}", ws2s(*ppwszValue));
|
||||
|
||||
// try first camera
|
||||
wchar_t *pwc = nullptr;
|
||||
if (CAMERA0_ID.length() == 23) {
|
||||
pwc = wcsstr(*ppwszValue, CAMERA0_ID.c_str());
|
||||
}
|
||||
|
||||
// try second camera if first wasn't found
|
||||
if (!pwc && CAMERA1_ID.length() == 23) {
|
||||
pwc = wcsstr(*ppwszValue, CAMERA1_ID.c_str());
|
||||
}
|
||||
|
||||
// check if camera could be identified
|
||||
if (pwc) {
|
||||
|
||||
// fake the USB IDs
|
||||
pwc[4] = L'2';
|
||||
pwc[5] = L'8';
|
||||
pwc[6] = L'8';
|
||||
pwc[7] = L'c';
|
||||
|
||||
pwc[13] = L'0';
|
||||
pwc[14] = L'0';
|
||||
pwc[15] = L'0';
|
||||
pwc[16] = L'2';
|
||||
|
||||
pwc[21] = L'0';
|
||||
pwc[22] = L'0';
|
||||
|
||||
log_misc("iidx::cam", "replaced {}", ws2s(*ppwszValue));
|
||||
}
|
||||
|
||||
// return original result
|
||||
return result;
|
||||
}
|
||||
|
||||
static void hook_camera(size_t no, std::wstring camera_id, std::string camera_instance) {
|
||||
|
||||
// logic based on camera no
|
||||
if (no == 0) {
|
||||
|
||||
// don't hook if camera 0 is already hooked
|
||||
if (CAMERA0_ID.length() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// save the camera ID
|
||||
CAMERA0_ID = camera_id;
|
||||
|
||||
// cfgmgr hook
|
||||
CFGMGR32_HOOK_SETTING camera_setting;
|
||||
camera_setting.device_instance = 0xDEADBEEF;
|
||||
camera_setting.parent_instance = ~camera_setting.device_instance;
|
||||
camera_setting.device_id = "USB\\VEN_1022&DEV_7908";
|
||||
camera_setting.device_node_id = "USB\\VID_288C&PID_0002&MI_00\\?&????????&?&????";
|
||||
if (camera_instance.length() == 17) {
|
||||
for (int i = 0; i < 17; i++) {
|
||||
camera_setting.device_node_id[28 + i] = camera_instance[i];
|
||||
}
|
||||
}
|
||||
cfgmgr32hook_add(camera_setting);
|
||||
|
||||
log_info("iidx::cam", "hooked camera 1 @ {}", ws2s(camera_id));
|
||||
}
|
||||
else if (no == 1) {
|
||||
|
||||
// don't hook if camera 1 is already hooked
|
||||
if (CAMERA1_ID.length() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// save the camera ID
|
||||
CAMERA1_ID = camera_id;
|
||||
|
||||
// cfgmgr hook
|
||||
CFGMGR32_HOOK_SETTING camera_setting;
|
||||
camera_setting.device_instance = 0xBEEFDEAD;
|
||||
camera_setting.parent_instance = ~camera_setting.device_instance;
|
||||
camera_setting.device_id = "USB\\VEN_1022&DEV_7914";
|
||||
camera_setting.device_node_id = "USB\\VID_288C&PID_0002&MI_00\\?&????????&?&????";
|
||||
if (camera_instance.length() == 17) {
|
||||
for (int i = 0; i < 17; i++) {
|
||||
camera_setting.device_node_id[28 + i] = camera_instance[i];
|
||||
}
|
||||
}
|
||||
cfgmgr32hook_add(camera_setting);
|
||||
|
||||
log_info("iidx::cam", "hooked camera 2 @ {}", ws2s(camera_id));
|
||||
}
|
||||
}
|
||||
|
||||
static void hook_camera_vtable(IMFActivate *camera) {
|
||||
|
||||
// hook allocated string method for camera identification
|
||||
memutils::VProtectGuard camera_guard(camera->lpVtbl);
|
||||
camera->lpVtbl->GetAllocatedString = GetAllocatedString_hook;
|
||||
}
|
||||
|
||||
static HRESULT WINAPI MFEnumDeviceSources_hook(IMFAttributes *pAttributes, IMFActivate ***pppSourceActivate,
|
||||
UINT32 *pcSourceActivate) {
|
||||
|
||||
// call original function
|
||||
HRESULT result_orig = MFEnumDeviceSources_orig(pAttributes, pppSourceActivate, pcSourceActivate);
|
||||
|
||||
// check for capture devices
|
||||
if (FAILED(result_orig) || !*pcSourceActivate) {
|
||||
return result_orig;
|
||||
}
|
||||
|
||||
// iterate cameras
|
||||
size_t cam_hook_num = 0;
|
||||
for (size_t cam_num = 0; cam_num < *pcSourceActivate && cam_hook_num < 2; cam_num++) {
|
||||
|
||||
// flip
|
||||
size_t cam_num_flipped = cam_num;
|
||||
if (should_flip_cams) {
|
||||
cam_num_flipped = *pcSourceActivate - cam_num - 1;
|
||||
}
|
||||
|
||||
// get camera
|
||||
IMFActivate *camera = (*pppSourceActivate)[cam_num_flipped];
|
||||
|
||||
// save original method for later use
|
||||
if (GetAllocatedString_orig == nullptr) {
|
||||
GetAllocatedString_orig = camera->lpVtbl->GetAllocatedString;
|
||||
}
|
||||
|
||||
// hook allocated string method for camera identification
|
||||
hook_camera_vtable(camera);
|
||||
|
||||
// get camera link
|
||||
LPWSTR camera_link_lpwstr;
|
||||
UINT32 camera_link_length;
|
||||
if (SUCCEEDED(GetAllocatedString_orig(
|
||||
camera,
|
||||
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
|
||||
&camera_link_lpwstr,
|
||||
&camera_link_length))) {
|
||||
|
||||
// cut name to make ID
|
||||
std::wstring camera_link_ws = std::wstring(camera_link_lpwstr);
|
||||
std::wstring camera_id = camera_link_ws.substr(8, 23);
|
||||
|
||||
log_info("iidx::cam", "found video capture device: {}", ws2s(camera_link_ws));
|
||||
|
||||
// only support cameras that start with \\?\usb
|
||||
if (!string_begins_with(camera_link_ws, L"\\\\?\\usb")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// get camera instance
|
||||
std::string camera_link = ws2s(camera_link_ws);
|
||||
std::string camera_instance = camera_link.substr(32, 17);
|
||||
|
||||
// hook the camera
|
||||
hook_camera(cam_hook_num, camera_id, camera_instance);
|
||||
|
||||
// increase camera hook number
|
||||
cam_hook_num++;
|
||||
|
||||
} else {
|
||||
log_warning("iidx::cam", "failed to open camera {}", cam_num_flipped);
|
||||
}
|
||||
}
|
||||
|
||||
// return result
|
||||
return result_orig;
|
||||
}
|
||||
|
||||
void init_legacy_camera_hook(bool flip_cams) {
|
||||
should_flip_cams = flip_cams;
|
||||
|
||||
// camera media framework hook
|
||||
MFEnumDeviceSources_orig = detour::iat_try(
|
||||
"MFEnumDeviceSources", MFEnumDeviceSources_hook, avs::game::DLL_INSTANCE);
|
||||
|
||||
// camera settings
|
||||
SETUPAPI_SETTINGS settings3 {};
|
||||
settings3.class_guid[0] = 0x00000000;
|
||||
settings3.class_guid[1] = 0x00000000;
|
||||
settings3.class_guid[2] = 0x00000000;
|
||||
settings3.class_guid[3] = 0x00000000;
|
||||
const char property3[] = "USB Composite Device";
|
||||
memcpy(settings3.property_devicedesc, property3, sizeof(property3));
|
||||
settings3.property_address[0] = 1;
|
||||
settings3.property_address[1] = 7;
|
||||
setupapihook_add(settings3);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
namespace games::iidx {
|
||||
void init_legacy_camera_hook(bool);
|
||||
}
|
||||
@@ -0,0 +1,872 @@
|
||||
#include "games/iidx/local_camera.h"
|
||||
|
||||
#if SPICE64
|
||||
|
||||
#include "util/logging.h"
|
||||
#include "util/utils.h"
|
||||
#include "mf_wrappers.h"
|
||||
|
||||
std::string CAMERA_CONTROL_LABELS[] = {
|
||||
"Pan",
|
||||
"Tilt",
|
||||
"Roll",
|
||||
"Zoom",
|
||||
"Exposure",
|
||||
"Iris",
|
||||
"Focus"
|
||||
};
|
||||
|
||||
std::string DRAW_MODE_LABELS[] = {
|
||||
"Stretch",
|
||||
"Crop",
|
||||
"Letterbox",
|
||||
"Crop to 4:3",
|
||||
"Letterbox to 4:3",
|
||||
};
|
||||
|
||||
// static HRESULT printTextureLevelDesc(LPDIRECT3DTEXTURE9 texture) {
|
||||
// HRESULT hr = S_OK;
|
||||
// D3DSURFACE_DESC desc;
|
||||
// hr = texture->GetLevelDesc(0, &desc);
|
||||
// log_info("iidx:camhook", "Texture Desc Size: {}x{} Res Type: {} Format: {} Usage: {}", desc.Width, desc.Height, (int) desc.Type, (int) desc.Format, (int) desc.Usage);
|
||||
// return hr;
|
||||
// }
|
||||
|
||||
LONG TARGET_SURFACE_WIDTH = 1280;
|
||||
LONG TARGET_SURFACE_HEIGHT = 720;
|
||||
|
||||
double RATIO_16_9 = 16.0 / 9.0;
|
||||
double RATIO_4_3 = 4.0 / 3.0;
|
||||
|
||||
namespace games::iidx {
|
||||
|
||||
IIDXLocalCamera::IIDXLocalCamera(
|
||||
std::string name,
|
||||
BOOL prefer_16_by_9,
|
||||
IMFActivate *pActivate,
|
||||
IDirect3DDeviceManager9 *pD3DManager,
|
||||
LPDIRECT3DDEVICE9EX device,
|
||||
LPDIRECT3DTEXTURE9 *texture_target
|
||||
):
|
||||
m_nRefCount(1),
|
||||
m_name(name),
|
||||
m_prefer_16_by_9(prefer_16_by_9),
|
||||
m_device(device),
|
||||
m_texture_target(texture_target),
|
||||
m_texture_original(*texture_target)
|
||||
{
|
||||
InitializeCriticalSection(&m_critsec);
|
||||
|
||||
HRESULT hr = S_OK;
|
||||
IMFAttributes *pAttributes = nullptr;
|
||||
|
||||
EnterCriticalSection(&m_critsec);
|
||||
|
||||
log_info("iidx:camhook", "[{}] Creating camera", m_name);
|
||||
|
||||
// Get friendly name for log purposes
|
||||
WCHAR *pwszFriendlyName = nullptr;
|
||||
UINT32 m_cchFriendlyName = 0;
|
||||
hr = pActivate->GetAllocatedString(
|
||||
MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME,
|
||||
&pwszFriendlyName,
|
||||
&m_cchFriendlyName
|
||||
);
|
||||
if (SUCCEEDED(hr) && pwszFriendlyName != nullptr) {
|
||||
log_misc("iidx:camhook", "[{}] Name: {}", m_name, ws2s(pwszFriendlyName));
|
||||
m_friendly_name = ws2s(pwszFriendlyName);
|
||||
CoTaskMemFree(pwszFriendlyName);
|
||||
pwszFriendlyName = nullptr;
|
||||
}
|
||||
|
||||
// Retrive symlink of Camera for control configurations
|
||||
hr = pActivate->GetAllocatedString(
|
||||
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
|
||||
&m_pwszSymbolicLink,
|
||||
&m_cchSymbolicLink
|
||||
);
|
||||
if (FAILED(hr)) { goto done; }
|
||||
log_misc("iidx:camhook", "[{}] Symlink: {}", m_name, GetSymLink());
|
||||
|
||||
// Create the media source object.
|
||||
hr = pActivate->ActivateObject(IID_PPV_ARGS(&m_pSource));
|
||||
if (FAILED(hr)) { goto done; }
|
||||
|
||||
// Retain reference to the camera
|
||||
m_pSource->AddRef();
|
||||
log_misc("iidx:camhook", "[{}] Activated", m_name);
|
||||
|
||||
// Create an attribute store to hold initialization settings.
|
||||
hr = WrappedMFCreateAttributes(&pAttributes, 2);
|
||||
if (FAILED(hr)) { goto done; }
|
||||
|
||||
hr = pAttributes->SetUnknown(MF_SOURCE_READER_D3D_MANAGER, pD3DManager);
|
||||
if (FAILED(hr)) { goto done; }
|
||||
|
||||
hr = pAttributes->SetUINT32(MF_SOURCE_READER_DISABLE_DXVA, FALSE);
|
||||
if (FAILED(hr)) { goto done; }
|
||||
|
||||
// TODO: Color space conversion
|
||||
// if (SUCCEEDED(hr)) {
|
||||
// hr = pAttributes->SetUINT32(MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING, TRUE);
|
||||
// }
|
||||
// if (SUCCEEDED(hr)) {
|
||||
// hr = pAttributes->SetUINT32(MF_READWRITE_DISABLE_CONVERTERS, FALSE);
|
||||
// }
|
||||
|
||||
// Create the source reader.
|
||||
hr = WrappedMFCreateSourceReaderFromMediaSource(
|
||||
m_pSource,
|
||||
pAttributes,
|
||||
&m_pSourceReader
|
||||
);
|
||||
if (FAILED(hr)) { goto done; }
|
||||
log_misc("iidx:camhook", "[{}] Created source reader", m_name);
|
||||
|
||||
hr = InitTargetTexture();
|
||||
if (FAILED(hr)) { goto done; }
|
||||
|
||||
// Camera should be still usable even if camera control is not supported
|
||||
InitCameraControl();
|
||||
|
||||
done:
|
||||
if (SUCCEEDED(hr)) {
|
||||
m_initialized = true;
|
||||
log_misc("iidx:camhook", "[{}] Initialized", m_name);
|
||||
} else {
|
||||
log_warning("iidx:camhook", "[{}] Failed to create camera: {}", m_name, hr);
|
||||
}
|
||||
SafeRelease(&pAttributes);
|
||||
LeaveCriticalSection(&m_critsec);
|
||||
}
|
||||
|
||||
HRESULT IIDXLocalCamera::StartCapture() {
|
||||
HRESULT hr = S_OK;
|
||||
IMFMediaType *pType = nullptr;
|
||||
|
||||
if (!m_initialized) {
|
||||
log_warning("iidx:camhook", "[{}] Camera not initialized", m_name);
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
// Try to find a suitable output type.
|
||||
log_misc("iidx:camhook", "[{}] Find best media type", m_name);
|
||||
UINT32 bestWidth = 0;
|
||||
double bestFrameRate = 0;
|
||||
|
||||
// The loop should terminate by MF_E_NO_MORE_TYPES
|
||||
// Adding a hard limit just in case
|
||||
for (DWORD i = 0; i < 1000; i++) {
|
||||
hr = m_pSourceReader->GetNativeMediaType(
|
||||
(DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM,
|
||||
i,
|
||||
&pType
|
||||
);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
if (hr != MF_E_NO_MORE_TYPES) {
|
||||
log_warning("iidx:camhook", "[{}] Cannot get media type {} {}", m_name, i, hr);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
hr = TryMediaType(pType, &bestWidth, &bestFrameRate);
|
||||
if (SUCCEEDED(hr)) {
|
||||
MediaTypeInfo info = GetMediaTypeInfo(pType);
|
||||
m_mediaTypeInfos.push_back(info);
|
||||
if (hr == S_OK) {
|
||||
m_pAutoMediaType = pType;
|
||||
}
|
||||
} else {
|
||||
// Invalid media type (e.g. no conversion function)
|
||||
SafeRelease(&pType);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort available media types
|
||||
std::sort(m_mediaTypeInfos.begin(), m_mediaTypeInfos.end(), [](const MediaTypeInfo &a, const MediaTypeInfo &b) {
|
||||
if (a.width != b.width) {
|
||||
return a.width > b.width;
|
||||
}
|
||||
if (a.height != b.height) {
|
||||
return a.height > b.height;
|
||||
}
|
||||
if (a.frameRate != b.frameRate) {
|
||||
return (int)a.frameRate > (int)b.frameRate;
|
||||
}
|
||||
return a.subtype.Data1 > b.subtype.Data1;
|
||||
});
|
||||
|
||||
if (!m_pAutoMediaType) {
|
||||
m_pAutoMediaType = m_mediaTypeInfos.front().p_mediaType;
|
||||
}
|
||||
|
||||
IMFMediaType *pSelectedMediaType = nullptr;
|
||||
|
||||
// Find media type specified by user configurations
|
||||
if (!m_useAutoMediaType && m_selectedMediaTypeDescription.length() > 0) {
|
||||
log_info("iidx:camhook", "[{}] Use media type from config {}", m_name, m_selectedMediaTypeDescription);
|
||||
auto it = std::find_if(m_mediaTypeInfos.begin(), m_mediaTypeInfos.end(), [this](const MediaTypeInfo &item){
|
||||
return item.description.compare(this->m_selectedMediaTypeDescription) == 0;
|
||||
});
|
||||
if (it != m_mediaTypeInfos.end()) {
|
||||
pSelectedMediaType = (*it).p_mediaType;
|
||||
}
|
||||
}
|
||||
|
||||
hr = S_OK;
|
||||
|
||||
if (!pSelectedMediaType) {
|
||||
pSelectedMediaType = m_pAutoMediaType;
|
||||
}
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = ChangeMediaType(pSelectedMediaType);
|
||||
}
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
log_info("iidx:camhook", "[{}] Creating thread", m_name);
|
||||
CreateThread();
|
||||
}
|
||||
return hr;
|
||||
}
|
||||
|
||||
HRESULT IIDXLocalCamera::ChangeMediaType(IMFMediaType *pType) {
|
||||
HRESULT hr = S_OK;
|
||||
MediaTypeInfo info = GetMediaTypeInfo(pType);
|
||||
log_info("iidx:camhook", "[{}] Changing media type: {}", m_name, info.description);
|
||||
|
||||
auto it = std::find_if(m_mediaTypeInfos.begin(), m_mediaTypeInfos.end(), [pType](const MediaTypeInfo &item) {
|
||||
return item.p_mediaType == pType;
|
||||
});
|
||||
m_selectedMediaTypeIndex = it - m_mediaTypeInfos.begin();
|
||||
m_selectedMediaTypeDescription = info.description;
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = m_pSourceReader->SetCurrentMediaType(
|
||||
(DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM,
|
||||
NULL,
|
||||
pType
|
||||
);
|
||||
}
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
m_cameraWidth = info.width;
|
||||
m_cameraHeight = info.height;
|
||||
UpdateDrawRect();
|
||||
}
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
void IIDXLocalCamera::UpdateDrawRect() {
|
||||
double cameraRatio = (double)m_cameraWidth / m_cameraHeight;
|
||||
|
||||
RECT cameraRect = {0, 0, m_cameraWidth, m_cameraHeight};
|
||||
RECT targetRect = {0, 0, TARGET_SURFACE_WIDTH, TARGET_SURFACE_HEIGHT};
|
||||
|
||||
switch (m_drawMode) {
|
||||
case DrawModeStretch: {
|
||||
CopyRect(&m_rcSource, &cameraRect);
|
||||
CopyRect(&m_rcDest, &targetRect);
|
||||
break;
|
||||
}
|
||||
case DrawModeCrop: {
|
||||
if (cameraRatio > RATIO_16_9) {
|
||||
// take full source height, crop left/right
|
||||
LONG croppedWidth = m_cameraHeight * RATIO_16_9;
|
||||
m_rcSource.left = (LONG)(m_cameraWidth - croppedWidth) / 2;
|
||||
m_rcSource.top = 0;
|
||||
m_rcSource.right = m_rcSource.left + croppedWidth;
|
||||
m_rcSource.bottom = m_cameraHeight;
|
||||
} else {
|
||||
// take full source width, crop top/bottom
|
||||
LONG croppedHeight = m_cameraWidth / RATIO_16_9;
|
||||
m_rcSource.left = 0;
|
||||
m_rcSource.top = (LONG)(m_cameraHeight - croppedHeight) / 2;
|
||||
m_rcSource.right = m_cameraWidth;
|
||||
m_rcSource.bottom = m_rcSource.top + croppedHeight;
|
||||
}
|
||||
CopyRect(&m_rcDest, &targetRect);
|
||||
break;
|
||||
}
|
||||
case DrawModeLetterbox: {
|
||||
CopyRect(&m_rcSource, &cameraRect);
|
||||
if (cameraRatio > RATIO_16_9) {
|
||||
// take full dest width, empty top/bottom
|
||||
LONG boxedHeight = TARGET_SURFACE_WIDTH / cameraRatio;
|
||||
m_rcDest.left = 0;
|
||||
m_rcDest.top = (LONG)(TARGET_SURFACE_HEIGHT - boxedHeight) / 2;
|
||||
m_rcDest.right = TARGET_SURFACE_WIDTH;
|
||||
m_rcDest.bottom = m_rcDest.top + boxedHeight;
|
||||
} else {
|
||||
// take full dest height, empty top/bottom
|
||||
LONG boxedWidth = TARGET_SURFACE_HEIGHT * cameraRatio;
|
||||
m_rcDest.left = (LONG)(TARGET_SURFACE_WIDTH - boxedWidth) / 2;
|
||||
m_rcDest.top = 0;
|
||||
m_rcDest.right = m_rcDest.left + boxedWidth;
|
||||
m_rcDest.bottom = TARGET_SURFACE_HEIGHT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DrawModeCrop4_3: {
|
||||
if (cameraRatio > RATIO_4_3) {
|
||||
// take full source height, crop left/right
|
||||
LONG croppedWidth = m_cameraHeight * RATIO_4_3;
|
||||
m_rcSource.left = (LONG)(m_cameraWidth - croppedWidth) / 2;
|
||||
m_rcSource.top = 0;
|
||||
m_rcSource.right = m_rcSource.left + croppedWidth;
|
||||
m_rcSource.bottom = m_cameraHeight;
|
||||
} else {
|
||||
// take full source width, crop top/bottom
|
||||
LONG croppedHeight = m_cameraWidth / RATIO_4_3;
|
||||
m_rcSource.left = 0;
|
||||
m_rcSource.top = (LONG)(m_cameraHeight - croppedHeight) / 2;
|
||||
m_rcSource.right = m_cameraWidth;
|
||||
m_rcSource.bottom = m_rcSource.top + croppedHeight;
|
||||
}
|
||||
CopyRect(&m_rcDest, &targetRect);
|
||||
break;
|
||||
}
|
||||
case DrawModeLetterbox4_3: {
|
||||
CopyRect(&m_rcSource, &cameraRect);
|
||||
if (cameraRatio > RATIO_4_3) {
|
||||
// take full dest width, empty top/bottom
|
||||
LONG boxedHeight = TARGET_SURFACE_HEIGHT / RATIO_4_3;
|
||||
m_rcDest.left = 0;
|
||||
m_rcDest.top = (LONG)(TARGET_SURFACE_HEIGHT - boxedHeight) / 2;
|
||||
m_rcDest.right = TARGET_SURFACE_WIDTH;
|
||||
m_rcDest.bottom = m_rcDest.top + boxedHeight;
|
||||
} else {
|
||||
// take full dest height, empty top/bottom
|
||||
LONG boxedWidth = TARGET_SURFACE_WIDTH * RATIO_4_3;
|
||||
m_rcDest.left = (LONG)(TARGET_SURFACE_WIDTH - boxedWidth) / 2;
|
||||
m_rcDest.top = 0;
|
||||
m_rcDest.right = m_rcDest.left + boxedWidth;
|
||||
m_rcDest.bottom = TARGET_SURFACE_HEIGHT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ensure the rects are valid
|
||||
IntersectRect(&m_rcSource, &m_rcSource, &cameraRect);
|
||||
IntersectRect(&m_rcDest, &m_rcDest, &targetRect);
|
||||
|
||||
log_info(
|
||||
"iidx:camhook", "[{}] Update draw rect mode={} src=({}, {}, {}, {}) dest=({}, {}, {}, {})",
|
||||
m_name,
|
||||
DRAW_MODE_LABELS[m_drawMode],
|
||||
m_rcSource.left,
|
||||
m_rcSource.top,
|
||||
m_rcSource.right,
|
||||
m_rcSource.bottom,
|
||||
m_rcDest.left,
|
||||
m_rcDest.top,
|
||||
m_rcDest.right,
|
||||
m_rcDest.bottom
|
||||
);
|
||||
|
||||
m_device->ColorFill(m_pDestSurf, &targetRect, D3DCOLOR_XRGB(0, 0, 0));
|
||||
}
|
||||
|
||||
void IIDXLocalCamera::CreateThread() {
|
||||
// Create thread
|
||||
m_drawThread = new std::thread([this]() {
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
|
||||
|
||||
double accumulator = 0.0;
|
||||
while (this->m_active) {
|
||||
this->Render();
|
||||
double frameTimeMicroSec = (1000000.0 / this->m_frameRate);
|
||||
int floorFrameTimeMicroSec = floor(frameTimeMicroSec);
|
||||
// This maybe an overkill but who knows
|
||||
accumulator += (frameTimeMicroSec - floorFrameTimeMicroSec);
|
||||
if (accumulator > 1.0) {
|
||||
accumulator -= 1.0;
|
||||
floorFrameTimeMicroSec += 1;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(floorFrameTimeMicroSec));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
LPDIRECT3DTEXTURE9 IIDXLocalCamera::GetTexture() {
|
||||
return m_texture;
|
||||
}
|
||||
|
||||
IAMCameraControl* IIDXLocalCamera::GetCameraControl() {
|
||||
return m_pCameraControl;
|
||||
}
|
||||
|
||||
HRESULT IIDXLocalCamera::InitCameraControl() {
|
||||
HRESULT hr = S_OK;
|
||||
|
||||
log_misc("iidx:camhook", "[{}] Init camera control", m_name);
|
||||
|
||||
hr = m_pSource->QueryInterface(IID_IAMCameraControl, (void**)&m_pCameraControl);
|
||||
if (FAILED(hr)) {
|
||||
// The device does not support IAMCameraControl
|
||||
log_warning("iidx:camhook", "[{}] Camera control not supported", m_name);
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < CAMERA_CONTROL_PROP_SIZE; i++) {
|
||||
long minValue = 0;
|
||||
long maxValue = 0;
|
||||
long delta = 0;
|
||||
long defaultValue = 0;
|
||||
long defFlags = 0;
|
||||
long value = 0;
|
||||
long valueFlags = 0;
|
||||
|
||||
m_pCameraControl->GetRange(
|
||||
i,
|
||||
&minValue,
|
||||
&maxValue,
|
||||
&delta,
|
||||
&defaultValue,
|
||||
&defFlags
|
||||
);
|
||||
m_pCameraControl->Get(
|
||||
i,
|
||||
&value,
|
||||
&valueFlags
|
||||
);
|
||||
m_controlProps.push_back({
|
||||
minValue,
|
||||
maxValue,
|
||||
delta,
|
||||
defaultValue,
|
||||
defFlags,
|
||||
value,
|
||||
valueFlags,
|
||||
});
|
||||
|
||||
CameraControlProp prop = m_controlProps.at(i);
|
||||
|
||||
log_misc(
|
||||
"iidx:camhook", "[{}] >> {} range=({}, {}) default={} delta={} dFlags={} value={} vFlags={}",
|
||||
m_name,
|
||||
CAMERA_CONTROL_LABELS[i],
|
||||
prop.minValue, prop.maxValue,
|
||||
prop.defaultValue,
|
||||
prop.delta,
|
||||
prop.defFlags,
|
||||
prop.value,
|
||||
prop.valueFlags
|
||||
);
|
||||
}
|
||||
|
||||
m_controlOptionsInitialized = true;
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
HRESULT IIDXLocalCamera::GetCameraControlProp(int index, CameraControlProp *pProp) {
|
||||
if (!m_controlOptionsInitialized) {
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
auto targetProp = m_controlProps.at(index);
|
||||
|
||||
pProp->minValue = targetProp.minValue;
|
||||
pProp->maxValue = targetProp.maxValue;
|
||||
pProp->defaultValue = targetProp.defaultValue;
|
||||
pProp->delta = targetProp.delta;
|
||||
pProp->defFlags = targetProp.defFlags;
|
||||
pProp->value = targetProp.value;
|
||||
pProp->valueFlags = targetProp.valueFlags;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT IIDXLocalCamera::SetCameraControlProp(int index, long value, long flags) {
|
||||
if (!m_controlOptionsInitialized || !m_allowManualControl) {
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
if (index < 0 || index >= CAMERA_CONTROL_PROP_SIZE) {
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
|
||||
auto targetProp = &(m_controlProps.at(index));
|
||||
HRESULT hr = m_pCameraControl->Set(index, value, flags);
|
||||
if (SUCCEEDED(hr)) {
|
||||
m_pCameraControl->Get(
|
||||
index,
|
||||
&targetProp->value,
|
||||
&targetProp->valueFlags
|
||||
);
|
||||
}
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
HRESULT IIDXLocalCamera::ResetCameraControlProps() {
|
||||
log_info("iidx:camhook", "[{}] Reset camera control", m_name);
|
||||
for (size_t i = 0; i < CAMERA_CONTROL_PROP_SIZE; i++) {
|
||||
CameraControlProp prop = m_controlProps.at(i);
|
||||
SetCameraControlProp(i, prop.defaultValue, prop.defFlags);
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
std::string IIDXLocalCamera::GetName() {
|
||||
return m_name;
|
||||
}
|
||||
|
||||
std::string IIDXLocalCamera::GetFriendlyName() {
|
||||
return m_friendly_name;
|
||||
}
|
||||
|
||||
std::string IIDXLocalCamera::GetSymLink() {
|
||||
if (!m_pwszSymbolicLink) {
|
||||
return "(unknown)";
|
||||
}
|
||||
return ws2s(m_pwszSymbolicLink);
|
||||
}
|
||||
|
||||
MediaTypeInfo IIDXLocalCamera::GetMediaTypeInfo(IMFMediaType *pType) {
|
||||
MediaTypeInfo info = {};
|
||||
HRESULT hr = S_OK;
|
||||
MFRatio frameRate = { 0, 0 };
|
||||
|
||||
info.p_mediaType = pType;
|
||||
|
||||
// Find the video subtype.
|
||||
hr = pType->GetGUID(MF_MT_SUBTYPE, &info.subtype);
|
||||
if (FAILED(hr)) { goto done; }
|
||||
|
||||
// Get the frame size.
|
||||
hr = MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, &info.width, &info.height);
|
||||
if (FAILED(hr)) { goto done; }
|
||||
|
||||
// Get frame rate
|
||||
hr = MFGetAttributeRatio(
|
||||
pType,
|
||||
MF_MT_FRAME_RATE,
|
||||
(UINT32*)&frameRate.Numerator,
|
||||
(UINT32*)&frameRate.Denominator
|
||||
);
|
||||
if (FAILED(hr)) { goto done; }
|
||||
|
||||
info.frameRate = frameRate.Numerator / frameRate.Denominator;
|
||||
|
||||
info.description = fmt::format(
|
||||
"{}x{} @{}FPS {}",
|
||||
info.width,
|
||||
info.height,
|
||||
(int)info.frameRate,
|
||||
GetVideoFormatName(info.subtype)
|
||||
);
|
||||
done:
|
||||
return info;
|
||||
}
|
||||
|
||||
std::string IIDXLocalCamera::GetVideoFormatName(GUID subtype) {
|
||||
if (subtype == MFVideoFormat_YUY2) {
|
||||
return "YUY2";
|
||||
}
|
||||
|
||||
if (subtype == MFVideoFormat_NV12) {
|
||||
return "NV12";
|
||||
}
|
||||
|
||||
if (subtype == MFVideoFormat_MJPG) {
|
||||
return "MJPG";
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return values:
|
||||
* S_OK: this is a "better" media type than the existing one
|
||||
* S_FALSE: valid media type, but not "better"
|
||||
* E_*: invalid meia type
|
||||
*/
|
||||
HRESULT IIDXLocalCamera::TryMediaType(IMFMediaType *pType, UINT32 *pBestWidth, double *pBestFrameRate) {
|
||||
HRESULT hr = S_OK;
|
||||
UINT32 width = 0, height = 0;
|
||||
GUID subtype = { 0, 0, 0, 0 };
|
||||
MFRatio frameRate = { 0, 0 };
|
||||
|
||||
hr = pType->GetGUID(MF_MT_SUBTYPE, &subtype);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
log_warning("iidx:camhook", "[{}] Failed to get subtype: {}", m_name, hr);
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, &width, &height);
|
||||
if (FAILED(hr)) {
|
||||
log_warning("iidx:camhook", "[{}] Failed to get frame size: {}", m_name, hr);
|
||||
return hr;
|
||||
}
|
||||
|
||||
// Only support format with converters
|
||||
// TODO: verify conversion support with DXVA
|
||||
if (subtype != MFVideoFormat_YUY2 && subtype != MFVideoFormat_NV12) {
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
// Frame rate
|
||||
hr = MFGetAttributeRatio(
|
||||
pType,
|
||||
MF_MT_FRAME_RATE,
|
||||
(UINT32*)&frameRate.Numerator,
|
||||
(UINT32*)&frameRate.Denominator
|
||||
);
|
||||
if (FAILED(hr)) {
|
||||
log_warning("iidx:camhook", "[{}] Failed to get frame rate: {}", m_name, hr);
|
||||
return hr;
|
||||
}
|
||||
double frameRateValue = frameRate.Numerator / frameRate.Denominator;
|
||||
|
||||
// Filter by aspect ratio
|
||||
auto aspect_ratio = 4.f / 3.f;
|
||||
if (m_prefer_16_by_9) {
|
||||
aspect_ratio = 16.f / 9.f;
|
||||
}
|
||||
if (fabs((height * aspect_ratio) - width) > 0.01f) {
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
// If we have 1280x720 already, only try for better frame rate
|
||||
if ((*pBestWidth >= (UINT32)TARGET_SURFACE_WIDTH) && (width > *pBestWidth) && (frameRateValue < *pBestFrameRate)) {
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
// Check if this format has better resolution / frame rate
|
||||
if ((width > *pBestWidth) || (width >= (UINT32)TARGET_SURFACE_WIDTH && frameRateValue >= *pBestFrameRate)) {
|
||||
// log_misc(
|
||||
// "iidx:camhook", "Better media type {} ({}x{}) @({} FPS)",
|
||||
// GetVideoFormatName(subtype),
|
||||
// width,
|
||||
// height,
|
||||
// (int)frameRateValue
|
||||
// );
|
||||
|
||||
*pBestWidth = width;
|
||||
*pBestFrameRate = frameRateValue;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
HRESULT IIDXLocalCamera::InitTargetTexture() {
|
||||
HRESULT hr = S_OK;
|
||||
|
||||
// Create a new destination texture
|
||||
hr = m_device->CreateTexture(TARGET_SURFACE_WIDTH, TARGET_SURFACE_HEIGHT, 1, D3DUSAGE_RENDERTARGET, D3DFMT_X8R8G8B8, D3DPOOL_DEFAULT, &m_texture, NULL);
|
||||
if (FAILED(hr)) { goto done; }
|
||||
|
||||
// Create a D3D9 surface for the destination texture so that camera sample can be drawn onto it
|
||||
hr = m_texture->GetSurfaceLevel(0, &m_pDestSurf);
|
||||
if (FAILED(hr)) { goto done; }
|
||||
|
||||
// Make the game use this new texture as camera stream source
|
||||
*m_texture_target = m_texture;
|
||||
m_active = TRUE;
|
||||
|
||||
// Create texture for colour conversion
|
||||
hr = m_device->CreateTexture(TARGET_SURFACE_WIDTH, TARGET_SURFACE_HEIGHT, 1, D3DUSAGE_RENDERTARGET, D3DFMT_X8R8G8B8, D3DPOOL_DEFAULT, &m_conversionTexture, NULL);
|
||||
if (FAILED(hr)) { goto done; }
|
||||
hr = m_conversionTexture->GetSurfaceLevel(0, &m_pConversionSurf);
|
||||
if (FAILED(hr)) { goto done; }
|
||||
|
||||
// Create texture for transformation
|
||||
hr = m_device->CreateTexture(TARGET_SURFACE_WIDTH, TARGET_SURFACE_HEIGHT, 1, D3DUSAGE_DYNAMIC, D3DFMT_X8R8G8B8, D3DPOOL_DEFAULT, &m_transformTexture, NULL);
|
||||
if (FAILED(hr)) { goto done; }
|
||||
hr = m_transformTexture->GetSurfaceLevel(0, &m_pTransformSurf);
|
||||
if (FAILED(hr)) { goto done; }
|
||||
|
||||
// Create texture for transformation result so that we don't have to screw our brain doing in-memory flipping
|
||||
hr = m_device->CreateTexture(TARGET_SURFACE_WIDTH, TARGET_SURFACE_HEIGHT, 1, D3DUSAGE_DYNAMIC, D3DFMT_X8R8G8B8, D3DPOOL_DEFAULT, &m_transformResultTexture, NULL);
|
||||
if (FAILED(hr)) { goto done; }
|
||||
hr = m_transformResultTexture->GetSurfaceLevel(0, &m_pTransformResultSurf);
|
||||
if (FAILED(hr)) { goto done; }
|
||||
|
||||
// printTextureLevelDesc(m_texture);
|
||||
// printTextureLevelDesc(m_texture_original);
|
||||
|
||||
done:
|
||||
if (SUCCEEDED(hr)) {
|
||||
log_misc("iidx:camhook", "[{}] Created texture", m_name);
|
||||
} else {
|
||||
log_warning("iidx:camhook", "[{}] Failed to create texture: {}", m_name, hr);
|
||||
}
|
||||
return hr;
|
||||
}
|
||||
|
||||
HRESULT IIDXLocalCamera::FlushDrawCommands() {
|
||||
IDirect3DQuery9* pEventQuery = nullptr;
|
||||
// It is necessary to flush the command queue
|
||||
// or the data is not ready for the receiver to read.
|
||||
// Adapted from : https://msdn.microsoft.com/en-us/library/windows/desktop/bb172234%28v=vs.85%29.aspx
|
||||
// Also see : http://www.ogre3d.org/forums/viewtopic.php?f=5&t=50486
|
||||
m_device->CreateQuery(D3DQUERYTYPE_EVENT, &pEventQuery);
|
||||
if (pEventQuery) {
|
||||
pEventQuery->Issue(D3DISSUE_END);
|
||||
while (S_FALSE == pEventQuery->GetData(NULL, 0, D3DGETDATA_FLUSH));
|
||||
pEventQuery->Release(); // Must be released or causes a leak and reference count increment
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT IIDXLocalCamera::DrawSample(IMFMediaBuffer *pSrcBuffer) {
|
||||
if (!m_active) {
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
// snap variables now so they don't change while inside the critical section
|
||||
const auto flip_h = m_flipHorizontal;
|
||||
const auto flip_v = m_flipVertical;
|
||||
|
||||
EnterCriticalSection(&m_critsec);
|
||||
|
||||
HRESULT hr = S_OK;
|
||||
IDirect3DSurface9 *pCameraSurf = NULL;
|
||||
|
||||
hr = WrappedMFGetService(pSrcBuffer, MR_BUFFER_SERVICE, IID_PPV_ARGS(&pCameraSurf));
|
||||
|
||||
if (flip_h || flip_v) {
|
||||
|
||||
// Stretch Camera content to texture and perform color space conversion
|
||||
hr = m_device->StretchRect(pCameraSurf, &m_rcSource, m_pConversionSurf, &m_rcDest, D3DTEXF_LINEAR);
|
||||
|
||||
// Copy converted camera content to dynamic texture for vertical/horizontal flipping
|
||||
hr = m_device->StretchRect(m_pConversionSurf, NULL, m_pTransformSurf, NULL, D3DTEXF_NONE);
|
||||
|
||||
// Transform
|
||||
D3DLOCKED_RECT srcLockedRect;
|
||||
hr = m_pTransformSurf->LockRect(&srcLockedRect, NULL, D3DLOCK_NOSYSLOCK | D3DLOCK_READONLY);
|
||||
D3DLOCKED_RECT destLockedRect;
|
||||
hr = m_pTransformResultSurf->LockRect(&destLockedRect, NULL, D3DLOCK_NOSYSLOCK);
|
||||
|
||||
BYTE* pSrc = (BYTE*)srcLockedRect.pBits;
|
||||
BYTE* pDest = (BYTE*)destLockedRect.pBits;
|
||||
const int pixelSize = 4;
|
||||
|
||||
if (flip_v) {
|
||||
pDest += destLockedRect.Pitch * (TARGET_SURFACE_HEIGHT - 1);
|
||||
}
|
||||
|
||||
for (int y = 0; y < TARGET_SURFACE_HEIGHT; y++) {
|
||||
for (int x = 0; x < TARGET_SURFACE_WIDTH; x++) {
|
||||
memcpy(
|
||||
pDest + x * pixelSize,
|
||||
pSrc + (flip_h ? (TARGET_SURFACE_WIDTH - x - 1) : x) * pixelSize,
|
||||
pixelSize
|
||||
);
|
||||
}
|
||||
|
||||
pSrc += srcLockedRect.Pitch;
|
||||
if (flip_v) {
|
||||
pDest -= destLockedRect.Pitch;
|
||||
} else {
|
||||
pDest += destLockedRect.Pitch;
|
||||
}
|
||||
}
|
||||
|
||||
m_pTransformSurf->UnlockRect();
|
||||
m_pTransformResultSurf->UnlockRect();
|
||||
|
||||
// Stretch camera texture to transform surface
|
||||
hr = m_device->StretchRect(m_pTransformResultSurf, NULL, m_pDestSurf, NULL, D3DTEXF_NONE);
|
||||
|
||||
} else {
|
||||
// No transformation needed, stretch to destination texture directly
|
||||
hr = m_device->StretchRect(pCameraSurf, &m_rcSource, m_pDestSurf, &m_rcDest, D3DTEXF_LINEAR);
|
||||
}
|
||||
|
||||
FlushDrawCommands();
|
||||
|
||||
if (FAILED(hr)) {
|
||||
log_warning("iidx:camhook", "Error in DrawSample {}", hr);
|
||||
}
|
||||
SafeRelease(&pCameraSurf);
|
||||
LeaveCriticalSection(&m_critsec);
|
||||
return hr;
|
||||
}
|
||||
|
||||
HRESULT IIDXLocalCamera::ReadSample() {
|
||||
HRESULT hr;
|
||||
DWORD streamIndex, flags;
|
||||
LONGLONG llTimeStamp;
|
||||
IMFSample *pSample = nullptr;
|
||||
IMFMediaBuffer *pBuffer = nullptr;
|
||||
|
||||
hr = m_pSourceReader->ReadSample(
|
||||
MF_SOURCE_READER_FIRST_VIDEO_STREAM,
|
||||
0,
|
||||
&streamIndex,
|
||||
&flags,
|
||||
&llTimeStamp,
|
||||
&pSample
|
||||
);
|
||||
|
||||
if (pSample) {
|
||||
// Draw to D3D
|
||||
pSample->GetBufferByIndex(0, &pBuffer);
|
||||
hr = DrawSample(pBuffer);
|
||||
}
|
||||
|
||||
SafeRelease(&pBuffer);
|
||||
SafeRelease(&pSample);
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
LPDIRECT3DTEXTURE9 IIDXLocalCamera::Render() {
|
||||
if (!m_active) {
|
||||
return nullptr;
|
||||
}
|
||||
HRESULT hr = ReadSample();
|
||||
if (FAILED(hr)) {
|
||||
return nullptr;
|
||||
}
|
||||
return m_texture;
|
||||
}
|
||||
|
||||
ULONG IIDXLocalCamera::Release() {
|
||||
log_info("iidx:camhook", "[{}] Release camera", m_name);
|
||||
m_active = false;
|
||||
|
||||
ULONG uCount = InterlockedDecrement(&m_nRefCount);
|
||||
|
||||
*m_texture_target = m_texture_original;
|
||||
|
||||
for (size_t i = 0; i < m_mediaTypeInfos.size(); i++) {
|
||||
SafeRelease(&(m_mediaTypeInfos.at(i).p_mediaType));
|
||||
}
|
||||
|
||||
SafeRelease(&m_pDestSurf);
|
||||
SafeRelease(&m_pTransformSurf);
|
||||
SafeRelease(&m_pConversionSurf);
|
||||
SafeRelease(&m_pTransformResultSurf);
|
||||
SafeRelease(&m_texture);
|
||||
SafeRelease(&m_conversionTexture);
|
||||
SafeRelease(&m_transformTexture);
|
||||
SafeRelease(&m_transformResultTexture);
|
||||
|
||||
if (m_pSource) {
|
||||
m_pSource->Shutdown();
|
||||
m_pSource->Release();
|
||||
}
|
||||
|
||||
CoTaskMemFree(m_pwszSymbolicLink);
|
||||
m_pwszSymbolicLink = NULL;
|
||||
m_cchSymbolicLink = 0;
|
||||
|
||||
if (uCount == 0) {
|
||||
delete this;
|
||||
}
|
||||
// For thread safety, return a temporary variable.
|
||||
return uCount;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,187 @@
|
||||
#pragma once
|
||||
|
||||
#if SPICE64
|
||||
|
||||
#include <d3d9.h>
|
||||
#include <dxva2api.h>
|
||||
#include <mutex>
|
||||
#include <mferror.h>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <strmif.h>
|
||||
#include <vector>
|
||||
#include "mf_wrappers.h"
|
||||
|
||||
#define CAMERA_CONTROL_PROP_SIZE 7
|
||||
#define DRAW_MODE_SIZE 5
|
||||
|
||||
template <class T> void SafeRelease(T **ppT)
|
||||
{
|
||||
if (*ppT)
|
||||
{
|
||||
(*ppT)->Release();
|
||||
*ppT = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
typedef void (*IMAGE_TRANSFORM_FN)(
|
||||
BYTE* pDest,
|
||||
LONG lDestStride,
|
||||
const BYTE* pSrc,
|
||||
LONG lSrcStride,
|
||||
DWORD dwWidthInPixels,
|
||||
DWORD dwHeightInPixels
|
||||
);
|
||||
|
||||
struct CameraControlProp {
|
||||
long minValue = 0;
|
||||
long maxValue = 0;
|
||||
long delta = 0;
|
||||
long defaultValue = 0;
|
||||
long defFlags = 0;
|
||||
long value = 0;
|
||||
long valueFlags = 0;
|
||||
};
|
||||
|
||||
struct MediaTypeInfo {
|
||||
GUID subtype = GUID_NULL;
|
||||
UINT32 width = 0;
|
||||
UINT32 height = 0;
|
||||
double frameRate = 0.0;
|
||||
IMFMediaType* p_mediaType = nullptr;
|
||||
std::string description = "";
|
||||
LONG *plStride = nullptr;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
DrawModeStretch = 0,
|
||||
DrawModeCrop = 1,
|
||||
DrawModeLetterbox = 2,
|
||||
DrawModeCrop4_3 = 3,
|
||||
DrawModeLetterbox4_3 = 4,
|
||||
} LocalCameraDrawMode;
|
||||
|
||||
extern std::string CAMERA_CONTROL_LABELS[];
|
||||
|
||||
extern std::string DRAW_MODE_LABELS[];
|
||||
|
||||
namespace games::iidx {
|
||||
class IIDXLocalCamera {
|
||||
protected:
|
||||
virtual ~IIDXLocalCamera() {};
|
||||
|
||||
ULONG m_nRefCount;
|
||||
CRITICAL_SECTION m_critsec;
|
||||
|
||||
std::string m_name;
|
||||
std::string m_friendly_name;
|
||||
BOOL m_prefer_16_by_9;
|
||||
WCHAR *m_pwszSymbolicLink = nullptr;
|
||||
UINT32 m_cchSymbolicLink = 0;
|
||||
|
||||
// For reading frames from Camera
|
||||
IMFMediaSource *m_pSource = nullptr;
|
||||
IMFSourceReader *m_pSourceReader = nullptr;
|
||||
|
||||
// Camera Format information
|
||||
double m_frameRate = 0;
|
||||
LONG m_cameraWidth;
|
||||
LONG m_cameraHeight;
|
||||
|
||||
// Draw rectangles
|
||||
RECT m_rcSource;
|
||||
RECT m_rcDest;
|
||||
|
||||
// Thread to draw texture asynchorously
|
||||
std::thread *m_drawThread = nullptr;
|
||||
|
||||
// DirectX9 DeviceEx
|
||||
LPDIRECT3DDEVICE9EX m_device;
|
||||
|
||||
// Address to hook camera texture onto the game
|
||||
LPDIRECT3DTEXTURE9 *m_texture_target = nullptr;
|
||||
|
||||
// Target texture (to be shown in the game)
|
||||
LPDIRECT3DTEXTURE9 m_texture = nullptr;
|
||||
IDirect3DSurface9 *m_pDestSurf = nullptr;
|
||||
|
||||
// Texture for color space conversion
|
||||
LPDIRECT3DTEXTURE9 m_conversionTexture = nullptr;
|
||||
IDirect3DSurface9 *m_pConversionSurf = nullptr;
|
||||
|
||||
// Texture for custom transform (e.g. horizontal flip)
|
||||
LPDIRECT3DTEXTURE9 m_transformTexture = nullptr;
|
||||
IDirect3DSurface9 *m_pTransformSurf = nullptr;
|
||||
|
||||
// Texture to store "transformed" camera content
|
||||
LPDIRECT3DTEXTURE9 m_transformResultTexture = nullptr;
|
||||
IDirect3DSurface9 *m_pTransformResultSurf = nullptr;
|
||||
|
||||
// Storing original texture for clean up
|
||||
LPDIRECT3DTEXTURE9 m_texture_original = nullptr;
|
||||
|
||||
IAMCameraControl *m_pCameraControl = nullptr;
|
||||
|
||||
// Camera Control
|
||||
std::vector<CameraControlProp> m_controlProps = {};
|
||||
|
||||
BOOL m_controlOptionsInitialized = false;
|
||||
|
||||
public:
|
||||
// True if first part of the setup steps (those in the constructor) succeeded
|
||||
BOOL m_initialized = false;
|
||||
|
||||
// True if all the setup steps succeeded
|
||||
BOOL m_active = false;
|
||||
|
||||
// Media type select
|
||||
std::vector<MediaTypeInfo> m_mediaTypeInfos = {};
|
||||
int m_selectedMediaTypeIndex = 0;
|
||||
bool m_useAutoMediaType = true;
|
||||
IMFMediaType *m_pAutoMediaType = nullptr;
|
||||
std::string m_selectedMediaTypeDescription = "";
|
||||
bool m_allowManualControl = false;
|
||||
|
||||
LocalCameraDrawMode m_drawMode = DrawModeCrop4_3;
|
||||
|
||||
// Render processing
|
||||
bool m_flipHorizontal = false;
|
||||
bool m_flipVertical = false;
|
||||
|
||||
IIDXLocalCamera(
|
||||
std::string name,
|
||||
BOOL prefer_16_by_9,
|
||||
IMFActivate *pActivate,
|
||||
IDirect3DDeviceManager9 *pD3DManager,
|
||||
LPDIRECT3DDEVICE9EX device,
|
||||
LPDIRECT3DTEXTURE9 *texture_target
|
||||
);
|
||||
LPDIRECT3DTEXTURE9 GetTexture();
|
||||
ULONG Release();
|
||||
IAMCameraControl* GetCameraControl();
|
||||
HRESULT GetCameraControlProp(int index, CameraControlProp *pProp);
|
||||
HRESULT SetCameraControlProp(int index, long value, long flags);
|
||||
HRESULT ResetCameraControlProps();
|
||||
HRESULT FlushDrawCommands();
|
||||
std::string GetName();
|
||||
std::string GetFriendlyName();
|
||||
std::string GetSymLink();
|
||||
HRESULT ChangeMediaType(IMFMediaType *pType);
|
||||
HRESULT StartCapture();
|
||||
void UpdateDrawRect();
|
||||
|
||||
private:
|
||||
HRESULT CreateD3DDeviceManager();
|
||||
void CreateThread();
|
||||
MediaTypeInfo GetMediaTypeInfo(IMFMediaType *pType);
|
||||
std::string GetVideoFormatName(GUID subtype);
|
||||
HRESULT TryMediaType(IMFMediaType *pType, UINT32 *pBestWidth, double *pBestFrameRate);
|
||||
HRESULT InitTargetTexture();
|
||||
HRESULT InitCameraControl();
|
||||
HRESULT DrawSample(IMFMediaBuffer *pSrcBuffer);
|
||||
HRESULT ReadSample();
|
||||
LPDIRECT3DTEXTURE9 Render();
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,116 @@
|
||||
#include "mf_wrappers.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
namespace games::iidx {
|
||||
|
||||
static bool INITIALIZED = false;
|
||||
|
||||
static HMODULE mf_dll = nullptr;
|
||||
static HMODULE mfreadwrite_dll = nullptr;
|
||||
static HMODULE mfplat_dll = nullptr;
|
||||
|
||||
typedef HRESULT (__stdcall * MFCreateAttributes_t)(
|
||||
_Out_ IMFAttributes** ppMFAttributes,
|
||||
_In_ UINT32 cInitialSize
|
||||
);
|
||||
|
||||
typedef HRESULT (__stdcall * MFEnumDeviceSources_t)(
|
||||
_In_ IMFAttributes* pAttributes,
|
||||
_Outptr_result_buffer_(*pcSourceActivate) IMFActivate*** pppSourceActivate,
|
||||
_Out_ UINT32* pcSourceActivate
|
||||
);
|
||||
|
||||
typedef HRESULT (__stdcall * MFCreateSourceReaderFromMediaSource_t)(
|
||||
_In_ IMFMediaSource *pMediaSource,
|
||||
_In_opt_ IMFAttributes *pAttributes,
|
||||
_Out_ IMFSourceReader **ppSourceReader
|
||||
);
|
||||
|
||||
typedef HRESULT (__stdcall * MFGetService_t)(
|
||||
IUnknown* punkObject,
|
||||
REFGUID guidService,
|
||||
REFIID riid,
|
||||
_Outptr_ LPVOID* ppvObject
|
||||
);
|
||||
|
||||
static MFCreateAttributes_t MFCreateAttributes = nullptr;
|
||||
static MFEnumDeviceSources_t MFEnumDeviceSources = nullptr;
|
||||
static MFCreateSourceReaderFromMediaSource_t MFCreateSourceReaderFromMediaSource = nullptr;
|
||||
static MFGetService_t MFGetService = nullptr;
|
||||
|
||||
void init_mf_library() {
|
||||
|
||||
// why was all of this needed?
|
||||
//
|
||||
// when iidx camhook was initially implemented, we linked to mf.lib, mfreadwrite.lib, and mfplat.lib
|
||||
// this made Unity-based really unhappy, causing them to skip over the logic that loads mf library
|
||||
// causing videos to not play ("Initializing Microsoft Media Foundation failed." in the cmd prompt)
|
||||
//
|
||||
// as a result, the static linking to mf libs were removed, and we are now doing the mess that is this file
|
||||
|
||||
if (INITIALIZED) {
|
||||
return;
|
||||
}
|
||||
INITIALIZED = true;
|
||||
|
||||
log_misc("mf_wrappers", "creating delay-loaded wrappers for MF routines - BEGIN");
|
||||
|
||||
mf_dll = libutils::load_library("mf.dll", true);
|
||||
mfreadwrite_dll = libutils::load_library("mfreadwrite.dll", true);
|
||||
mfplat_dll = libutils::load_library("mfplat.dll", true);
|
||||
|
||||
MFCreateAttributes = (MFCreateAttributes_t)
|
||||
libutils::get_proc(mfplat_dll, "MFCreateAttributes");
|
||||
if (!MFCreateAttributes) {
|
||||
log_fatal("mf_wrappers", "MFCreateAttributes failed to hook");
|
||||
}
|
||||
|
||||
MFEnumDeviceSources = (MFEnumDeviceSources_t)
|
||||
libutils::get_proc(mf_dll, "MFEnumDeviceSources");
|
||||
if (!MFEnumDeviceSources) {
|
||||
log_fatal("mf_wrappers", "MFEnumDeviceSources failed to hook");
|
||||
}
|
||||
|
||||
MFCreateSourceReaderFromMediaSource = (MFCreateSourceReaderFromMediaSource_t)
|
||||
libutils::get_proc(mfreadwrite_dll, "MFCreateSourceReaderFromMediaSource");
|
||||
if (!MFCreateSourceReaderFromMediaSource) {
|
||||
log_fatal("mf_wrappers", "MFCreateSourceReaderFromMediaSource failed to hook");
|
||||
}
|
||||
|
||||
MFGetService = (MFGetService_t)libutils::get_proc(mf_dll, "MFGetService");
|
||||
if (!MFGetService) {
|
||||
log_fatal("mf_wrappers", "MFGetService failed to hook");
|
||||
}
|
||||
|
||||
log_misc("mf_wrappers", "creating delay-loaded wrappers for MF routines - DONE");
|
||||
}
|
||||
|
||||
HRESULT WrappedMFCreateAttributes (
|
||||
_Out_ IMFAttributes** ppMFAttributes,
|
||||
_In_ UINT32 cInitialSize) {
|
||||
return MFCreateAttributes(ppMFAttributes, cInitialSize);
|
||||
}
|
||||
|
||||
HRESULT WrappedMFEnumDeviceSources (
|
||||
_In_ IMFAttributes* pAttributes,
|
||||
_Outptr_result_buffer_(*pcSourceActivate) IMFActivate*** pppSourceActivate,
|
||||
_Out_ UINT32* pcSourceActivate) {
|
||||
return MFEnumDeviceSources(pAttributes, pppSourceActivate, pcSourceActivate);
|
||||
}
|
||||
|
||||
HRESULT WrappedMFCreateSourceReaderFromMediaSource (
|
||||
_In_ IMFMediaSource *pMediaSource,
|
||||
_In_opt_ IMFAttributes *pAttributes,
|
||||
_Out_ IMFSourceReader **ppSourceReader) {
|
||||
return MFCreateSourceReaderFromMediaSource(pMediaSource, pAttributes, ppSourceReader);
|
||||
}
|
||||
|
||||
HRESULT WrappedMFGetService (
|
||||
IUnknown* punkObject,
|
||||
REFGUID guidService,
|
||||
REFIID riid,
|
||||
_Outptr_ LPVOID* ppvObject) {
|
||||
return MFGetService(punkObject, guidService, riid, ppvObject);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#include <mfapi.h>
|
||||
#include <mfidl.h>
|
||||
#include <mfreadwrite.h>
|
||||
#include <mfobjects.h>
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace games::iidx {
|
||||
void init_mf_library();
|
||||
|
||||
HRESULT WrappedMFCreateAttributes (
|
||||
_Out_ IMFAttributes** ppMFAttributes,
|
||||
_In_ UINT32 cInitialSize);
|
||||
|
||||
HRESULT WrappedMFEnumDeviceSources (
|
||||
_In_ IMFAttributes* pAttributes,
|
||||
_Outptr_result_buffer_(*pcSourceActivate) IMFActivate*** pppSourceActivate,
|
||||
_Out_ UINT32* pcSourceActivate);
|
||||
|
||||
HRESULT WrappedMFCreateSourceReaderFromMediaSource (
|
||||
_In_ IMFMediaSource *pMediaSource,
|
||||
_In_opt_ IMFAttributes *pAttributes,
|
||||
_Out_ IMFSourceReader **ppSourceReader);
|
||||
|
||||
HRESULT WrappedMFGetService (
|
||||
IUnknown* punkObject,
|
||||
REFGUID guidService,
|
||||
REFIID riid,
|
||||
_Outptr_ LPVOID* ppvObject);
|
||||
}
|
||||
@@ -0,0 +1,345 @@
|
||||
#include "poke.h"
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include "windows.h"
|
||||
#include "cfg/screen_resize.h"
|
||||
#include "games/io.h"
|
||||
#include "games/iidx/iidx.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "launcher/shutdown.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "overlay/overlay.h"
|
||||
#include "overlay/windows/generic_sub.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "touch/touch.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
#define POKE_NATIVE_TOUCH 0
|
||||
|
||||
#if POKE_NATIVE_TOUCH
|
||||
static HINSTANCE USER32_INSTANCE = nullptr;
|
||||
typedef BOOL (WINAPI *InitializeTouchInjection_t)(UINT32, DWORD);
|
||||
typedef BOOL (WINAPI *InjectTouchInput_t)(UINT32, POINTER_TOUCH_INFO*);
|
||||
static InitializeTouchInjection_t pInitializeTouchInjection = nullptr;
|
||||
static InjectTouchInput_t pInjectTouchInput = nullptr;
|
||||
#endif
|
||||
|
||||
namespace games::iidx::poke {
|
||||
|
||||
static std::thread *THREAD = nullptr;
|
||||
static bool THREAD_RUNNING = false;
|
||||
|
||||
struct KeypadMapping {
|
||||
char character;
|
||||
uint16_t state;
|
||||
};
|
||||
|
||||
static KeypadMapping KEYPAD_MAPPINGS[] = {
|
||||
{ '0', 1 << EAM_IO_KEYPAD_0 },
|
||||
{ '1', 1 << EAM_IO_KEYPAD_1 },
|
||||
{ '2', 1 << EAM_IO_KEYPAD_2 },
|
||||
{ '3', 1 << EAM_IO_KEYPAD_3 },
|
||||
{ '4', 1 << EAM_IO_KEYPAD_4 },
|
||||
{ '5', 1 << EAM_IO_KEYPAD_5 },
|
||||
{ '6', 1 << EAM_IO_KEYPAD_6 },
|
||||
{ '7', 1 << EAM_IO_KEYPAD_7 },
|
||||
{ '8', 1 << EAM_IO_KEYPAD_8 },
|
||||
{ '9', 1 << EAM_IO_KEYPAD_9 },
|
||||
{ 'A', 1 << EAM_IO_KEYPAD_00 },
|
||||
|
||||
// Touch panel keypad does not have the decimal key.
|
||||
// This will be used for toggling the keypad instead
|
||||
{ 'D', 1 << EAM_IO_KEYPAD_DECIMAL },
|
||||
};
|
||||
|
||||
const int IIDX_KEYPAD_BUTTON_SIZE = 90;
|
||||
const int IIDX_KEYPAD_GAP = 15;
|
||||
const int IIDX_KEYPAD_TOP = 570;
|
||||
const int IIDX_KEYPAD_LEFT_1P = 90;
|
||||
const int IIDX_KEYPAD_LEFT_2P = 1530;
|
||||
|
||||
// <KeypadID>/<Key> mapped to 1080p coordinate space
|
||||
static const std::unordered_map<std::string, int> IIDX_KEYPAD_POSITION_X {
|
||||
{"0/0", (int)(IIDX_KEYPAD_LEFT_1P + IIDX_KEYPAD_BUTTON_SIZE * 0.5)},
|
||||
{"0/1", (int)(IIDX_KEYPAD_LEFT_1P + IIDX_KEYPAD_BUTTON_SIZE * 0.5)},
|
||||
{"0/2", (int)(IIDX_KEYPAD_LEFT_1P + IIDX_KEYPAD_BUTTON_SIZE * 1.5 + IIDX_KEYPAD_GAP)},
|
||||
{"0/3", (int)(IIDX_KEYPAD_LEFT_1P + IIDX_KEYPAD_BUTTON_SIZE * 2.5 + IIDX_KEYPAD_GAP * 2)},
|
||||
{"0/4", (int)(IIDX_KEYPAD_LEFT_1P + IIDX_KEYPAD_BUTTON_SIZE * 0.5)},
|
||||
{"0/5", (int)(IIDX_KEYPAD_LEFT_1P + IIDX_KEYPAD_BUTTON_SIZE * 1.5 + IIDX_KEYPAD_GAP)},
|
||||
{"0/6", (int)(IIDX_KEYPAD_LEFT_1P + IIDX_KEYPAD_BUTTON_SIZE * 2.5 + IIDX_KEYPAD_GAP * 2)},
|
||||
{"0/7", (int)(IIDX_KEYPAD_LEFT_1P + IIDX_KEYPAD_BUTTON_SIZE * 0.5)},
|
||||
{"0/8", (int)(IIDX_KEYPAD_LEFT_1P + IIDX_KEYPAD_BUTTON_SIZE * 1.5 + IIDX_KEYPAD_GAP)},
|
||||
{"0/9", (int)(IIDX_KEYPAD_LEFT_1P + IIDX_KEYPAD_BUTTON_SIZE * 2.5 + IIDX_KEYPAD_GAP * 2)},
|
||||
{"0/A", (int)(IIDX_KEYPAD_LEFT_1P + IIDX_KEYPAD_BUTTON_SIZE * 1.5 + IIDX_KEYPAD_GAP)},
|
||||
{"0/D", 60},
|
||||
{"1/0", (int)(IIDX_KEYPAD_LEFT_2P + IIDX_KEYPAD_BUTTON_SIZE * 0.5)},
|
||||
{"1/1", (int)(IIDX_KEYPAD_LEFT_2P + IIDX_KEYPAD_BUTTON_SIZE * 0.5)},
|
||||
{"1/2", (int)(IIDX_KEYPAD_LEFT_2P + IIDX_KEYPAD_BUTTON_SIZE * 1.5 + IIDX_KEYPAD_GAP)},
|
||||
{"1/3", (int)(IIDX_KEYPAD_LEFT_2P + IIDX_KEYPAD_BUTTON_SIZE * 2.5 + IIDX_KEYPAD_GAP * 2)},
|
||||
{"1/4", (int)(IIDX_KEYPAD_LEFT_2P + IIDX_KEYPAD_BUTTON_SIZE * 0.5)},
|
||||
{"1/5", (int)(IIDX_KEYPAD_LEFT_2P + IIDX_KEYPAD_BUTTON_SIZE * 1.5 + IIDX_KEYPAD_GAP)},
|
||||
{"1/6", (int)(IIDX_KEYPAD_LEFT_2P + IIDX_KEYPAD_BUTTON_SIZE * 2.5 + IIDX_KEYPAD_GAP * 2)},
|
||||
{"1/7", (int)(IIDX_KEYPAD_LEFT_2P + IIDX_KEYPAD_BUTTON_SIZE * 0.5)},
|
||||
{"1/8", (int)(IIDX_KEYPAD_LEFT_2P + IIDX_KEYPAD_BUTTON_SIZE * 1.5 + IIDX_KEYPAD_GAP)},
|
||||
{"1/9", (int)(IIDX_KEYPAD_LEFT_2P + IIDX_KEYPAD_BUTTON_SIZE * 2.5 + IIDX_KEYPAD_GAP * 2)},
|
||||
{"1/A", (int)(IIDX_KEYPAD_LEFT_2P + IIDX_KEYPAD_BUTTON_SIZE * 1.5 + IIDX_KEYPAD_GAP)},
|
||||
{"1/D", 1920 - 60},
|
||||
};
|
||||
|
||||
static const std::unordered_map<std::string, int> IIDX_KEYPAD_POSITION_Y {
|
||||
{"0/0", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_GAP * 3 + IIDX_KEYPAD_BUTTON_SIZE * 3.5)},
|
||||
{"0/1", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_GAP * 2 + IIDX_KEYPAD_BUTTON_SIZE * 2.5)},
|
||||
{"0/2", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_GAP * 2 + IIDX_KEYPAD_BUTTON_SIZE * 2.5)},
|
||||
{"0/3", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_GAP * 2 + IIDX_KEYPAD_BUTTON_SIZE * 2.5)},
|
||||
{"0/4", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_GAP + IIDX_KEYPAD_BUTTON_SIZE * 1.5)},
|
||||
{"0/5", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_GAP + IIDX_KEYPAD_BUTTON_SIZE * 1.5)},
|
||||
{"0/6", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_GAP + IIDX_KEYPAD_BUTTON_SIZE * 1.5)},
|
||||
{"0/7", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_BUTTON_SIZE * 0.5)},
|
||||
{"0/8", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_BUTTON_SIZE * 0.5)},
|
||||
{"0/9", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_BUTTON_SIZE * 0.5)},
|
||||
{"0/A", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_GAP * 3 + IIDX_KEYPAD_BUTTON_SIZE * 3.5)},
|
||||
{"0/D", 50},
|
||||
{"1/0", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_GAP * 3 + IIDX_KEYPAD_BUTTON_SIZE * 3.5)},
|
||||
{"1/1", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_GAP * 2 + IIDX_KEYPAD_BUTTON_SIZE * 2.5)},
|
||||
{"1/2", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_GAP * 2 + IIDX_KEYPAD_BUTTON_SIZE * 2.5)},
|
||||
{"1/3", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_GAP * 2 + IIDX_KEYPAD_BUTTON_SIZE * 2.5)},
|
||||
{"1/4", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_GAP + IIDX_KEYPAD_BUTTON_SIZE * 1.5)},
|
||||
{"1/5", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_GAP + IIDX_KEYPAD_BUTTON_SIZE * 1.5)},
|
||||
{"1/6", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_GAP + IIDX_KEYPAD_BUTTON_SIZE * 1.5)},
|
||||
{"1/7", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_BUTTON_SIZE * 0.5)},
|
||||
{"1/8", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_BUTTON_SIZE * 0.5)},
|
||||
{"1/9", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_BUTTON_SIZE * 0.5)},
|
||||
{"1/A", (int)(IIDX_KEYPAD_TOP + IIDX_KEYPAD_GAP * 3 + IIDX_KEYPAD_BUTTON_SIZE * 3.5)},
|
||||
{"1/D", 50},
|
||||
};
|
||||
|
||||
#if POKE_NATIVE_TOUCH
|
||||
void initialize_native_touch_library() {
|
||||
if (USER32_INSTANCE == nullptr) {
|
||||
USER32_INSTANCE = libutils::load_library("user32.dll");
|
||||
}
|
||||
|
||||
pInitializeTouchInjection = libutils::try_proc<InitializeTouchInjection_t>(
|
||||
USER32_INSTANCE, "InitializeTouchInjection");
|
||||
pInjectTouchInput = libutils::try_proc<InjectTouchInput_t>(
|
||||
USER32_INSTANCE, "InjectTouchInput");
|
||||
}
|
||||
|
||||
void emulate_native_touch(TouchPoint tp, bool is_down) {
|
||||
if (pInjectTouchInput == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
POINTER_TOUCH_INFO contact;
|
||||
BOOL bRet = TRUE;
|
||||
const int contact_offset = 2;
|
||||
|
||||
memset(&contact, 0, sizeof(POINTER_TOUCH_INFO));
|
||||
|
||||
contact.pointerInfo.pointerType = PT_TOUCH;
|
||||
contact.pointerInfo.pointerId = 0;
|
||||
contact.pointerInfo.ptPixelLocation.x = tp.x;
|
||||
contact.pointerInfo.ptPixelLocation.y = tp.y;
|
||||
if (is_down) {
|
||||
contact.pointerInfo.pointerFlags = POINTER_FLAG_DOWN | POINTER_FLAG_INRANGE | POINTER_FLAG_INCONTACT;
|
||||
} else {
|
||||
contact.pointerInfo.pointerFlags = POINTER_FLAG_UP;
|
||||
}
|
||||
|
||||
contact.pointerInfo.dwTime = 0;
|
||||
contact.pointerInfo.PerformanceCount = 0;
|
||||
|
||||
contact.touchFlags = TOUCH_FLAG_NONE;
|
||||
contact.touchMask = TOUCH_MASK_CONTACTAREA | TOUCH_MASK_ORIENTATION | TOUCH_MASK_PRESSURE;
|
||||
contact.orientation = 0;
|
||||
contact.pressure = 1024;
|
||||
|
||||
contact.rcContact.top = tp.y - contact_offset;
|
||||
contact.rcContact.bottom = tp.y + contact_offset;
|
||||
contact.rcContact.left = tp.x - contact_offset;
|
||||
contact.rcContact.right = tp.x + contact_offset;
|
||||
|
||||
bRet = InjectTouchInput(1, &contact);
|
||||
if (!bRet) {
|
||||
log_warning("poke", "error injecting native touch {}: ({}, {}) error: {}", is_down ? "down" : "up", tp.x, tp.y, GetLastError());
|
||||
}
|
||||
}
|
||||
|
||||
void emulate_native_touch_points(std::vector<TouchPoint> *touch_points) {
|
||||
int i = 0;
|
||||
for (auto &touch : *touch_points) {
|
||||
emulate_native_touch(touch, true);
|
||||
}
|
||||
}
|
||||
|
||||
void clear_native_touch_points(std::vector<TouchPoint> *touch_points) {
|
||||
for (auto &touch : *touch_points) {
|
||||
emulate_native_touch(touch, false);
|
||||
}
|
||||
touch_points->clear();
|
||||
}
|
||||
#endif
|
||||
|
||||
void clear_touch_points(std::vector<TouchPoint> *touch_points) {
|
||||
std::vector<DWORD> touch_ids;
|
||||
for (auto &touch : *touch_points) {
|
||||
touch_ids.emplace_back(touch.id);
|
||||
}
|
||||
touch_remove_points(&touch_ids);
|
||||
touch_points->clear();
|
||||
}
|
||||
|
||||
void enable() {
|
||||
|
||||
// check if already running
|
||||
if (THREAD)
|
||||
return;
|
||||
|
||||
// create new thread
|
||||
THREAD_RUNNING = true;
|
||||
THREAD = new std::thread([] {
|
||||
|
||||
// log
|
||||
log_info("poke", "enabled");
|
||||
|
||||
|
||||
std::vector<TouchPoint> touch_points;
|
||||
std::vector<uint16_t> last_keypad_state = {0, 0};
|
||||
|
||||
#if POKE_NATIVE_TOUCH
|
||||
bool use_native = games::iidx::NATIVE_TOUCH;
|
||||
|
||||
if (use_native) {
|
||||
initialize_native_touch_library();
|
||||
|
||||
if (pInitializeTouchInjection != nullptr) {
|
||||
pInitializeTouchInjection(1, TOUCH_FEEDBACK_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
bool use_native = false;
|
||||
#endif
|
||||
|
||||
// set variable to false to stop
|
||||
while (THREAD_RUNNING) {
|
||||
|
||||
// clean up touch from last frame
|
||||
if (touch_points.size() > 0) {
|
||||
#if POKE_NATIVE_TOUCH
|
||||
if (use_native) {
|
||||
clear_native_touch_points(&touch_points);
|
||||
} else {
|
||||
clear_touch_points(&touch_points);
|
||||
}
|
||||
#else
|
||||
clear_touch_points(&touch_points);
|
||||
#endif
|
||||
}
|
||||
|
||||
for (int unit = 0; unit < 2; unit++) {
|
||||
// get keypad state
|
||||
auto state = eamuse_get_keypad_state(unit);
|
||||
|
||||
if (state != 0) {
|
||||
// add keys
|
||||
for (auto &mapping : KEYPAD_MAPPINGS) {
|
||||
if (state & mapping.state) {
|
||||
if (last_keypad_state[unit] & mapping.state) {
|
||||
// log_warning("poke", "ignoring hold {} {}", unit, mapping.character);
|
||||
continue;
|
||||
}
|
||||
std::string handle = fmt::format("{0}/{1}", unit, mapping.character);
|
||||
|
||||
auto x_iter = IIDX_KEYPAD_POSITION_X.find(handle);
|
||||
auto y_iter = IIDX_KEYPAD_POSITION_Y.find(handle);
|
||||
|
||||
if (x_iter != IIDX_KEYPAD_POSITION_X.end() && y_iter != IIDX_KEYPAD_POSITION_Y.end()) {
|
||||
DWORD touch_id = (DWORD)(0xFFFFFF * unit + mapping.character);
|
||||
|
||||
float x = x_iter->second / 1920.0;
|
||||
float y = y_iter->second / 1080.0;
|
||||
if (use_native) {
|
||||
x *= rawinput::TOUCHSCREEN_RANGE_X;
|
||||
y *= rawinput::TOUCHSCREEN_RANGE_Y;
|
||||
} else if (GRAPHICS_IIDX_WSUB) {
|
||||
// Scale to windowed subscreen
|
||||
x *= GRAPHICS_IIDX_WSUB_WIDTH;
|
||||
y *= GRAPHICS_IIDX_WSUB_HEIGHT;
|
||||
} else if (GENERIC_SUB_WINDOW_FULLSIZE || !overlay::OVERLAY->get_active()) {
|
||||
// Overlay is not present, scale to main screen
|
||||
if (GRAPHICS_WINDOWED) {
|
||||
x *= SPICETOUCH_TOUCH_WIDTH;
|
||||
y *= SPICETOUCH_TOUCH_HEIGHT;
|
||||
} else {
|
||||
x *= ImGui::GetIO().DisplaySize.x;
|
||||
y *= ImGui::GetIO().DisplaySize.y;
|
||||
}
|
||||
} else {
|
||||
// Overlay subscreen
|
||||
x = (GENERIC_SUB_WINDOW_X + x * GENERIC_SUB_WINDOW_WIDTH);
|
||||
y = (GENERIC_SUB_WINDOW_Y + y * GENERIC_SUB_WINDOW_HEIGHT);
|
||||
|
||||
// Scale to window size ratio
|
||||
if (GRAPHICS_WINDOWED) {
|
||||
x *= SPICETOUCH_TOUCH_WIDTH / ImGui::GetIO().DisplaySize.x;
|
||||
y *= SPICETOUCH_TOUCH_HEIGHT / ImGui::GetIO().DisplaySize.y;
|
||||
}
|
||||
}
|
||||
|
||||
TouchPoint tp {
|
||||
.id = touch_id,
|
||||
.x = (LONG)x,
|
||||
.y = (LONG)y,
|
||||
.mouse = true,
|
||||
};
|
||||
touch_points.emplace_back(tp);
|
||||
// log_warning("poke", "coords: {} {}", to_string(tp.x), to_string(tp.y));
|
||||
}
|
||||
}
|
||||
} // for all keys
|
||||
} // if state != 0
|
||||
|
||||
last_keypad_state[unit] = state;
|
||||
|
||||
} // for all units
|
||||
|
||||
if (touch_points.size() > 0) {
|
||||
#if POKE_NATIVE_TOUCH
|
||||
if (use_native) {
|
||||
emulate_native_touch_points(&touch_points);
|
||||
} else {
|
||||
touch_write_points(&touch_points);
|
||||
}
|
||||
#else
|
||||
touch_write_points(&touch_points);
|
||||
#endif
|
||||
}
|
||||
|
||||
// slow down
|
||||
Sleep(50);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
});
|
||||
}
|
||||
|
||||
void disable() {
|
||||
if (!THREAD) {
|
||||
return;
|
||||
}
|
||||
|
||||
// stop old thread
|
||||
THREAD_RUNNING = false;
|
||||
THREAD->join();
|
||||
|
||||
// delete thread
|
||||
delete THREAD;
|
||||
THREAD = nullptr;
|
||||
|
||||
// log
|
||||
log_info("poke", "disabled");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace games::iidx::poke {
|
||||
void enable();
|
||||
void disable();
|
||||
}
|
||||
+62
-9
@@ -33,6 +33,9 @@
|
||||
#include "pcm/io.h"
|
||||
#include "onpara/io.h"
|
||||
#include "bc/io.h"
|
||||
#include "ccj/io.h"
|
||||
#include "qks/io.h"
|
||||
#include "mfg/io.h"
|
||||
|
||||
namespace games {
|
||||
|
||||
@@ -42,6 +45,7 @@ namespace games {
|
||||
static robin_hood::unordered_map<std::string, std::vector<Button> &> buttons;
|
||||
static robin_hood::unordered_map<std::string, std::vector<Button>> buttons_keypads;
|
||||
static robin_hood::unordered_map<std::string, std::vector<Button>> buttons_overlay;
|
||||
static robin_hood::unordered_map<std::string, std::string> buttons_help;
|
||||
static robin_hood::unordered_map<std::string, std::vector<Analog> &> analogs;
|
||||
static robin_hood::unordered_map<std::string, std::vector<Light> &> lights;
|
||||
static robin_hood::unordered_map<std::string, std::vector<Option>> options;
|
||||
@@ -59,6 +63,7 @@ namespace games {
|
||||
const std::string bbc("Bishi Bashi Channel");
|
||||
games.push_back(bbc);
|
||||
buttons.insert({ bbc, bbc::get_buttons() });
|
||||
buttons_help.insert({ bbc, bbc::get_buttons_help() });
|
||||
analogs.insert({ bbc, bbc::get_analogs() });
|
||||
lights.insert({ bbc, bbc::get_lights() });
|
||||
file_hints[bbc].emplace_back("bsch.dll");
|
||||
@@ -67,6 +72,7 @@ namespace games {
|
||||
const std::string hpm("HELLO! Pop'n Music");
|
||||
games.push_back(hpm);
|
||||
buttons.insert({ hpm, hpm::get_buttons() });
|
||||
buttons_help.insert({ hpm, hpm::get_buttons_help() });
|
||||
lights.insert({ hpm, hpm::get_lights() });
|
||||
file_hints[hpm].emplace_back("popn.dll");
|
||||
|
||||
@@ -99,6 +105,7 @@ namespace games {
|
||||
const std::string gitadora("GitaDora");
|
||||
games.push_back(gitadora);
|
||||
buttons.insert({ gitadora, gitadora::get_buttons() });
|
||||
buttons_help.insert({ gitadora, gitadora::get_buttons_help() });
|
||||
analogs.insert({ gitadora, gitadora::get_analogs() });
|
||||
lights.insert({ gitadora, gitadora::get_lights() });
|
||||
file_hints[gitadora].emplace_back("gdxg.dll");
|
||||
@@ -107,6 +114,7 @@ namespace games {
|
||||
const std::string iidx("Beatmania IIDX");
|
||||
games.push_back(iidx);
|
||||
buttons.insert({ iidx, iidx::get_buttons() });
|
||||
buttons_help.insert({ iidx, iidx::get_buttons_help() });
|
||||
analogs.insert({ iidx, iidx::get_analogs() });
|
||||
lights.insert({ iidx, iidx::get_lights() });
|
||||
file_hints[iidx].emplace_back("bm2dx.dll");
|
||||
@@ -115,6 +123,7 @@ namespace games {
|
||||
const std::string jb("Jubeat");
|
||||
games.push_back(jb);
|
||||
buttons.insert({ jb, jb::get_buttons() });
|
||||
buttons_help.insert({ jb, jb::get_buttons_help() });
|
||||
lights.insert({ jb, jb::get_lights() });
|
||||
file_hints[jb].emplace_back("jubeat.dll");
|
||||
|
||||
@@ -130,6 +139,7 @@ namespace games {
|
||||
const std::string museca("Museca");
|
||||
games.push_back(museca);
|
||||
buttons.insert({ museca, museca::get_buttons() });
|
||||
buttons_help.insert({ museca, museca::get_buttons_help() });
|
||||
analogs.insert({ museca, museca::get_analogs() });
|
||||
lights.insert({ museca, museca::get_lights() });
|
||||
file_hints[museca].emplace_back("museca.dll");
|
||||
@@ -138,6 +148,7 @@ namespace games {
|
||||
const std::string nost("Nostalgia");
|
||||
games.push_back(nost);
|
||||
buttons.insert({ nost, nost::get_buttons() });
|
||||
buttons_help.insert({ nost, nost::get_buttons_help() });
|
||||
analogs.insert({ nost, nost::get_analogs() });
|
||||
lights.insert({ nost, nost::get_lights() });
|
||||
file_hints[nost].emplace_back("nostalgia.dll");
|
||||
@@ -146,6 +157,7 @@ namespace games {
|
||||
const std::string popn("Pop'n Music");
|
||||
games.push_back(popn);
|
||||
buttons.insert({ popn, popn::get_buttons() });
|
||||
buttons_help.insert({ popn, popn::get_buttons_help() });
|
||||
lights.insert({ popn, popn::get_lights() });
|
||||
file_hints[popn].emplace_back("popn19.dll");
|
||||
file_hints[popn].emplace_back("popn20.dll");
|
||||
@@ -195,6 +207,7 @@ namespace games {
|
||||
const std::string sdvx("Sound Voltex");
|
||||
games.push_back(sdvx);
|
||||
buttons.insert({ sdvx, sdvx::get_buttons() });
|
||||
buttons_help.insert({ sdvx, sdvx::get_buttons_help() });
|
||||
analogs.insert({ sdvx, sdvx::get_analogs() });
|
||||
lights.insert({ sdvx, sdvx::get_lights() });
|
||||
file_hints[sdvx].emplace_back("soundvoltex.dll");
|
||||
@@ -209,6 +222,7 @@ namespace games {
|
||||
const std::string ftt("FutureTomTom");
|
||||
games.push_back(ftt);
|
||||
buttons.insert({ ftt, ftt::get_buttons() });
|
||||
buttons_help.insert({ ftt, ftt::get_buttons_help() });
|
||||
analogs.insert({ ftt, ftt::get_analogs() });
|
||||
lights.insert({ ftt, ftt::get_lights() });
|
||||
file_hints[ftt].emplace_back("arkmmd.dll");
|
||||
@@ -231,6 +245,7 @@ namespace games {
|
||||
const std::string drs("DANCERUSH");
|
||||
games.push_back(drs);
|
||||
buttons.insert({ drs, drs::get_buttons() });
|
||||
buttons_help.insert({ drs, drs::get_buttons_help() });
|
||||
lights.insert({ drs, drs::get_lights() });
|
||||
file_hints[drs].emplace_back("superstep.dll");
|
||||
|
||||
@@ -272,7 +287,32 @@ namespace games {
|
||||
const std::string bc("Busou Shinki: Armored Princess Battle Conductor");
|
||||
games.push_back(bc);
|
||||
buttons.insert({ bc, bc::get_buttons() });
|
||||
file_hints[bc].emplace_back("kamunity.dll");
|
||||
buttons_help.insert({ bc, bc::get_buttons_help() });
|
||||
analogs.insert({ bc, bc::get_analogs() });
|
||||
file_hints[bc].emplace_back("game/bsac_app.exe");
|
||||
|
||||
// ccj
|
||||
const std::string ccj("Chase Chase Jokers");
|
||||
games.push_back(ccj);
|
||||
buttons.insert({ ccj, ccj::get_buttons() });
|
||||
buttons_help.insert({ ccj, ccj::get_buttons_help() });
|
||||
analogs.insert({ ccj, ccj::get_analogs() });
|
||||
lights.insert({ ccj, ccj::get_lights() });
|
||||
file_hints[ccj].emplace_back("game/chaseproject.exe");
|
||||
|
||||
// QuizKnock STADIUM
|
||||
const std::string qks("QuizKnock STADIUM");
|
||||
games.push_back(qks);
|
||||
buttons.insert({ qks, qks::get_buttons() });
|
||||
buttons_help.insert({ qks, qks::get_buttons_help() });
|
||||
file_hints[qks].emplace_back("game/uks.exe");
|
||||
|
||||
// Mahjong Fight Girl
|
||||
const std::string mfg("Mahjong Fight Girl");
|
||||
games.push_back(mfg);
|
||||
buttons.insert({ mfg, mfg::get_buttons() });
|
||||
buttons_help.insert({ mfg, mfg::get_buttons_help() });
|
||||
file_hints[mfg].emplace_back("game/MFGClient_Data");
|
||||
}
|
||||
|
||||
const std::vector<std::string> &get_games() {
|
||||
@@ -290,6 +330,15 @@ namespace games {
|
||||
return &it->second;
|
||||
}
|
||||
|
||||
std::string get_buttons_help(const std::string &game) {
|
||||
initialize();
|
||||
auto it = buttons_help.find(game);
|
||||
if (it == buttons_help.end()) {
|
||||
return "";
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
static std::vector<Button> gen_buttons_keypads(const std::string &game) {
|
||||
auto buttons = GameAPI::Buttons::getButtons(game);
|
||||
std::vector<std::string> names;
|
||||
@@ -368,6 +417,8 @@ namespace games {
|
||||
// overlay button definitions
|
||||
names.emplace_back("Screenshot");
|
||||
vkey_defaults.push_back(VK_SNAPSHOT);
|
||||
names.emplace_back("Toggle Main Menu");
|
||||
vkey_defaults.push_back(VK_ESCAPE);
|
||||
names.emplace_back("Toggle Sub Screen");
|
||||
vkey_defaults.push_back(VK_PRIOR);
|
||||
names.emplace_back("Insert Coin");
|
||||
@@ -392,21 +443,23 @@ namespace games {
|
||||
vkey_defaults.push_back(VK_F11);
|
||||
names.emplace_back("Toggle Overlay");
|
||||
vkey_defaults.push_back(VK_F12);
|
||||
names.emplace_back("Toggle VR Control");
|
||||
names.emplace_back("Toggle Camera Control");
|
||||
vkey_defaults.push_back(0xFF);
|
||||
names.emplace_back("Player 1 PIN Macro");
|
||||
vkey_defaults.push_back(0xFF);
|
||||
names.emplace_back("Player 2 PIN Macro");
|
||||
vkey_defaults.push_back(0xFF);
|
||||
names.emplace_back("Screen Resize");
|
||||
vkey_defaults.push_back(0xFF);
|
||||
names.emplace_back("Navigator Activate");
|
||||
names.emplace_back("Screen Resize Scene 1");
|
||||
vkey_defaults.push_back(0xFF);
|
||||
names.emplace_back("Navigator Cancel");
|
||||
names.emplace_back("Screen Resize Scene 2");
|
||||
vkey_defaults.push_back(0xFF);
|
||||
names.emplace_back("Navigator Up");
|
||||
names.emplace_back("Screen Resize Scene 3");
|
||||
vkey_defaults.push_back(0xFF);
|
||||
names.emplace_back("Navigator Down");
|
||||
names.emplace_back("Screen Resize Scene 4");
|
||||
vkey_defaults.push_back(0xFF);
|
||||
names.emplace_back("Navigator Left");
|
||||
vkey_defaults.push_back(0xFF);
|
||||
names.emplace_back("Navigator Right");
|
||||
names.emplace_back("Force Exit Game");
|
||||
vkey_defaults.push_back(0xFF);
|
||||
names.emplace_back("Hotkey Enable 1");
|
||||
vkey_defaults.push_back(0xFF);
|
||||
|
||||
+10
-7
@@ -8,6 +8,7 @@ namespace games {
|
||||
namespace OverlayButtons {
|
||||
enum {
|
||||
Screenshot,
|
||||
ToggleMainMenu,
|
||||
ToggleSubScreen,
|
||||
InsertCoin,
|
||||
ToggleIOPanel,
|
||||
@@ -20,14 +21,15 @@ namespace games {
|
||||
TogglePatchManager,
|
||||
ToggleScreenResize,
|
||||
ToggleOverlay,
|
||||
ToggleVRControl,
|
||||
ToggleCameraControl,
|
||||
TriggerPinMacroP1,
|
||||
TriggerPinMacroP2,
|
||||
ScreenResize,
|
||||
NavigatorActivate,
|
||||
NavigatorCancel,
|
||||
NavigatorUp,
|
||||
NavigatorDown,
|
||||
NavigatorLeft,
|
||||
NavigatorRight,
|
||||
ScreenResizeScene1,
|
||||
ScreenResizeScene2,
|
||||
ScreenResizeScene3,
|
||||
ScreenResizeScene4,
|
||||
SuperExit,
|
||||
HotkeyEnable1,
|
||||
HotkeyEnable2,
|
||||
HotkeyToggle,
|
||||
@@ -55,6 +57,7 @@ namespace games {
|
||||
|
||||
const std::vector<std::string> &get_games();
|
||||
std::vector<Button> *get_buttons(const std::string &game);
|
||||
std::string get_buttons_help(const std::string &game);
|
||||
std::vector<Button> *get_buttons_keypads(const std::string &game);
|
||||
std::vector<Button> *get_buttons_overlay(const std::string &game);
|
||||
std::vector<Analog> *get_analogs(const std::string &game);
|
||||
|
||||
@@ -33,6 +33,18 @@ std::vector<Button> &games::jb::get_buttons() {
|
||||
return buttons;
|
||||
}
|
||||
|
||||
std::string games::jb::get_buttons_help() {
|
||||
// keep to max 100 characters wide
|
||||
return
|
||||
" 1 2 3 4 \n"
|
||||
" 5 6 7 8 \n"
|
||||
" 9 10 11 12 \n"
|
||||
" 13 14 15 16 \n"
|
||||
"\n"
|
||||
"Touchscreen input is also supported."
|
||||
;
|
||||
}
|
||||
|
||||
std::vector<Light> &games::jb::get_lights() {
|
||||
static std::vector<Light> lights;
|
||||
|
||||
|
||||
@@ -56,5 +56,6 @@ namespace games::jb {
|
||||
|
||||
// getters
|
||||
std::vector<Button> &get_buttons();
|
||||
std::string get_buttons_help();
|
||||
std::vector<Light> &get_lights();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#include "jb.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <filesystem>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "cfg/configurator.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "touch/touch.h"
|
||||
#include "util/logging.h"
|
||||
@@ -162,6 +164,25 @@ namespace games::jb {
|
||||
JBGame::JBGame() : Game("Jubeat") {
|
||||
}
|
||||
|
||||
void JBGame::pre_attach() {
|
||||
if (!cfg::CONFIGURATOR_STANDALONE) {
|
||||
const auto current_path = std::filesystem::current_path();
|
||||
log_misc("jubeat", "current working directory: {}", current_path.string());
|
||||
if (current_path.parent_path() == current_path.root_path()) {
|
||||
log_fatal(
|
||||
"jubeat",
|
||||
"\n\nInvalid path error; jubeat cannot run from a directory in the drive root\n"
|
||||
"The game will overflow the stack and silently fail to boot\n\n"
|
||||
"Instead, it must be at least two levels deep, for example:\n"
|
||||
" c:\\jubeat\\spice.exe <- CRASH\n"
|
||||
" c:\\jubeat\\contents\\spice.exe <- OK\n\n"
|
||||
"To fix this, create a new directory and move ALL game files there.\n\n"
|
||||
"Your current working directory: {}\n",
|
||||
current_path.string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void JBGame::attach() {
|
||||
Game::attach();
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace games::jb {
|
||||
class JBGame : public games::Game {
|
||||
public:
|
||||
JBGame();
|
||||
virtual void pre_attach() override;
|
||||
virtual void attach() override;
|
||||
virtual void detach() override;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,385 @@
|
||||
#include "bi2a_hook.h"
|
||||
|
||||
#include "util/execexe.h"
|
||||
#include "util/logging.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "games/io.h"
|
||||
#include "io.h"
|
||||
#include "util/tapeled.h"
|
||||
|
||||
namespace games::mfg {
|
||||
|
||||
/*
|
||||
* class definitions
|
||||
*/
|
||||
|
||||
struct AIO_SCI_COMM {
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB {
|
||||
};
|
||||
|
||||
struct AIO_IOB_BI2A_VFG {
|
||||
};
|
||||
|
||||
struct AIO_IOB_BI2A_VFG__INPUTDATA {
|
||||
uint8_t data[16];
|
||||
};
|
||||
|
||||
struct AIO_IOB_BI2A_VFG__OUTPUTDATA {
|
||||
uint8_t data[40];
|
||||
};
|
||||
|
||||
struct AIO_IOB_BI2A_VFG__INPUT
|
||||
{
|
||||
uint8_t bPcPowerCheck;
|
||||
uint8_t CoinCount;
|
||||
uint8_t bTest;
|
||||
uint8_t bService;
|
||||
uint8_t bCoinSw;
|
||||
uint8_t bCoinJam;
|
||||
uint8_t bCabinet;
|
||||
uint8_t bHPDetect;
|
||||
uint8_t bRecDetect;
|
||||
uint8_t bStart;
|
||||
};
|
||||
|
||||
struct AIO_IOB_BI2A_VFG__DEVSTATUS
|
||||
{
|
||||
uint8_t MechType;
|
||||
uint8_t bIsBi2a;
|
||||
uint8_t IoCounter;
|
||||
AIO_IOB_BI2A_VFG__INPUT Input;
|
||||
AIO_IOB_BI2A_VFG__INPUTDATA InputData;
|
||||
AIO_IOB_BI2A_VFG__OUTPUTDATA OutputData;
|
||||
};
|
||||
|
||||
/*
|
||||
* typedefs
|
||||
*/
|
||||
|
||||
// libaio-iob_video.dll
|
||||
typedef AIO_SCI_COMM* (__fastcall *aioIobBi2a_OpenSciUsbCdc_t)(uint32_t i_SerialNumber);
|
||||
typedef AIO_IOB_BI2A_VFG* (__fastcall *aioIobBi2aVFG_Create_t)(AIO_NMGR_IOB *i_pNodeMgr, uint32_t i_DevId);
|
||||
typedef void (__fastcall *aioIobBi2aVFG_SetMechType_t)(AIO_IOB_BI2A_VFG *i_pNodeCtl, uint32_t i_MechType);
|
||||
typedef void (__fastcall *aioIobBi2aVFG_GetDeviceStatus_t)(AIO_IOB_BI2A_VFG* i_pNodeCtl,
|
||||
AIO_IOB_BI2A_VFG__DEVSTATUS *o_pDevStatus,
|
||||
uint32_t i_cbDevStatus);
|
||||
typedef void (__fastcall *aioIobBi2aVFG_SetWatchDogTimer_t)(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint16_t i_Count);
|
||||
typedef void (__fastcall *aioIobBi2aVFG_ControlCoinBlocker_t)(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint32_t i_Slot,
|
||||
bool i_bOpen);
|
||||
typedef void (__fastcall *aioIobBi2aVFG_AddCounter_t)(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint32_t i_Counter,
|
||||
uint32_t i_Count);
|
||||
typedef void (__fastcall *aioIobBi2aVFG_SetAmpVolume_t)(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint32_t i_Amp,
|
||||
uint32_t i_Volume);
|
||||
typedef void (__fastcall *aioIobBi2aVFG_EnableUsbCharger_t)(AIO_IOB_BI2A_VFG* i_pNodeCtl, bool i_bEnable);
|
||||
typedef void (__fastcall *aioIobBi2aVFG_SetLamp_t)(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint32_t i_Lamp, uint8_t i_Bright);
|
||||
typedef void (__fastcall *aioIobBi2aVFG_SetIccrLed_t)(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint32_t i_RGB);
|
||||
typedef void (__fastcall *aioIobBi2aVFG_SetLedData_t)(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint8_t *i_pData,
|
||||
uint32_t i_cbData);
|
||||
|
||||
// libaio-iob.dll
|
||||
typedef AIO_NMGR_IOB* (__fastcall *aioNMgrIob_Create_t)(AIO_SCI_COMM* i_pSci, uint32_t i_bfMode);
|
||||
|
||||
// libaio.dll
|
||||
typedef void (__fastcall *aioNodeMgr_Destroy_t)(AIO_NMGR_IOB *i_pNodeMgr);
|
||||
typedef int32_t (__fastcall *aioNodeMgr_GetState_t)(AIO_NMGR_IOB *i_pNodeMgr);
|
||||
typedef bool (__fastcall *aioNodeMgr_IsReady_t)(AIO_NMGR_IOB *i_pNodeMgr, int32_t i_State);
|
||||
typedef bool (__fastcall *aioNodeMgr_IsError_t)(AIO_NMGR_IOB *i_pNodeMgr, int32_t i_State);
|
||||
typedef void (__fastcall *aioNodeCtl_Destroy_t)(AIO_IOB_BI2A_VFG *i_pNodeCtl);
|
||||
typedef int32_t (__fastcall *aioNodeCtl_GetState_t)(AIO_IOB_BI2A_VFG *i_pNodeCtl);
|
||||
typedef bool (__fastcall *aioNodeCtl_IsReady_t)(AIO_IOB_BI2A_VFG *i_pNodeCtl, int32_t i_State);
|
||||
typedef bool (__fastcall *aioNodeCtl_IsError_t)(AIO_IOB_BI2A_VFG *i_pNodeCtl, int32_t i_State);
|
||||
|
||||
/*
|
||||
* function pointers
|
||||
*/
|
||||
|
||||
// libaio-iob_video.dll
|
||||
static aioIobBi2a_OpenSciUsbCdc_t aioIobBi2a_OpenSciUsbCdc_orig = nullptr;
|
||||
static aioIobBi2aVFG_Create_t aioIobBi2aVFG_Create_orig = nullptr;
|
||||
static aioIobBi2aVFG_SetMechType_t aioIobBi2aVFG_SetMechType_orig = nullptr;
|
||||
static aioIobBi2aVFG_GetDeviceStatus_t aioIobBi2aVFG_GetDeviceStatus_orig = nullptr;
|
||||
static aioIobBi2aVFG_SetWatchDogTimer_t aioIobBi2aVFG_SetWatchDogTimer_orig = nullptr;
|
||||
static aioIobBi2aVFG_ControlCoinBlocker_t aioIobBi2aVFG_ControlCoinBlocker_orig = nullptr;
|
||||
static aioIobBi2aVFG_AddCounter_t aioIobBi2aVFG_AddCounter_orig = nullptr;
|
||||
static aioIobBi2aVFG_SetAmpVolume_t aioIobBi2aVFG_SetAmpVolume_orig = nullptr;
|
||||
static aioIobBi2aVFG_EnableUsbCharger_t aioIobBi2aVFG_EnableUsbCharger_orig = nullptr;
|
||||
static aioIobBi2aVFG_SetLamp_t aioIobBi2aVFG_SetLamp_orig = nullptr;
|
||||
static aioIobBi2aVFG_SetIccrLed_t aioIobBi2aVFG_SetIccrLed_orig = nullptr;
|
||||
static aioIobBi2aVFG_SetLedData_t aioIobBi2aVFG_SetLedData_orig = nullptr;
|
||||
|
||||
// libaio-iob.dll
|
||||
static aioNMgrIob_Create_t aioNMgrIob_Create_orig = nullptr;
|
||||
|
||||
// libaio.dll
|
||||
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 aioNodeMgr_IsError_t aioNodeMgr_IsError_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 aioNodeCtl_IsError_t aioNodeCtl_IsError_orig = nullptr;
|
||||
|
||||
/*
|
||||
* variables
|
||||
*/
|
||||
|
||||
static AIO_SCI_COMM *aioSciComm;
|
||||
static AIO_NMGR_IOB *aioNmgrIob;
|
||||
static AIO_IOB_BI2A_VFG *aioIobBi2aVfg;
|
||||
static uint8_t mechType = 0;
|
||||
static uint8_t count = 0;
|
||||
|
||||
/*
|
||||
* implementations
|
||||
*/
|
||||
|
||||
static AIO_SCI_COMM* __fastcall aioIob2Bi2a_OpenSciUsbCdc(uint32_t i_SerialNumber) {
|
||||
log_info("bi2a_hook", "aioIob2Bi2a_OpenSciUsbCdc hook hit");
|
||||
aioSciComm = new AIO_SCI_COMM;
|
||||
return aioSciComm;
|
||||
}
|
||||
|
||||
static AIO_IOB_BI2A_VFG* __fastcall aioIobBi2aVFG_Create(AIO_NMGR_IOB *i_pNodeMgr, uint32_t i_DevId) {
|
||||
if (i_pNodeMgr == aioNmgrIob) {
|
||||
log_info("bi2a_hook", "node created");
|
||||
aioIobBi2aVfg = new AIO_IOB_BI2A_VFG;
|
||||
return aioIobBi2aVfg;
|
||||
} else {
|
||||
return aioIobBi2aVFG_Create_orig(i_pNodeMgr, i_DevId);
|
||||
}
|
||||
}
|
||||
|
||||
void __fastcall aioIobBi2aVFG_SetMechType(AIO_IOB_BI2A_VFG *i_pNodeCtl, uint32_t i_MechType) {
|
||||
if (i_pNodeCtl == aioIobBi2aVfg) {
|
||||
mechType = i_MechType;
|
||||
} else {
|
||||
return aioIobBi2aVFG_SetMechType_orig(i_pNodeCtl, i_MechType);
|
||||
}
|
||||
}
|
||||
|
||||
void __fastcall aioIobBi2aVFG_GetDeviceStatus(AIO_IOB_BI2A_VFG* i_pNodeCtl,
|
||||
AIO_IOB_BI2A_VFG__DEVSTATUS *o_pDevStatus,
|
||||
uint32_t i_cbDevStatus) {
|
||||
|
||||
RI_MGR->devices_flush_output();
|
||||
|
||||
if (i_pNodeCtl != aioIobBi2aVfg) {
|
||||
return aioIobBi2aVFG_GetDeviceStatus_orig(i_pNodeCtl, o_pDevStatus, i_cbDevStatus);
|
||||
}
|
||||
|
||||
memset(o_pDevStatus, 0x00, sizeof(AIO_IOB_BI2A_VFG__DEVSTATUS));
|
||||
|
||||
o_pDevStatus->MechType = mechType;
|
||||
o_pDevStatus->bIsBi2a = 1;
|
||||
o_pDevStatus->IoCounter = count++;
|
||||
|
||||
auto &buttons = get_buttons();
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test]))
|
||||
o_pDevStatus->Input.bTest = 1;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service]))
|
||||
o_pDevStatus->Input.bService = 1;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::CoinMech]))
|
||||
o_pDevStatus->Input.bCoinSw = 1;
|
||||
|
||||
o_pDevStatus->Input.CoinCount = eamuse_coin_get_stock();
|
||||
}
|
||||
|
||||
void __fastcall aioIobBi2aVFG_SetWatchDogTimer(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint16_t i_Count) {
|
||||
if (i_pNodeCtl == aioIobBi2aVfg) {
|
||||
} else {
|
||||
return aioIobBi2aVFG_SetWatchDogTimer_orig(i_pNodeCtl, i_Count);
|
||||
}
|
||||
}
|
||||
|
||||
void __fastcall aioIobBi2aVFG_ControlCoinBlocker(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint32_t i_Slot, bool i_bOpen) {
|
||||
if (i_pNodeCtl == aioIobBi2aVfg) {
|
||||
} else {
|
||||
return aioIobBi2aVFG_ControlCoinBlocker_orig(i_pNodeCtl, i_Slot, i_bOpen);
|
||||
}
|
||||
}
|
||||
|
||||
void __fastcall aioIobBi2aVFG_AddCounter(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint32_t i_Counter, uint32_t i_Count) {
|
||||
if (i_pNodeCtl == aioIobBi2aVfg) {
|
||||
} else {
|
||||
return aioIobBi2aVFG_AddCounter_orig(i_pNodeCtl, i_Counter, i_Count);
|
||||
}
|
||||
}
|
||||
|
||||
void __fastcall aioIobBi2aVFG_SetAmpVolume(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint32_t i_Amp, uint32_t i_Volume) {
|
||||
if (i_pNodeCtl == aioIobBi2aVfg) {
|
||||
} else {
|
||||
return aioIobBi2aVFG_SetAmpVolume_orig(i_pNodeCtl, i_Amp, i_Volume);
|
||||
}
|
||||
}
|
||||
|
||||
void __fastcall aioIobBi2aVFG_EnableUsbCharger(AIO_IOB_BI2A_VFG* i_pNodeCtl, bool i_bEnable) {
|
||||
if (i_pNodeCtl == aioIobBi2aVfg) {
|
||||
} else {
|
||||
return aioIobBi2aVFG_EnableUsbCharger_orig(i_pNodeCtl, i_bEnable);
|
||||
}
|
||||
}
|
||||
|
||||
void __fastcall aioIobBi2aVFG_SetLamp(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint32_t i_Lamp, uint8_t i_Bright) {
|
||||
if (i_pNodeCtl == aioIobBi2aVfg) {
|
||||
} else {
|
||||
return aioIobBi2aVFG_SetLamp_orig(i_pNodeCtl, i_Lamp, i_Bright);
|
||||
}
|
||||
}
|
||||
|
||||
void __fastcall aioIobBi2aVFG_SetIccrLed(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint32_t i_RGB) {
|
||||
if (i_pNodeCtl == aioIobBi2aVfg) {
|
||||
} else {
|
||||
return aioIobBi2aVFG_SetIccrLed_orig(i_pNodeCtl, i_RGB);
|
||||
}
|
||||
}
|
||||
|
||||
void __fastcall aioIobBi2aVFG_SetLedData(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint8_t *i_pData, uint32_t i_cbData) {
|
||||
if (i_pNodeCtl == aioIobBi2aVfg) {
|
||||
} else {
|
||||
return aioIobBi2aVFG_SetLedData_orig(i_pNodeCtl, i_pData, i_cbData);
|
||||
}
|
||||
}
|
||||
|
||||
static AIO_NMGR_IOB* __fastcall aioNMgrIob_Create(AIO_SCI_COMM *i_pSci, uint32_t i_bfMode) {
|
||||
if (i_pSci == aioSciComm) {
|
||||
aioNmgrIob = new AIO_NMGR_IOB;
|
||||
return aioNmgrIob;
|
||||
} else {
|
||||
return aioNMgrIob_Create_orig(i_pSci, i_bfMode);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioNodeMgr_Destroy(AIO_NMGR_IOB *i_pNodeMgr) {
|
||||
if (i_pNodeMgr == aioNmgrIob) {
|
||||
delete aioNmgrIob;
|
||||
aioNmgrIob = nullptr;
|
||||
} else {
|
||||
return aioNodeMgr_Destroy_orig(i_pNodeMgr);
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t __fastcall aioNodeMgr_GetState(AIO_NMGR_IOB *i_pNodeMgr) {
|
||||
if (i_pNodeMgr == aioNmgrIob) {
|
||||
return 1;
|
||||
} else {
|
||||
return aioNodeMgr_GetState_orig(i_pNodeMgr);
|
||||
}
|
||||
}
|
||||
|
||||
static bool __fastcall aioNodeMgr_IsReady(AIO_NMGR_IOB *i_pNodeMgr, int32_t i_State) {
|
||||
if (i_pNodeMgr == aioNmgrIob) {
|
||||
return true;
|
||||
} else {
|
||||
return aioNodeMgr_IsReady_orig(i_pNodeMgr, i_State);
|
||||
}
|
||||
}
|
||||
|
||||
static bool __fastcall aioNodeMgr_IsError(AIO_NMGR_IOB *i_pNodeMgr, int32_t i_State) {
|
||||
if (i_pNodeMgr == aioNmgrIob) {
|
||||
return false;
|
||||
} else {
|
||||
return aioNodeMgr_IsError_orig(i_pNodeMgr, i_State);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioNodeCtl_Destroy(AIO_IOB_BI2A_VFG *i_pNodeCtl) {
|
||||
if (i_pNodeCtl == aioIobBi2aVfg) {
|
||||
delete aioIobBi2aVfg;
|
||||
aioIobBi2aVfg = nullptr;
|
||||
} else {
|
||||
return aioNodeCtl_Destroy_orig(i_pNodeCtl);
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t __fastcall aioNodeCtl_GetState(AIO_IOB_BI2A_VFG *i_pNodeCtl) {
|
||||
if (i_pNodeCtl == aioIobBi2aVfg) {
|
||||
return 1;
|
||||
} else {
|
||||
return aioNodeCtl_GetState_orig(i_pNodeCtl);
|
||||
}
|
||||
}
|
||||
|
||||
static bool __fastcall aioNodeCtl_IsReady(AIO_IOB_BI2A_VFG *i_pNodeCtl, int32_t i_State) {
|
||||
if (i_pNodeCtl == aioIobBi2aVfg) {
|
||||
return true;
|
||||
} else {
|
||||
return aioNodeCtl_IsReady_orig(i_pNodeCtl, i_State);
|
||||
}
|
||||
}
|
||||
|
||||
static bool __fastcall aioNodeCtl_IsError(AIO_IOB_BI2A_VFG *i_pNodeCtl, int32_t i_State) {
|
||||
if (i_pNodeCtl == aioIobBi2aVfg) {
|
||||
return false;
|
||||
} else {
|
||||
return aioNodeCtl_IsError_orig(i_pNodeCtl, i_State);
|
||||
}
|
||||
}
|
||||
|
||||
void bi2a_hook_init() {
|
||||
// avoid double init
|
||||
static bool initialized = false;
|
||||
if (initialized) {
|
||||
return;
|
||||
} else {
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
// announce
|
||||
log_info("bi2a_hook", "init");
|
||||
|
||||
// libaio-iob_video.dll
|
||||
const auto libaioIobVideoDll = "libaio-iob_video.dll";
|
||||
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2a_OpenSciUsbCdc",
|
||||
aioIob2Bi2a_OpenSciUsbCdc, &aioIobBi2a_OpenSciUsbCdc_orig);
|
||||
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2aVFG_Create",
|
||||
aioIobBi2aVFG_Create, &aioIobBi2aVFG_Create_orig);
|
||||
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2aVFG_SetMechType",
|
||||
aioIobBi2aVFG_SetMechType, &aioIobBi2aVFG_SetMechType_orig);
|
||||
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2aVFG_GetDeviceStatus",
|
||||
aioIobBi2aVFG_GetDeviceStatus, &aioIobBi2aVFG_GetDeviceStatus_orig);
|
||||
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2aVFG_SetWatchDogTimer",
|
||||
aioIobBi2aVFG_SetWatchDogTimer, &aioIobBi2aVFG_SetWatchDogTimer_orig);
|
||||
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2aVFG_ControlCoinBlocker",
|
||||
aioIobBi2aVFG_ControlCoinBlocker, &aioIobBi2aVFG_ControlCoinBlocker_orig);
|
||||
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2aVFG_AddCounter",
|
||||
aioIobBi2aVFG_AddCounter, &aioIobBi2aVFG_AddCounter_orig);
|
||||
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2aVFG_SetAmpVolume",
|
||||
aioIobBi2aVFG_SetAmpVolume, &aioIobBi2aVFG_SetAmpVolume_orig);
|
||||
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2aVFG_EnableUsbCharger",
|
||||
aioIobBi2aVFG_EnableUsbCharger, &aioIobBi2aVFG_EnableUsbCharger_orig);
|
||||
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2aVFG_SetLamp",
|
||||
aioIobBi2aVFG_SetLamp, &aioIobBi2aVFG_SetLamp_orig);
|
||||
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2aVFG_SetIccrLed",
|
||||
aioIobBi2aVFG_SetIccrLed, &aioIobBi2aVFG_SetIccrLed_orig);
|
||||
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2aVFG_SetLedData",
|
||||
aioIobBi2aVFG_SetLedData, &aioIobBi2aVFG_SetLedData_orig);
|
||||
|
||||
// libaio-iob.dll
|
||||
const auto libaioIobDll = "libaio-iob.dll";
|
||||
execexe::trampoline_try(libaioIobDll, "aioNMgrIob_Create",
|
||||
aioNMgrIob_Create, &aioNMgrIob_Create_orig);
|
||||
|
||||
// libaio.dll
|
||||
const auto libaioDll = "libaio.dll";
|
||||
execexe::trampoline_try(libaioDll, "aioNodeMgr_Destroy",
|
||||
aioNodeMgr_Destroy, &aioNodeMgr_Destroy_orig);
|
||||
execexe::trampoline_try(libaioDll, "aioNodeMgr_GetState",
|
||||
aioNodeMgr_GetState, &aioNodeMgr_GetState_orig);
|
||||
execexe::trampoline_try(libaioDll, "aioNodeMgr_IsReady",
|
||||
aioNodeMgr_IsReady, &aioNodeMgr_IsReady_orig);
|
||||
execexe::trampoline_try(libaioDll, "aioNodeMgr_IsError",
|
||||
aioNodeMgr_IsError, &aioNodeMgr_IsError_orig);
|
||||
execexe::trampoline_try(libaioDll, "aioNodeCtl_Destroy",
|
||||
aioNodeCtl_Destroy, &aioNodeCtl_Destroy_orig);
|
||||
execexe::trampoline_try(libaioDll, "aioNodeCtl_GetState",
|
||||
aioNodeCtl_GetState, &aioNodeCtl_GetState_orig);
|
||||
execexe::trampoline_try(libaioDll, "aioNodeCtl_IsReady",
|
||||
aioNodeCtl_IsReady, &aioNodeCtl_IsReady_orig);
|
||||
execexe::trampoline_try(libaioDll, "aioNodeCtl_IsError",
|
||||
aioNodeCtl_IsError, &aioNodeCtl_IsError_orig);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
namespace games::mfg {
|
||||
void bi2a_hook_init();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#include "io.h"
|
||||
|
||||
std::vector<Button> &games::mfg::get_buttons() {
|
||||
static std::vector<Button> buttons;
|
||||
|
||||
if (buttons.empty()) {
|
||||
buttons = GameAPI::Buttons::getButtons("Mahjong Fight Girl");
|
||||
|
||||
GameAPI::Buttons::sortButtons(
|
||||
&buttons,
|
||||
"Service",
|
||||
"Test",
|
||||
"Coin Mech"
|
||||
);
|
||||
}
|
||||
|
||||
return buttons;
|
||||
}
|
||||
|
||||
std::string games::mfg::get_buttons_help() {
|
||||
return "";
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "cfg/api.h"
|
||||
|
||||
namespace games::mfg {
|
||||
namespace Buttons {
|
||||
enum {
|
||||
Service,
|
||||
Test,
|
||||
CoinMech
|
||||
};
|
||||
}
|
||||
|
||||
// getters
|
||||
std::vector<Button> &get_buttons();
|
||||
std::string get_buttons_help();
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
#include "mfg.h"
|
||||
|
||||
#include <format>
|
||||
#include "util/fileutils.h"
|
||||
#include "util/unity_player.h"
|
||||
#include "util/utils.h"
|
||||
#include "util/execexe.h"
|
||||
#include "acioemu/handle.h"
|
||||
#include "misc/wintouchemu.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "bi2a_hook.h"
|
||||
|
||||
namespace games::mfg {
|
||||
std::string MFG_INJECT_ARGS = "";
|
||||
std::string MFG_CABINET_TYPE = "HG";
|
||||
bool MFG_NO_IO = false;
|
||||
|
||||
static acioemu::ACIOHandle *acioHandle = nullptr;
|
||||
static std::wstring portName;
|
||||
|
||||
void MFGGame::attach() {
|
||||
Game::attach();
|
||||
|
||||
// create required files
|
||||
fileutils::dir_create_recursive("dev/raw/log");
|
||||
fileutils::bin_write("dev/raw/log/output_log.txt", nullptr, 0);
|
||||
|
||||
SetEnvironmentVariableA("VFG_CABINET_TYPE", MFG_CABINET_TYPE.c_str());
|
||||
|
||||
// add card reader
|
||||
portName = MFG_CABINET_TYPE == "UKS" ? L"\\\\.\\COM1" : L"\\\\.\\COM3";
|
||||
acioHandle = new acioemu::ACIOHandle(portName.c_str(), 1);
|
||||
devicehook_init_trampoline();
|
||||
devicehook_add(acioHandle);
|
||||
|
||||
execexe::init();
|
||||
execexe::init_port_hook(portName, acioHandle);
|
||||
|
||||
if (GRAPHICS_SHOW_CURSOR) {
|
||||
unity_utils::force_show_cursor(true);
|
||||
}
|
||||
|
||||
unity_utils::set_args(
|
||||
std::format("{} {}{}",
|
||||
GetCommandLineA(),
|
||||
MFG_INJECT_ARGS,
|
||||
unity_utils::get_unity_player_args()));
|
||||
}
|
||||
|
||||
void MFGGame::post_attach() {
|
||||
Game::post_attach();
|
||||
|
||||
execexe::load_library("libaio.dll");
|
||||
execexe::load_library("libaio-iob.dll");
|
||||
execexe::load_library("libaio-iob_video.dll");
|
||||
execexe::load_library("libaio-iob2_video.dll");
|
||||
execexe::load_library("win10actlog.dll");
|
||||
|
||||
if (!MFG_NO_IO) {
|
||||
// insert BI2* hooks
|
||||
if (MFG_CABINET_TYPE == "UKS") {
|
||||
log_fatal("mfg", "UKS io is not supported");
|
||||
} else {
|
||||
bi2a_hook_init();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MFGGame::detach() {
|
||||
Game::detach();
|
||||
|
||||
devicehook_dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "games/game.h"
|
||||
|
||||
namespace games::mfg {
|
||||
extern std::string MFG_INJECT_ARGS;
|
||||
extern std::string MFG_CABINET_TYPE;
|
||||
extern bool MFG_NO_IO;
|
||||
|
||||
class MFGGame : public games::Game {
|
||||
public:
|
||||
MFGGame() : Game("Mahjong Fight Girl") {}
|
||||
|
||||
virtual void attach() override;
|
||||
virtual void post_attach() override;
|
||||
virtual void detach() override;
|
||||
};
|
||||
}
|
||||
@@ -34,6 +34,16 @@ std::vector<Button> &games::museca::get_buttons() {
|
||||
return buttons;
|
||||
}
|
||||
|
||||
std::string games::museca::get_buttons_help() {
|
||||
// keep to max 100 characters wide
|
||||
return
|
||||
" Disc1 Disc3 Disc5 \n"
|
||||
" Disc2 Disc4 \n"
|
||||
"\n"
|
||||
" Pedal"
|
||||
;
|
||||
}
|
||||
|
||||
std::vector<Analog> &games::museca::get_analogs() {
|
||||
static std::vector<Analog> analogs;
|
||||
|
||||
@@ -61,6 +71,8 @@ std::vector<Light> &games::museca::get_lights() {
|
||||
|
||||
GameAPI::Lights::sortLights(
|
||||
&lights,
|
||||
"Start",
|
||||
"Keypad",
|
||||
"Title R",
|
||||
"Title G",
|
||||
"Title B",
|
||||
|
||||
@@ -46,6 +46,8 @@ namespace games::museca {
|
||||
// all lights in correct order
|
||||
namespace Lights {
|
||||
enum {
|
||||
Start,
|
||||
Keypad,
|
||||
TitleR,
|
||||
TitleG,
|
||||
TitleB,
|
||||
@@ -81,6 +83,7 @@ namespace games::museca {
|
||||
|
||||
// getters
|
||||
std::vector<Button> &get_buttons();
|
||||
std::string get_buttons_help();
|
||||
std::vector<Analog> &get_analogs();
|
||||
std::vector<Light> &get_lights();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
#include "museca.h"
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/shared/lcdhandle.h"
|
||||
#include "hooks/devicehook.h"
|
||||
|
||||
namespace games::museca {
|
||||
|
||||
MusecaGame::MusecaGame() : Game("Museca") {
|
||||
}
|
||||
|
||||
void MusecaGame::attach() {
|
||||
Game::attach();
|
||||
|
||||
devicehook_init();
|
||||
devicehook_add(new games::shared::LCDHandle());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "games/game.h"
|
||||
|
||||
namespace games::museca {
|
||||
|
||||
class MusecaGame : public games::Game {
|
||||
public:
|
||||
MusecaGame();
|
||||
|
||||
virtual void attach() override;
|
||||
};
|
||||
}
|
||||
+115
-1
@@ -38,13 +38,127 @@ std::vector<Button> &games::nost::get_buttons() {
|
||||
"Key 25",
|
||||
"Key 26",
|
||||
"Key 27",
|
||||
"Key 28"
|
||||
"Key 28",
|
||||
"Key 1 Soft",
|
||||
"Key 2 Soft",
|
||||
"Key 3 Soft",
|
||||
"Key 4 Soft",
|
||||
"Key 5 Soft",
|
||||
"Key 6 Soft",
|
||||
"Key 7 Soft",
|
||||
"Key 8 Soft",
|
||||
"Key 9 Soft",
|
||||
"Key 10 Soft",
|
||||
"Key 11 Soft",
|
||||
"Key 12 Soft",
|
||||
"Key 13 Soft",
|
||||
"Key 14 Soft",
|
||||
"Key 15 Soft",
|
||||
"Key 16 Soft",
|
||||
"Key 17 Soft",
|
||||
"Key 18 Soft",
|
||||
"Key 19 Soft",
|
||||
"Key 20 Soft",
|
||||
"Key 21 Soft",
|
||||
"Key 22 Soft",
|
||||
"Key 23 Soft",
|
||||
"Key 24 Soft",
|
||||
"Key 25 Soft",
|
||||
"Key 26 Soft",
|
||||
"Key 27 Soft",
|
||||
"Key 28 Soft",
|
||||
"Key 1 Medium",
|
||||
"Key 2 Medium",
|
||||
"Key 3 Medium",
|
||||
"Key 4 Medium",
|
||||
"Key 5 Medium",
|
||||
"Key 6 Medium",
|
||||
"Key 7 Medium",
|
||||
"Key 8 Medium",
|
||||
"Key 9 Medium",
|
||||
"Key 10 Medium",
|
||||
"Key 11 Medium",
|
||||
"Key 12 Medium",
|
||||
"Key 13 Medium",
|
||||
"Key 14 Medium",
|
||||
"Key 15 Medium",
|
||||
"Key 16 Medium",
|
||||
"Key 17 Medium",
|
||||
"Key 18 Medium",
|
||||
"Key 19 Medium",
|
||||
"Key 20 Medium",
|
||||
"Key 21 Medium",
|
||||
"Key 22 Medium",
|
||||
"Key 23 Medium",
|
||||
"Key 24 Medium",
|
||||
"Key 25 Medium",
|
||||
"Key 26 Medium",
|
||||
"Key 27 Medium",
|
||||
"Key 28 Medium",
|
||||
"Key 1 Hard",
|
||||
"Key 2 Hard",
|
||||
"Key 3 Hard",
|
||||
"Key 4 Hard",
|
||||
"Key 5 Hard",
|
||||
"Key 6 Hard",
|
||||
"Key 7 Hard",
|
||||
"Key 8 Hard",
|
||||
"Key 9 Hard",
|
||||
"Key 10 Hard",
|
||||
"Key 11 Hard",
|
||||
"Key 12 Hard",
|
||||
"Key 13 Hard",
|
||||
"Key 14 Hard",
|
||||
"Key 15 Hard",
|
||||
"Key 16 Hard",
|
||||
"Key 17 Hard",
|
||||
"Key 18 Hard",
|
||||
"Key 19 Hard",
|
||||
"Key 20 Hard",
|
||||
"Key 21 Hard",
|
||||
"Key 22 Hard",
|
||||
"Key 23 Hard",
|
||||
"Key 24 Hard",
|
||||
"Key 25 Hard",
|
||||
"Key 26 Hard",
|
||||
"Key 27 Hard",
|
||||
"Key 28 Hard",
|
||||
"Swipe Next Page",
|
||||
"Swipe Previous Page",
|
||||
"Touch Confirm",
|
||||
"Touch Back",
|
||||
"Touch Song 1",
|
||||
"Touch Song 2",
|
||||
"Touch Song 3",
|
||||
"Touch Song 4",
|
||||
"Touch Song 5",
|
||||
"Touch Song 6",
|
||||
"Touch Difficulty 1",
|
||||
"Touch Difficulty 2",
|
||||
"Touch Difficulty 3",
|
||||
"Touch Difficulty 4"
|
||||
);
|
||||
}
|
||||
|
||||
return buttons;
|
||||
}
|
||||
|
||||
std::string games::nost::get_buttons_help() {
|
||||
// keep to max 100 characters wide
|
||||
return
|
||||
"For a MIDI piano, use [Key 1-28] for velocity-sensitive input.\n"
|
||||
"For digital input (PC keyboard), use [Key 1-28 Medium].\n"
|
||||
"For velocity-sensitive controllers in HID mode, use Analog tab.\n"
|
||||
"\n"
|
||||
"[Key 1-28] are velocity sensitive.\n"
|
||||
"[Key 1-28 Soft] always trigger minimum velocity (ELEGANT in blue zone).\n"
|
||||
"[Key 1-28 Medium] always trigger medium velocity (ELEGANT in both blue and yellow zones).\n"
|
||||
"[Key 1-28 Hard] always trigger maximum velocity (ELEGANT in yellow zone)."
|
||||
"\n"
|
||||
"Swipe/Touch bindings are used for touch emulation, but enable -nostpoke first."
|
||||
;
|
||||
}
|
||||
|
||||
std::vector<Analog> &games::nost::get_analogs() {
|
||||
static std::vector<Analog> analogs;
|
||||
|
||||
|
||||
+100
-1
@@ -7,7 +7,7 @@ namespace games::nost {
|
||||
|
||||
// all buttons in correct order
|
||||
namespace Buttons {
|
||||
enum {
|
||||
enum Button {
|
||||
Service,
|
||||
Test,
|
||||
CoinMech,
|
||||
@@ -39,6 +39,104 @@ namespace games::nost {
|
||||
Key26,
|
||||
Key27,
|
||||
Key28,
|
||||
Key1Soft,
|
||||
Key2Soft,
|
||||
Key3Soft,
|
||||
Key4Soft,
|
||||
Key5Soft,
|
||||
Key6Soft,
|
||||
Key7Soft,
|
||||
Key8Soft,
|
||||
Key9Soft,
|
||||
Key10Soft,
|
||||
Key11Soft,
|
||||
Key12Soft,
|
||||
Key13Soft,
|
||||
Key14Soft,
|
||||
Key15Soft,
|
||||
Key16Soft,
|
||||
Key17Soft,
|
||||
Key18Soft,
|
||||
Key19Soft,
|
||||
Key20Soft,
|
||||
Key21Soft,
|
||||
Key22Soft,
|
||||
Key23Soft,
|
||||
Key24Soft,
|
||||
Key25Soft,
|
||||
Key26Soft,
|
||||
Key27Soft,
|
||||
Key28Soft,
|
||||
Key1Medium,
|
||||
Key2Medium,
|
||||
Key3Medium,
|
||||
Key4Medium,
|
||||
Key5Medium,
|
||||
Key6Medium,
|
||||
Key7Medium,
|
||||
Key8Medium,
|
||||
Key9Medium,
|
||||
Key10Medium,
|
||||
Key11Medium,
|
||||
Key12Medium,
|
||||
Key13Medium,
|
||||
Key14Medium,
|
||||
Key15Medium,
|
||||
Key16Medium,
|
||||
Key17Medium,
|
||||
Key18Medium,
|
||||
Key19Medium,
|
||||
Key20Medium,
|
||||
Key21Medium,
|
||||
Key22Medium,
|
||||
Key23Medium,
|
||||
Key24Medium,
|
||||
Key25Medium,
|
||||
Key26Medium,
|
||||
Key27Medium,
|
||||
Key28Medium,
|
||||
Key1Hard,
|
||||
Key2Hard,
|
||||
Key3Hard,
|
||||
Key4Hard,
|
||||
Key5Hard,
|
||||
Key6Hard,
|
||||
Key7Hard,
|
||||
Key8Hard,
|
||||
Key9Hard,
|
||||
Key10Hard,
|
||||
Key11Hard,
|
||||
Key12Hard,
|
||||
Key13Hard,
|
||||
Key14Hard,
|
||||
Key15Hard,
|
||||
Key16Hard,
|
||||
Key17Hard,
|
||||
Key18Hard,
|
||||
Key19Hard,
|
||||
Key20Hard,
|
||||
Key21Hard,
|
||||
Key22Hard,
|
||||
Key23Hard,
|
||||
Key24Hard,
|
||||
Key25Hard,
|
||||
Key26Hard,
|
||||
Key27Hard,
|
||||
Key28Hard,
|
||||
PokeNextPage,
|
||||
PokePrevPage,
|
||||
PokeConfirm,
|
||||
PokeBack,
|
||||
PokeSong1,
|
||||
PokeSong2,
|
||||
PokeSong3,
|
||||
PokeSong4,
|
||||
PokeSong5,
|
||||
PokeSong6,
|
||||
PokeDifficulty1,
|
||||
PokeDifficulty2,
|
||||
PokeDifficulty3,
|
||||
PokeDifficulty4,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -174,6 +272,7 @@ namespace games::nost {
|
||||
|
||||
// getters
|
||||
std::vector<Button> &get_buttons();
|
||||
std::string get_buttons_help();
|
||||
std::vector<Analog> &get_analogs();
|
||||
std::vector<Light> &get_lights();
|
||||
}
|
||||
|
||||
+17
-1
@@ -3,12 +3,15 @@
|
||||
#define _WIN32_WINNT 0x0601
|
||||
|
||||
#include "nost.h"
|
||||
#include "poke.h"
|
||||
#include "hooks/setupapihook.h"
|
||||
#include "misc/wintouchemu.h"
|
||||
#include "avs/game.h"
|
||||
|
||||
namespace games::nost {
|
||||
|
||||
bool ENABLE_POKE = false;
|
||||
|
||||
NostGame::NostGame() : Game("Nostalgia") {
|
||||
}
|
||||
|
||||
@@ -41,6 +44,19 @@ namespace games::nost {
|
||||
setupapihook_add(touch_settings);
|
||||
|
||||
// custom touch
|
||||
wintouchemu::hook("nostalgia", avs::game::DLL_INSTANCE);
|
||||
// nostalgia crashes if you inject touch events too early
|
||||
wintouchemu::hook("nostalgia", avs::game::DLL_INSTANCE, 20);
|
||||
}
|
||||
|
||||
void NostGame::post_attach() {
|
||||
if (ENABLE_POKE) {
|
||||
poke::enable();
|
||||
}
|
||||
}
|
||||
|
||||
void NostGame::detach() {
|
||||
if (ENABLE_POKE) {
|
||||
poke::disable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,13 @@
|
||||
|
||||
namespace games::nost {
|
||||
|
||||
extern bool ENABLE_POKE;
|
||||
|
||||
class NostGame : public games::Game {
|
||||
public:
|
||||
NostGame();
|
||||
virtual void attach() override;
|
||||
virtual void post_attach() override;
|
||||
virtual void detach() override;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
#include "poke.h"
|
||||
|
||||
#include <thread>
|
||||
#include <optional>
|
||||
|
||||
#include "games/nost/io.h"
|
||||
#include "cfg/api.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "touch/touch.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
namespace games::nost::poke {
|
||||
|
||||
static std::thread *THREAD = nullptr;
|
||||
static volatile bool THREAD_RUNNING = false;
|
||||
|
||||
static const std::unordered_map<int, std::pair<LONG, LONG>> NOST_POKE_NUM {
|
||||
{EAM_IO_KEYPAD_0, {760, 440}},
|
||||
{EAM_IO_KEYPAD_1, {760, 385}},
|
||||
{EAM_IO_KEYPAD_2, {820, 385}},
|
||||
{EAM_IO_KEYPAD_3, {880, 385}},
|
||||
{EAM_IO_KEYPAD_4, {760, 325}},
|
||||
{EAM_IO_KEYPAD_5, {820, 325}},
|
||||
{EAM_IO_KEYPAD_6, {880, 325}},
|
||||
{EAM_IO_KEYPAD_7, {760, 265}},
|
||||
{EAM_IO_KEYPAD_8, {820, 265}},
|
||||
{EAM_IO_KEYPAD_9, {880, 265}},
|
||||
{EAM_IO_KEYPAD_00, {820, 440}}, // erase button
|
||||
{EAM_IO_KEYPAD_DECIMAL, {880, 440}} // also erase button
|
||||
};
|
||||
|
||||
static const std::unordered_map<games::nost::Buttons::Button, std::pair<LONG, LONG>> NOST_POKE {
|
||||
{games::nost::Buttons::Button::PokeConfirm, {1100, 525}},
|
||||
{games::nost::Buttons::Button::PokeBack, {340, 100}},
|
||||
{games::nost::Buttons::Button::PokeSong1, {450, 190}},
|
||||
{games::nost::Buttons::Button::PokeSong2, {450, 330}},
|
||||
{games::nost::Buttons::Button::PokeSong3, {450, 480}},
|
||||
{games::nost::Buttons::Button::PokeSong4, {840, 190}},
|
||||
{games::nost::Buttons::Button::PokeSong5, {840, 330}},
|
||||
{games::nost::Buttons::Button::PokeSong6, {840, 480}},
|
||||
{games::nost::Buttons::Button::PokeDifficulty1, {820, 240}},
|
||||
{games::nost::Buttons::Button::PokeDifficulty2, {820, 320}},
|
||||
{games::nost::Buttons::Button::PokeDifficulty3, {820, 410}},
|
||||
{games::nost::Buttons::Button::PokeDifficulty4, {820, 490}},
|
||||
};
|
||||
|
||||
void clear_touch_points(std::vector<TouchPoint> *touch_points) {
|
||||
std::vector<DWORD> touch_ids;
|
||||
for (auto &touch : *touch_points) {
|
||||
touch_ids.emplace_back(touch.id);
|
||||
}
|
||||
touch_remove_points(&touch_ids);
|
||||
touch_points->clear();
|
||||
}
|
||||
|
||||
void enable() {
|
||||
// check if already running
|
||||
if (THREAD) {
|
||||
return;
|
||||
}
|
||||
|
||||
// create new thread
|
||||
THREAD_RUNNING = true;
|
||||
THREAD = new std::thread([] {
|
||||
const DWORD touch_id = (DWORD)(0xFFFFFFFE);
|
||||
|
||||
const int swipe_anim_total_frames = 6;
|
||||
const int swipe_anim_y = 300;
|
||||
|
||||
const int swipe_next_page_x_begin = 1120;
|
||||
const int swipe_next_page_x_end = 1120-120;
|
||||
|
||||
const int swipe_prev_page_x_begin = 280-120;
|
||||
const int swipe_prev_page_x_end = 280;
|
||||
|
||||
int next_page_anim_index = -1;
|
||||
int prev_page_anim_index = -1;
|
||||
std::vector<TouchPoint> touch_points;
|
||||
|
||||
bool touch_release = false;
|
||||
|
||||
// log
|
||||
log_info("poke", "enabled");
|
||||
|
||||
// set variable to false to stop
|
||||
while (THREAD_RUNNING) {
|
||||
// clean up touch from last frame
|
||||
if (!touch_points.empty()) {
|
||||
if (touch_release) {
|
||||
clear_touch_points(&touch_points);
|
||||
touch_release = false;
|
||||
} else {
|
||||
touch_points.clear();
|
||||
}
|
||||
}
|
||||
|
||||
auto &buttons = games::nost::get_buttons();
|
||||
|
||||
if (0 <= next_page_anim_index) {
|
||||
const auto delta =
|
||||
(swipe_next_page_x_end - swipe_next_page_x_begin)
|
||||
* (swipe_anim_total_frames - next_page_anim_index) / swipe_anim_total_frames;
|
||||
|
||||
TouchPoint tp {
|
||||
.id = touch_id,
|
||||
.x = (LONG)swipe_next_page_x_begin + delta,
|
||||
.y = (LONG)swipe_anim_y,
|
||||
.mouse = true,
|
||||
};
|
||||
touch_points.emplace_back(tp);
|
||||
next_page_anim_index--;
|
||||
if (next_page_anim_index < 0) {
|
||||
touch_release = true;
|
||||
}
|
||||
} else if (0 <= prev_page_anim_index) {
|
||||
const auto delta =
|
||||
(swipe_prev_page_x_end - swipe_prev_page_x_begin)
|
||||
* (swipe_anim_total_frames - prev_page_anim_index) / swipe_anim_total_frames;
|
||||
|
||||
TouchPoint tp {
|
||||
.id = touch_id,
|
||||
.x = (LONG)swipe_prev_page_x_begin + delta,
|
||||
.y = (LONG)swipe_anim_y,
|
||||
.mouse = true,
|
||||
};
|
||||
touch_points.emplace_back(tp);
|
||||
prev_page_anim_index--;
|
||||
if (prev_page_anim_index < 0) {
|
||||
touch_release = true;
|
||||
}
|
||||
} else {
|
||||
for (const auto& it : NOST_POKE) {
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(it.first))) {
|
||||
TouchPoint tp {
|
||||
.id = touch_id,
|
||||
.x = it.second.first,
|
||||
.y = it.second.second,
|
||||
.mouse = true,
|
||||
};
|
||||
touch_points.emplace_back(tp);
|
||||
touch_release = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!touch_release) {
|
||||
const auto state = eamuse_get_keypad_state(0);
|
||||
if (state) {
|
||||
for (const auto& it : NOST_POKE_NUM) {
|
||||
if (state & (1 << it.first)) {
|
||||
TouchPoint tp {
|
||||
.id = touch_id,
|
||||
.x = it.second.first,
|
||||
.y = it.second.second,
|
||||
.mouse = true,
|
||||
};
|
||||
touch_points.emplace_back(tp);
|
||||
touch_release = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// start animations for next frame
|
||||
if (!touch_release) {
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(games::nost::Buttons::PokeNextPage))) {
|
||||
next_page_anim_index = swipe_anim_total_frames;
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(games::nost::Buttons::PokePrevPage))) {
|
||||
prev_page_anim_index = swipe_anim_total_frames;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!touch_points.empty()) {
|
||||
touch_write_points(&touch_points);
|
||||
}
|
||||
|
||||
// slow down
|
||||
Sleep(30);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
});
|
||||
}
|
||||
|
||||
void disable() {
|
||||
if (!THREAD) {
|
||||
return;
|
||||
}
|
||||
|
||||
// stop old thread
|
||||
THREAD_RUNNING = false;
|
||||
THREAD->join();
|
||||
|
||||
// delete thread
|
||||
delete THREAD;
|
||||
THREAD = nullptr;
|
||||
|
||||
// log
|
||||
log_info("poke", "disabled");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace games::nost::poke {
|
||||
void enable();
|
||||
void disable();
|
||||
}
|
||||
+44
-10
@@ -1,18 +1,22 @@
|
||||
#include "pcm.h"
|
||||
|
||||
#include "acio/icca/icca.h"
|
||||
#include "io.h"
|
||||
#include "misc/wintouchemu.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/utils.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/time.h"
|
||||
|
||||
namespace games::pcm {
|
||||
uint32_t *(__cdecl *GsIo_GetState_orig)(uint32_t *state);
|
||||
static double BILL_IN_TIME = 0;
|
||||
static double BILL_DELAY = 2.0;
|
||||
|
||||
static int BillKind = 0;
|
||||
static int bill_kind = 0;
|
||||
|
||||
static int __cdecl BillVali_GetEscrowBillKind() {
|
||||
return BillKind;
|
||||
return bill_kind;
|
||||
}
|
||||
|
||||
static int __cdecl BillVali_GetCurrentStep() {
|
||||
@@ -28,25 +32,25 @@ namespace games::pcm {
|
||||
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::pcm::Buttons::Insert1000YenBill])) {
|
||||
log_misc("pcm", "1000 yen bill inserted");
|
||||
BillKind = 1;
|
||||
bill_kind = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::pcm::Buttons::Insert2000YenBill])) {
|
||||
log_misc("pcm", "2000 yen bill inserted");
|
||||
BillKind = 2;
|
||||
bill_kind = 2;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::pcm::Buttons::Insert5000YenBill])) {
|
||||
log_misc("pcm", "5000 yen bill inserted");
|
||||
BillKind = 3;
|
||||
bill_kind = 3;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::pcm::Buttons::Insert10000YenBill])) {
|
||||
log_misc("pcm", "10000 yen bill inserted");
|
||||
BillKind = 4;
|
||||
bill_kind = 4;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -62,19 +66,42 @@ namespace games::pcm {
|
||||
}
|
||||
|
||||
static bool __cdecl BillVali_IsWaiting() {
|
||||
return true;
|
||||
return BILL_IN_TIME == 0;
|
||||
}
|
||||
|
||||
static int __cdecl BillVali_GetReceivedBill() {
|
||||
static int bills_received = 0;
|
||||
|
||||
return ++bills_received;
|
||||
if (BILL_IN_TIME == 0 && BillVali_IsEscrowNow()) {
|
||||
BILL_IN_TIME = get_performance_seconds();
|
||||
} else if ((BILL_IN_TIME != 0) && ((BILL_IN_TIME + BILL_DELAY) <= get_performance_seconds())) {
|
||||
BILL_IN_TIME = 0;
|
||||
switch (bill_kind) {
|
||||
case 1:
|
||||
return 1;
|
||||
case 2:
|
||||
return 2;
|
||||
case 3:
|
||||
return 5;
|
||||
case 4:
|
||||
return 10;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool __cdecl BillVali_ReceiveBill(int) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool __cdecl BillVali_ReceiveCancel() {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool __cdecl BillVali_SetAcceptBill(int) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint32_t* __cdecl GsIo_GetState_hook(uint32_t *state) {
|
||||
GsIo_GetState_orig(state);
|
||||
|
||||
@@ -92,6 +119,9 @@ namespace games::pcm {
|
||||
// system.dll doesn't get loaded until later so load it now for hooking.
|
||||
HMODULE system = libutils::try_library("system.dll");
|
||||
|
||||
// fix ICCA
|
||||
acio::ICCA_COMPAT_ACTIVE = true;
|
||||
|
||||
// apply hooks
|
||||
detour::trampoline("system.dll", "?GetState@GsIo@@SA?BVSTATE@1@XZ",
|
||||
GsIo_GetState_hook, &GsIo_GetState_orig);
|
||||
@@ -112,7 +142,11 @@ namespace games::pcm {
|
||||
system, "?IsReady@GsBillVali@@SA_NXZ"));
|
||||
detour::inline_hook(BillVali_ReceiveBill, libutils::try_proc(
|
||||
system, "?ReceiveBill@GsBillVali@@SA_NH@Z"));
|
||||
detour::inline_hook(BillVali_ReceiveCancel, libutils::try_proc(
|
||||
system, "?ReceiveCancel@GsBillVali@@SAHXZ"));
|
||||
detour::inline_hook(BillVali_GetEscrowBillKind, libutils::try_proc(
|
||||
system, "?GetEscrowBillKind@GsBillVali@@SAHXZ"));
|
||||
detour::inline_hook(BillVali_SetAcceptBill, libutils::try_proc(
|
||||
system, "?SetAcceptBill@GsBillVali@@SA_NH@Z"));
|
||||
}
|
||||
}
|
||||
|
||||
+12
-1
@@ -26,6 +26,14 @@ std::vector<Button> &games::popn::get_buttons() {
|
||||
return buttons;
|
||||
}
|
||||
|
||||
std::string games::popn::get_buttons_help() {
|
||||
// keep to max 100 characters wide
|
||||
return
|
||||
" 2 4 6 8 \n"
|
||||
" 1 3 5 7 9 "
|
||||
;
|
||||
}
|
||||
|
||||
std::vector<Light> &games::popn::get_lights() {
|
||||
static std::vector<Light> lights;
|
||||
|
||||
@@ -86,7 +94,10 @@ std::vector<Light> &games::popn::get_lights() {
|
||||
"Left Lamp 1",
|
||||
"Left Lamp 2",
|
||||
"Right Lamp 1",
|
||||
"Right Lamp 2"
|
||||
"Right Lamp 2",
|
||||
"Woofer LED R",
|
||||
"Woofer LED G",
|
||||
"Woofer LED B"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -79,10 +79,14 @@ namespace games::popn {
|
||||
LeftLamp2,
|
||||
RightLamp1,
|
||||
RightLamp2,
|
||||
WooferLED_R,
|
||||
WooferLED_G,
|
||||
WooferLED_B,
|
||||
};
|
||||
}
|
||||
|
||||
// getters
|
||||
std::vector<Button> &get_buttons();
|
||||
std::string get_buttons_help();
|
||||
std::vector<Light> &get_lights();
|
||||
}
|
||||
|
||||
+26
-1
@@ -4,6 +4,7 @@
|
||||
#include <cstring>
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/fileutils.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/utils.h"
|
||||
#include "cfg/button.h"
|
||||
@@ -167,7 +168,26 @@ namespace games::popn {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __cdecl usbSetExtIo() {
|
||||
static int __cdecl usbSetExtIo(uint32_t rgb_bits) {
|
||||
|
||||
// get lights
|
||||
auto &lights = get_lights();
|
||||
|
||||
// bit scan
|
||||
static const size_t mapping[] = {
|
||||
Lights::WooferLED_R,
|
||||
Lights::WooferLED_G,
|
||||
Lights::WooferLED_B,
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < 3; i++) {
|
||||
float value = (rgb_bits & (1 << (i + 1)));
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights.at(mapping[i]), value);
|
||||
}
|
||||
|
||||
// flush output
|
||||
RI_MGR->devices_flush_output();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -229,6 +249,11 @@ namespace games::popn {
|
||||
void POPNGame::pre_attach() {
|
||||
Game::pre_attach();
|
||||
|
||||
// only needed for older versions (19-21, inclusive) but create them anyway since 21 and above are all M39
|
||||
fileutils::dir_create_recursive_log("popn", "dev\\raw\\bookkeeping");
|
||||
fileutils::dir_create_recursive_log("popn", "dev\\raw\\ranking");
|
||||
fileutils::dir_create_recursive_log("popn", "dev\\raw\\settings");
|
||||
|
||||
// load without resolving references
|
||||
// makes game not trigger DLLMain which results in an error due to missing EZUSB device
|
||||
LoadLibraryExW((MODULE_PATH / "ezusb.dll").c_str(), nullptr, DONT_RESOLVE_DLL_REFERENCES);
|
||||
|
||||
@@ -0,0 +1,605 @@
|
||||
#include "bi2x_hook.h"
|
||||
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "games/io.h"
|
||||
#include "io.h"
|
||||
#include "util/tapeled.h"
|
||||
|
||||
namespace games::qks {
|
||||
|
||||
/*
|
||||
* class definitions
|
||||
*/
|
||||
|
||||
struct AIO_SCI_COMM {
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB {
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_AC1 {
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_WRFIRM {
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB__NODEINFO {
|
||||
uint8_t data[0xA3];
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_AC1__INPUT {
|
||||
uint8_t DevIoCounter;
|
||||
uint8_t bExIoAErr;
|
||||
uint8_t bExIoBErr;
|
||||
uint8_t bPcPowerOn;
|
||||
uint8_t bPcPowerCheck;
|
||||
uint8_t bCoin1Jam;
|
||||
uint8_t bCoin2Jam;
|
||||
uint8_t bCoin3Jam;
|
||||
uint8_t bCoin4Jam;
|
||||
uint8_t Coin1Count;
|
||||
uint8_t Coin2Count;
|
||||
uint8_t Coin3Count;
|
||||
uint8_t Coin4Count;
|
||||
uint16_t AnalogCh1;
|
||||
uint16_t AnalogCh2;
|
||||
uint16_t AnalogCh3;
|
||||
uint16_t AnalogCh4;
|
||||
uint8_t CN8_8;
|
||||
uint8_t CN8_9;
|
||||
uint8_t CN8_10;
|
||||
uint8_t CN9_8;
|
||||
uint8_t CN9_9;
|
||||
uint8_t CN9_10;
|
||||
uint8_t CN11_11;
|
||||
uint8_t CN11_12;
|
||||
uint8_t CN11_13;
|
||||
uint8_t CN11_14;
|
||||
uint8_t CN11_15;
|
||||
uint8_t CN11_16;
|
||||
uint8_t CN11_17;
|
||||
uint8_t CN11_18;
|
||||
uint8_t CN11_19;
|
||||
uint8_t CN11_20;
|
||||
uint8_t CN12_11;
|
||||
uint8_t CN12_12;
|
||||
uint8_t CN12_13;
|
||||
uint8_t CN12_14;
|
||||
uint8_t CN12_15;
|
||||
uint8_t CN12_16;
|
||||
uint8_t CN12_17;
|
||||
uint8_t CN12_18;
|
||||
uint8_t CN12_19;
|
||||
uint8_t CN12_20;
|
||||
uint8_t CN12_21;
|
||||
uint8_t CN12_22;
|
||||
uint8_t CN12_23;
|
||||
uint8_t CN12_24;
|
||||
uint8_t CN15_3;
|
||||
uint8_t CN15_4;
|
||||
uint8_t CN15_5;
|
||||
uint8_t CN15_6;
|
||||
uint8_t CN15_7;
|
||||
uint8_t CN15_8;
|
||||
uint8_t CN15_9;
|
||||
uint8_t CN15_10;
|
||||
uint8_t CN15_11;
|
||||
uint8_t CN15_12;
|
||||
uint8_t CN15_13;
|
||||
uint8_t CN15_14;
|
||||
uint8_t CN15_15;
|
||||
uint8_t CN15_16;
|
||||
uint8_t CN15_17;
|
||||
uint8_t CN15_18;
|
||||
uint8_t CN15_19;
|
||||
uint8_t CN15_20;
|
||||
uint8_t CN19_8;
|
||||
uint8_t CN19_9;
|
||||
uint8_t CN19_10;
|
||||
uint8_t CN19_11;
|
||||
uint8_t CN19_12;
|
||||
uint8_t CN19_13;
|
||||
uint8_t CN19_14;
|
||||
uint8_t CN19_15;
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_AC1__INPUTDATA {
|
||||
uint8_t Data[228];
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_AC1__OUTPUTDATA {
|
||||
uint8_t Data[48];
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_AC1__IORESETDATA {
|
||||
uint8_t Data[4];
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_AC1__ICNPIN {
|
||||
uint16_t Ain[4];
|
||||
uint64_t CnPin;
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_AC1__SETTING_AMPVOL {
|
||||
uint8_t m_CnDown;
|
||||
uint8_t m_PinDown;
|
||||
uint8_t m_CnUp;
|
||||
uint8_t m_PinUp;
|
||||
uint8_t m_CnMute;
|
||||
uint8_t m_PinMute;
|
||||
uint16_t m_OnTime;
|
||||
uint16_t m_OffTime;
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_AC1__SETTING_COUNTER {
|
||||
uint8_t m_Connector;
|
||||
uint8_t m_Pin;
|
||||
uint16_t m_OnTime;
|
||||
uint16_t m_OffTime;
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_AC1__SETTING_COIN {
|
||||
uint8_t m_Connector;
|
||||
uint8_t m_Pin;
|
||||
uint8_t m_OnTime;
|
||||
uint8_t m_OffTime;
|
||||
uint16_t m_JamTimeout;
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_AC1__SETTING {
|
||||
AIO_IOB2_BI2X_AC1__SETTING_COIN m_aCoin[4];
|
||||
AIO_IOB2_BI2X_AC1__SETTING_COUNTER m_aCounter[4];
|
||||
AIO_IOB2_BI2X_AC1__SETTING_AMPVOL m_aAmpVol[4];
|
||||
|
||||
uint16_t m_aTapeLed[8];
|
||||
uint8_t m_TestFirm;
|
||||
uint8_t m_bEnableDbgMon;
|
||||
uint8_t m_bDisableSciCmd;
|
||||
uint8_t m_bDisablePcPwrCtl;
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_AC1__DEVSTATUS {
|
||||
uint8_t InputCounter;
|
||||
uint8_t OutputCounter;
|
||||
uint8_t TapeLedCounter;
|
||||
uint8_t TapeLedRate[8];
|
||||
AIO_IOB2_BI2X_AC1__INPUT Input;
|
||||
AIO_IOB2_BI2X_AC1__INPUTDATA InputData;
|
||||
AIO_IOB2_BI2X_AC1__OUTPUTDATA OutputData;
|
||||
AIO_IOB2_BI2X_AC1__IORESETDATA IoResetData;
|
||||
AIO_IOB2_BI2X_AC1__ICNPIN ICnPinHist[17];
|
||||
AIO_IOB2_BI2X_AC1__SETTING Setting;
|
||||
};
|
||||
|
||||
/*
|
||||
* typedefs
|
||||
*/
|
||||
|
||||
// libaio-iob2_video.dll
|
||||
// (Konami.Aio.aioIob2Bi2xAC1)
|
||||
typedef AIO_IOB2_BI2X_AC1* (__fastcall *aioIob2Bi2xAC1_Create_t)(AIO_NMGR_IOB *i_pNodeMgr, uint32_t i_DevId, AIO_IOB2_BI2X_AC1__SETTING *i_pSetting, uint32_t i_cbSetting);
|
||||
typedef void (__fastcall *aioIob2Bi2xAC1_GetDeviceStatus_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, AIO_IOB2_BI2X_AC1__DEVSTATUS *o_DevStatus, uint32_t i_cbDevStatus);
|
||||
typedef void (__fastcall *aioIob2Bi2xAC1_IoReset_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_bfIoReset);
|
||||
typedef void (__fastcall *aioIob2Bi2xAC1_SetWatchDogTimer_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint8_t i_Count);
|
||||
typedef void (__fastcall *aioIob2Bi2xAC1_AddCounter_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_Counter, uint32_t i_Count);
|
||||
typedef void (__fastcall *aioIob2Bi2xAC1_SetAmpVolume_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_Amp, uint32_t i_Volume);
|
||||
typedef void (__fastcall *aioIob2Bi2xAC1_SetOutputData_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_CnPin, uint8_t i_Data);
|
||||
typedef void (__fastcall *aioIob2Bi2xAC1_SetTapeLedDataPart_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_TapeLedCh, uint32_t i_Offset, uint8_t i_pData[], uint32_t i_cntTapeLed, bool i_bReverse);
|
||||
|
||||
// libaio-iob.dll
|
||||
// (Konami.Aio.aioNMgrIob)
|
||||
typedef AIO_NMGR_IOB* (__fastcall *aioNMgrIob2_Create_t)(AIO_SCI_COMM *i_pSci, uint32_t i_bfMode);
|
||||
typedef uint32_t (__fastcall *aioNMgrIob_GetCommStatus_t)(AIO_NMGR_IOB *i_pNodeMgr, AIO_NMGR_IOB__NODEINFO o_pCommStatus, uint32_t i_cbCommStatus);
|
||||
typedef void (__fastcall *aioNMgrIob_BeginManage_t)(AIO_NMGR_IOB *i_pNodeMgr);
|
||||
// (Konami.Aio.aioNCtlIob)
|
||||
typedef void (__fastcall *aioNCtlIob_GetNodeInfo_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, AIO_NMGR_IOB__NODEINFO *o_NodeInfo, uint32_t i_cbNodeInfo);
|
||||
// (Konami.Aio.aioIob2Bi2x)
|
||||
typedef AIO_SCI_COMM* (__fastcall *aioIob2Bi2x_OpenSciUsbCdc_t)(uint32_t i_SerialNumber);
|
||||
typedef AIO_SCI_COMM* (__fastcall *aioIob2Bi2x_SciOpenB8PNS1_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_Port, uint32_t i_BaudRate);
|
||||
typedef void (__fastcall *aioIob2Bi2x_SetTapeLedDataGroup_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint8_t i_bfGroup);
|
||||
typedef void (__fastcall *aioIob2Bi2x_SetTapeLedDataLimit_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_Channel, uint8_t i_Scale, uint8_t i_Limit);
|
||||
typedef AIO_IOB2_BI2X_WRFIRM* (__fastcall *aioIob2Bi2x_CreateWriteFirmContext_t)(uint32_t i_SerialNumber, uint32_t i_bfIob);
|
||||
typedef void (__fastcall *aioIob2Bi2x_DestroyWriteFirmContext_t)(AIO_IOB2_BI2X_WRFIRM *i_pWrFirm);
|
||||
typedef int32_t (__fastcall *aioIob2Bi2x_WriteFirmGetState_t)(AIO_IOB2_BI2X_WRFIRM *i_pWrFirm);
|
||||
typedef bool (__fastcall *aioIob2Bi2x_WriteFirmIsCompleted_t)(int32_t i_State);
|
||||
typedef bool (__fastcall *aioIob2Bi2x_WriteFirmIsError_t)(int32_t i_State);
|
||||
|
||||
// libaio.dll
|
||||
// (Konami.Aio.aioNodeMgr)
|
||||
typedef void (__fastcall *aioNodeMgr_Destroy_t)(AIO_NMGR_IOB *i_pNodeMgr);
|
||||
typedef int32_t (__fastcall *aioNodeMgr_GetState_t)(AIO_NMGR_IOB *i_pNodeMgr);
|
||||
typedef bool (__fastcall *aioNodeMgr_IsReady_t)(AIO_NMGR_IOB *i_pNodeMgr, int32_t i_State);
|
||||
typedef bool (__fastcall *aioNodeMgr_IsError_t)(AIO_NMGR_IOB *i_pNodeMgr, int32_t i_State);
|
||||
// (Konami.Aio.aioNodeCtl)
|
||||
typedef void (__fastcall *aioNodeCtl_Destroy_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl);
|
||||
typedef int32_t (__fastcall *aioNodeCtl_GetState_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl);
|
||||
typedef bool (__fastcall *aioNodeCtl_IsReady_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, int32_t i_State);
|
||||
typedef bool (__fastcall *aioNodeCtl_IsError_t)(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, int32_t i_State);
|
||||
|
||||
/*
|
||||
* function pointers
|
||||
*/
|
||||
|
||||
// libaio-iob2_video.dll
|
||||
// (Konami.Aio.aioIob2Bi2xAC1)
|
||||
static aioIob2Bi2xAC1_Create_t aioIob2Bi2xAC1_Create_orig = nullptr;
|
||||
static aioIob2Bi2xAC1_GetDeviceStatus_t aioIob2Bi2xAC1_GetDeviceStatus_orig = nullptr;
|
||||
static aioIob2Bi2xAC1_IoReset_t aioIob2Bi2xAC1_IoReset_orig = nullptr;
|
||||
static aioIob2Bi2xAC1_SetWatchDogTimer_t aioIob2Bi2xAC1_SetWatchDogTimer_orig = nullptr;
|
||||
static aioIob2Bi2xAC1_AddCounter_t aioIob2Bi2xAC1_AddCounter_orig = nullptr;
|
||||
static aioIob2Bi2xAC1_SetAmpVolume_t aioIob2Bi2xAC1_SetAmpVolume_orig = nullptr;
|
||||
static aioIob2Bi2xAC1_SetOutputData_t aioIob2Bi2xAC1_SetOutputData_orig = nullptr;
|
||||
static aioIob2Bi2xAC1_SetTapeLedDataPart_t aioIob2Bi2xAC1_SetTapeLedDataPart_orig = nullptr;
|
||||
|
||||
// libaio-iob.dll
|
||||
// (Konami.Aio.aioNMgrIob)
|
||||
static aioNMgrIob2_Create_t aioNMgrIob2_Create_orig = nullptr;
|
||||
static aioNMgrIob_GetCommStatus_t aioNMgrIob_GetCommStatus_orig = nullptr;
|
||||
static aioNMgrIob_BeginManage_t aioNMgrIob_BeginManage_orig = nullptr;
|
||||
// (Konami.Aio.aioNCtlIob)
|
||||
static aioNCtlIob_GetNodeInfo_t aioNCtlIob_GetNodeInfo_orig = nullptr;
|
||||
// (Konami.Aio.aioIob2Bi2x)
|
||||
static aioIob2Bi2x_OpenSciUsbCdc_t aioIob2Bi2x_OpenSciUsbCdc_orig = nullptr;
|
||||
static aioIob2Bi2x_SciOpenB8PNS1_t aioIob2Bi2x_SciOpenB8PNS1_orig = nullptr;
|
||||
static aioIob2Bi2x_SetTapeLedDataGroup_t aioIob2Bi2x_SetTapeLedDataGroup_orig = nullptr;
|
||||
static aioIob2Bi2x_SetTapeLedDataLimit_t aioIob2Bi2x_SetTapeLedDataLimit_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;
|
||||
|
||||
// libaio.dll
|
||||
// (Konami.Aio.aioNodeMgr)
|
||||
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 aioNodeMgr_IsError_t aioNodeMgr_IsError_orig = nullptr;
|
||||
// (Konami.Aio.aioNodeCtl)
|
||||
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 aioNodeCtl_IsError_t aioNodeCtl_IsError_orig = nullptr;
|
||||
|
||||
/*
|
||||
* variables
|
||||
*/
|
||||
|
||||
static AIO_SCI_COMM *aioSciComm;
|
||||
static AIO_NMGR_IOB *aioNmgrIob;
|
||||
static AIO_IOB2_BI2X_AC1 *aioIob2Bi2xAc1;
|
||||
static AIO_IOB2_BI2X_WRFIRM *aioIob2Bi2xWrfirm;
|
||||
static uint8_t count = 0;
|
||||
|
||||
/*
|
||||
* implementations
|
||||
*/
|
||||
|
||||
static AIO_IOB2_BI2X_AC1* __fastcall aioIob2Bi2xAC1_Create(AIO_NMGR_IOB *i_pNodeMgr, uint32_t i_DevId, AIO_IOB2_BI2X_AC1__SETTING *i_pSetting, uint32_t i_cbSetting) {
|
||||
if (i_pNodeMgr == aioNmgrIob) {
|
||||
log_info("bi2x_hook", "node created");
|
||||
aioIob2Bi2xAc1 = new AIO_IOB2_BI2X_AC1;
|
||||
return aioIob2Bi2xAc1;
|
||||
}
|
||||
return aioIob2Bi2xAC1_Create_orig(i_pNodeMgr, i_DevId, i_pSetting, i_cbSetting);
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xAC1_GetDeviceStatus(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, AIO_IOB2_BI2X_AC1__DEVSTATUS *o_DevStatus, uint32_t i_cbDevStatus) {
|
||||
RI_MGR->devices_flush_output();
|
||||
|
||||
if (i_pNodeCtl != aioIob2Bi2xAc1) {
|
||||
return aioIob2Bi2xAC1_GetDeviceStatus_orig(i_pNodeCtl, o_DevStatus, i_cbDevStatus);
|
||||
}
|
||||
|
||||
memset(o_DevStatus, 0x00, sizeof(AIO_IOB2_BI2X_AC1__DEVSTATUS));
|
||||
|
||||
auto &buttons = get_buttons();
|
||||
|
||||
o_DevStatus->Input.DevIoCounter = count;
|
||||
o_DevStatus->InputCounter = count;
|
||||
o_DevStatus->Input.CN11_13 = 1; // Alive flag?
|
||||
o_DevStatus->Input.CN8_8 = 1;
|
||||
o_DevStatus->Input.CN8_9 = 1;
|
||||
o_DevStatus->Input.CN8_10 = 1;
|
||||
o_DevStatus->Input.CN15_3 = 0;
|
||||
o_DevStatus->Input.CN15_4 = 0;
|
||||
o_DevStatus->Input.CN15_5 = 0;
|
||||
o_DevStatus->Input.CN15_8 = 1;
|
||||
o_DevStatus->Input.CN12_12 = 0;
|
||||
o_DevStatus->Input.CN12_13 = 0;
|
||||
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test])) {
|
||||
o_DevStatus->Input.CN8_8 = 0;
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service])) {
|
||||
o_DevStatus->Input.CN8_9 = 0;
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Coin])) {
|
||||
o_DevStatus->Input.CN8_10 = 0;
|
||||
eamuse_coin_add();
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::QButton1])) {
|
||||
o_DevStatus->Input.CN15_3 = 1;
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::QButton2])) {
|
||||
o_DevStatus->Input.CN15_4 = 1;
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::QButton3])) {
|
||||
o_DevStatus->Input.CN15_5 = 1;
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::QButton])) {
|
||||
o_DevStatus->Input.CN15_8 = 0;
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::JackDetect])) {
|
||||
o_DevStatus->Input.CN12_12 = 1;
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::MicDetect])) {
|
||||
o_DevStatus->Input.CN12_13 = 1;
|
||||
}
|
||||
|
||||
o_DevStatus->Input.Coin1Count = eamuse_coin_get_stock();
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xAC1_IoReset(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_bfIoReset) {
|
||||
if (i_pNodeCtl != aioIob2Bi2xAc1) {
|
||||
return aioIob2Bi2xAC1_IoReset_orig(i_pNodeCtl, i_bfIoReset);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xAC1_SetWatchDogTimer(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint8_t i_Count) {
|
||||
if (i_pNodeCtl != aioIob2Bi2xAc1) {
|
||||
return aioIob2Bi2xAC1_SetWatchDogTimer_orig(i_pNodeCtl, i_Count);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xAC1_AddCounter(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_Counter, uint32_t i_Count) {
|
||||
if (i_pNodeCtl == aioIob2Bi2xAc1 && i_Count == 0) {
|
||||
eamuse_coin_set_stock((uint16_t) i_Count);
|
||||
return;
|
||||
}
|
||||
return aioIob2Bi2xAC1_AddCounter_orig(i_pNodeCtl, i_Counter, i_Count);
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xAC1_SetAmpVolume(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_Amp, uint32_t i_Volume) {
|
||||
if (i_pNodeCtl != aioIob2Bi2xAc1) {
|
||||
return aioIob2Bi2xAC1_SetAmpVolume_orig(i_pNodeCtl, i_Amp, i_Volume);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xAC1_SetOutputData(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_CnPin, uint8_t i_Data) {
|
||||
if (i_pNodeCtl == aioIob2Bi2xAc1 && i_CnPin == 1) {
|
||||
eamuse_coin_set_block(i_Data == 0);
|
||||
return;
|
||||
}
|
||||
// TODO: LED impl
|
||||
// return aioIob2Bi2xAC1_SetOutputData_orig(i_pNodeCtl, i_CnPin, i_Data);
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2xAC1_SetTapeLedDataPart(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_TapeLedCh, uint32_t i_Offset, uint8_t i_pData[], uint32_t i_cntTapeLed, bool i_bReverse) {
|
||||
// TODO: LED tape impl
|
||||
// return aioIob2Bi2xAC1_SetTapeLedDataPart_orig(i_pNodeCtl, i_TapeLedCh, i_Offset, i_pData, i_cntTapeLed, i_bReverse);
|
||||
}
|
||||
|
||||
static AIO_NMGR_IOB* __fastcall aioNMgrIob2_Create(AIO_SCI_COMM *i_pSci, uint32_t i_bfMode) {
|
||||
if (i_pSci == aioSciComm) {
|
||||
aioNmgrIob = new AIO_NMGR_IOB;
|
||||
return aioNmgrIob;
|
||||
}
|
||||
return aioNMgrIob2_Create_orig(i_pSci, i_bfMode);
|
||||
}
|
||||
|
||||
static uint32_t __fastcall aioNMgrIob_GetCommStatus(AIO_NMGR_IOB *i_pNodeMgr, AIO_NMGR_IOB__NODEINFO o_pCommStatus, uint32_t i_cbCommStatus) {
|
||||
return aioNMgrIob_GetCommStatus_orig(i_pNodeMgr, o_pCommStatus, i_cbCommStatus);
|
||||
}
|
||||
|
||||
static void __fastcall aioNMgrIob_BeginManage(AIO_NMGR_IOB *i_pNodeMgr) {
|
||||
if (i_pNodeMgr != aioNmgrIob) {
|
||||
return aioNMgrIob_BeginManage_orig(i_pNodeMgr);
|
||||
}
|
||||
}
|
||||
|
||||
static void __fastcall aioNCtlIob_GetNodeInfo(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, AIO_NMGR_IOB__NODEINFO *o_NodeInfo, uint32_t i_cbNodeInfo) {
|
||||
if (i_pNodeCtl == aioIob2Bi2xAc1) {
|
||||
memset(o_NodeInfo, 0, sizeof(AIO_NMGR_IOB__NODEINFO));
|
||||
} else {
|
||||
return aioNCtlIob_GetNodeInfo_orig(i_pNodeCtl, o_NodeInfo, i_cbNodeInfo);
|
||||
}
|
||||
}
|
||||
|
||||
static AIO_SCI_COMM* __fastcall aioIob2Bi2x_OpenSciUsbCdc(uint32_t i_SerialNumber) {
|
||||
aioSciComm = new AIO_SCI_COMM;
|
||||
return aioSciComm;
|
||||
}
|
||||
|
||||
static AIO_SCI_COMM* __fastcall aioIob2Bi2x_SciOpenB8PNS1(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_Port, uint32_t i_BaudRate) {
|
||||
log_info("bi2x_hook", "aioIob2Bi2x_SciOpenB8PNS1");
|
||||
return aioIob2Bi2x_SciOpenB8PNS1_orig(i_pNodeCtl, i_Port, i_BaudRate);
|
||||
}
|
||||
|
||||
static AIO_IOB2_BI2X_WRFIRM* __fastcall aioIob2Bi2x_CreateWriteFirmContext(uint32_t i_SerialNumber, uint32_t i_bfIob) {
|
||||
aioIob2Bi2xWrfirm = new AIO_IOB2_BI2X_WRFIRM;
|
||||
return aioIob2Bi2xWrfirm;
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2x_SetTapeLedDataGroup(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint8_t i_bfGroup) {
|
||||
log_info("bi2x_hook", "aioIob2Bi2x_SetTapeLedDataGroup");
|
||||
return aioIob2Bi2x_SetTapeLedDataGroup_orig(i_pNodeCtl, i_bfGroup);
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2x_SetTapeLedDataLimit(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_Channel, uint8_t i_Scale, uint8_t i_Limit) {
|
||||
log_info("bi2x_hook", "aioIob2Bi2x_SetTapeLedDataLimit");
|
||||
return aioIob2Bi2x_SetTapeLedDataLimit_orig(i_pNodeCtl, i_Channel, i_Scale, i_Limit);
|
||||
}
|
||||
|
||||
static void __fastcall aioIob2Bi2x_DestroyWriteFirmContext(AIO_IOB2_BI2X_WRFIRM *i_pWrFirm) {
|
||||
if (i_pWrFirm != aioIob2Bi2xWrfirm) {
|
||||
return aioIob2Bi2x_DestroyWriteFirmContext_orig(i_pWrFirm);
|
||||
}
|
||||
delete aioIob2Bi2xWrfirm;
|
||||
aioIob2Bi2xWrfirm = nullptr;
|
||||
}
|
||||
|
||||
static int32_t __fastcall aioIob2Bi2x_WriteFirmGetState(AIO_IOB2_BI2X_WRFIRM *i_pWrFirm) {
|
||||
if (i_pWrFirm == aioIob2Bi2xWrfirm) {
|
||||
return 8;
|
||||
}
|
||||
return aioIob2Bi2x_WriteFirmGetState_orig(i_pWrFirm);
|
||||
}
|
||||
|
||||
static bool __fastcall aioIob2Bi2x_WriteFirmIsCompleted(int32_t i_State) {
|
||||
if (aioIob2Bi2xWrfirm != nullptr) {
|
||||
return true;
|
||||
}
|
||||
return aioIob2Bi2x_WriteFirmIsCompleted_orig(i_State);
|
||||
}
|
||||
|
||||
static bool __fastcall aioIob2Bi2x_WriteFirmIsError(int32_t i_State) {
|
||||
if (aioIob2Bi2xWrfirm != nullptr) {
|
||||
return false;
|
||||
}
|
||||
return aioIob2Bi2x_WriteFirmIsError_orig(i_State);
|
||||
}
|
||||
|
||||
static void __fastcall aioNodeMgr_Destroy(AIO_NMGR_IOB *i_pNodeMgr) {
|
||||
if (i_pNodeMgr != aioNmgrIob) {
|
||||
return aioNodeMgr_Destroy_orig(i_pNodeMgr);
|
||||
}
|
||||
delete aioNmgrIob;
|
||||
aioNmgrIob = nullptr;
|
||||
}
|
||||
|
||||
static int32_t __fastcall aioNodeMgr_GetState(AIO_NMGR_IOB *i_pNodeMgr) {
|
||||
if (i_pNodeMgr == aioNmgrIob) {
|
||||
return 1;
|
||||
}
|
||||
return aioNodeMgr_GetState_orig(i_pNodeMgr);
|
||||
}
|
||||
|
||||
static bool __fastcall aioNodeMgr_IsReady(AIO_NMGR_IOB *i_pNodeMgr, int32_t i_State) {
|
||||
if (i_pNodeMgr == aioNmgrIob) {
|
||||
return true;
|
||||
}
|
||||
return aioNodeMgr_IsReady_orig(i_pNodeMgr, i_State);
|
||||
}
|
||||
|
||||
static bool __fastcall aioNodeMgr_IsError(AIO_NMGR_IOB *i_pNodeMgr, int32_t i_State) {
|
||||
if (i_pNodeMgr == aioNmgrIob) {
|
||||
return false;
|
||||
}
|
||||
return aioNodeMgr_IsError_orig(i_pNodeMgr, i_State);
|
||||
}
|
||||
|
||||
static void __fastcall aioNodeCtl_Destroy(AIO_IOB2_BI2X_AC1 *i_pNodeCtl) {
|
||||
if (i_pNodeCtl != aioIob2Bi2xAc1) {
|
||||
return aioNodeCtl_Destroy_orig(i_pNodeCtl);
|
||||
}
|
||||
delete aioIob2Bi2xAc1;
|
||||
aioIob2Bi2xAc1 = nullptr;
|
||||
}
|
||||
|
||||
static int32_t __fastcall aioNodeCtl_GetState(AIO_IOB2_BI2X_AC1 *i_pNodeCtl) {
|
||||
if (i_pNodeCtl == aioIob2Bi2xAc1) {
|
||||
return 1;
|
||||
}
|
||||
return aioNodeCtl_GetState_orig(i_pNodeCtl);
|
||||
}
|
||||
|
||||
static bool __fastcall aioNodeCtl_IsReady(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, int32_t i_State) {
|
||||
if (i_pNodeCtl == aioIob2Bi2xAc1) {
|
||||
return true;
|
||||
}
|
||||
return aioNodeCtl_IsReady_orig(i_pNodeCtl, i_State);
|
||||
}
|
||||
|
||||
static bool __fastcall aioNodeCtl_IsError(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, int32_t i_State) {
|
||||
if (i_pNodeCtl == aioIob2Bi2xAc1) {
|
||||
return false;
|
||||
}
|
||||
return aioNodeCtl_IsError_orig(i_pNodeCtl, i_State);
|
||||
}
|
||||
|
||||
void bi2x_hook_init() {
|
||||
// avoid double init
|
||||
static bool initialized = false;
|
||||
if (initialized) {
|
||||
return;
|
||||
} else {
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
// announce
|
||||
log_info("bi2x_hook", "init");
|
||||
|
||||
// libaio-iob2_video.dll
|
||||
const auto libaioIob2VideoDll = "libaio-iob2_video.dll";
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_Create",
|
||||
aioIob2Bi2xAC1_Create, &aioIob2Bi2xAC1_Create_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_GetDeviceStatus",
|
||||
aioIob2Bi2xAC1_GetDeviceStatus, &aioIob2Bi2xAC1_GetDeviceStatus_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_IoReset",
|
||||
aioIob2Bi2xAC1_IoReset, &aioIob2Bi2xAC1_IoReset_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_SetWatchDogTimer",
|
||||
aioIob2Bi2xAC1_SetWatchDogTimer, &aioIob2Bi2xAC1_SetWatchDogTimer_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_AddCounter",
|
||||
aioIob2Bi2xAC1_AddCounter, &aioIob2Bi2xAC1_AddCounter_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_SetAmpVolume",
|
||||
aioIob2Bi2xAC1_SetAmpVolume, &aioIob2Bi2xAC1_SetAmpVolume_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_SetOutputData",
|
||||
aioIob2Bi2xAC1_SetOutputData, &aioIob2Bi2xAC1_SetOutputData_orig);
|
||||
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xAC1_SetTapeLedDataPart",
|
||||
aioIob2Bi2xAC1_SetTapeLedDataPart, &aioIob2Bi2xAC1_SetTapeLedDataPart_orig);
|
||||
|
||||
// libaio-iob.dll
|
||||
const auto libaioIobDll = "libaio-iob.dll";
|
||||
detour::trampoline_try(libaioIobDll, "aioNMgrIob2_Create",
|
||||
aioNMgrIob2_Create, &aioNMgrIob2_Create_orig);
|
||||
detour::trampoline_try(libaioIobDll, "aioNMgrIob_GetCommStatus",
|
||||
aioNMgrIob_GetCommStatus, &aioNMgrIob_GetCommStatus_orig);
|
||||
detour::trampoline_try(libaioIobDll, "aioNMgrIob_BeginManage",
|
||||
aioNMgrIob_BeginManage, &aioNMgrIob_BeginManage_orig);
|
||||
detour::trampoline_try(libaioIobDll, "aioNCtlIob_GetNodeInfo",
|
||||
aioNCtlIob_GetNodeInfo, &aioNCtlIob_GetNodeInfo_orig);
|
||||
detour::trampoline_try(libaioIobDll, "aioIob2Bi2x_OpenSciUsbCdc",
|
||||
aioIob2Bi2x_OpenSciUsbCdc, &aioIob2Bi2x_OpenSciUsbCdc_orig);
|
||||
detour::trampoline_try(libaioIobDll, "aioIob2Bi2x_SciOpenB8PNS1",
|
||||
aioIob2Bi2x_SciOpenB8PNS1, &aioIob2Bi2x_SciOpenB8PNS1_orig);
|
||||
detour::trampoline_try(libaioIobDll, "aioIob2Bi2x_SetTapeLedDataGroup",
|
||||
aioIob2Bi2x_SetTapeLedDataGroup, &aioIob2Bi2x_SetTapeLedDataGroup_orig);
|
||||
detour::trampoline_try(libaioIobDll, "aioIob2Bi2x_SetTapeLedDataLimit",
|
||||
aioIob2Bi2x_SetTapeLedDataLimit, &aioIob2Bi2x_SetTapeLedDataLimit_orig);
|
||||
detour::trampoline_try(libaioIobDll, "aioIob2Bi2x_CreateWriteFirmContext",
|
||||
aioIob2Bi2x_CreateWriteFirmContext, &aioIob2Bi2x_CreateWriteFirmContext_orig);
|
||||
detour::trampoline_try(libaioIobDll, "aioIob2Bi2x_DestroyWriteFirmContext",
|
||||
aioIob2Bi2x_DestroyWriteFirmContext, &aioIob2Bi2x_DestroyWriteFirmContext_orig);
|
||||
detour::trampoline_try(libaioIobDll, "aioIob2Bi2x_WriteFirmGetState",
|
||||
aioIob2Bi2x_WriteFirmGetState, &aioIob2Bi2x_WriteFirmGetState_orig);
|
||||
detour::trampoline_try(libaioIobDll, "aioIob2Bi2x_WriteFirmIsCompleted",
|
||||
aioIob2Bi2x_WriteFirmIsCompleted, &aioIob2Bi2x_WriteFirmIsCompleted_orig);
|
||||
detour::trampoline_try(libaioIobDll, "aioIob2Bi2x_WriteFirmIsError",
|
||||
aioIob2Bi2x_WriteFirmIsError, &aioIob2Bi2x_WriteFirmIsError_orig);
|
||||
|
||||
// libaio.dll
|
||||
const auto libaioDll = "libaio.dll";
|
||||
detour::trampoline_try(libaioDll, "aioNodeMgr_Destroy",
|
||||
aioNodeMgr_Destroy, &aioNodeMgr_Destroy_orig);
|
||||
detour::trampoline_try(libaioDll, "aioNodeMgr_GetState",
|
||||
aioNodeMgr_GetState, &aioNodeMgr_GetState_orig);
|
||||
detour::trampoline_try(libaioDll, "aioNodeMgr_IsReady",
|
||||
aioNodeMgr_IsReady, &aioNodeMgr_IsReady_orig);
|
||||
detour::trampoline_try(libaioDll, "aioNodeMgr_IsError",
|
||||
aioNodeMgr_IsError, &aioNodeMgr_IsError_orig);
|
||||
detour::trampoline_try(libaioDll, "aioNodeCtl_Destroy",
|
||||
aioNodeCtl_Destroy, &aioNodeCtl_Destroy_orig);
|
||||
detour::trampoline_try(libaioDll, "aioNodeCtl_GetState",
|
||||
aioNodeCtl_GetState, &aioNodeCtl_GetState_orig);
|
||||
detour::trampoline_try(libaioDll, "aioNodeCtl_IsReady",
|
||||
aioNodeCtl_IsReady, &aioNodeCtl_IsReady_orig);
|
||||
detour::trampoline_try(libaioDll, "aioNodeCtl_IsError",
|
||||
aioNodeCtl_IsError, &aioNodeCtl_IsError_orig);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace games::qks {
|
||||
void bi2x_hook_init();
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#include "io.h"
|
||||
|
||||
std::vector<Button> &games::qks::get_buttons() {
|
||||
static std::vector<Button> buttons;
|
||||
|
||||
if (buttons.empty()) {
|
||||
buttons = GameAPI::Buttons::getButtons("QuizKnock STADIUM");
|
||||
|
||||
GameAPI::Buttons::sortButtons(
|
||||
&buttons,
|
||||
"Test",
|
||||
"Service",
|
||||
"Coin",
|
||||
"Q Button Press",
|
||||
"Q Button Sensor 1",
|
||||
"Q Button Sensor 2",
|
||||
"Q Button Sensor 3",
|
||||
"Headphones Detect",
|
||||
"Microphone Detect"
|
||||
);
|
||||
}
|
||||
|
||||
return buttons;
|
||||
}
|
||||
|
||||
std::string games::qks::get_buttons_help() {
|
||||
// keep to max 100 characters wide
|
||||
return "";
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "cfg/api.h"
|
||||
|
||||
namespace games::qks {
|
||||
namespace Buttons {
|
||||
enum {
|
||||
Test,
|
||||
Service,
|
||||
Coin,
|
||||
QButton,
|
||||
QButton1,
|
||||
QButton2,
|
||||
QButton3,
|
||||
JackDetect,
|
||||
MicDetect
|
||||
};
|
||||
}
|
||||
|
||||
// getters
|
||||
std::vector<Button> &get_buttons();
|
||||
std::string get_buttons_help();
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
#include "qks.h"
|
||||
|
||||
#include <format>
|
||||
#include "util/libutils.h"
|
||||
#include "util/fileutils.h"
|
||||
#include "util/utils.h"
|
||||
#include "util/unity_player.h"
|
||||
#include "util/detour.h"
|
||||
#include "acioemu/handle.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "bi2x_hook.h"
|
||||
|
||||
namespace games::qks {
|
||||
std::string QKS_INJECT_ARGS = "";
|
||||
|
||||
const std::wstring portName = L"\\\\.\\COM1";
|
||||
static acioemu::ACIOHandle *acioHandle = nullptr;
|
||||
static std::string commandLine;
|
||||
static bool isOpened = false;
|
||||
|
||||
static decltype(AddVectoredExceptionHandler) *AddVectoredExceptionHandler_orig = nullptr;
|
||||
static decltype(ShowCursor) *ShowCursor_orig = nullptr;
|
||||
static decltype(CreateFileA) *execexe_CreateFileA_orig = nullptr;
|
||||
static decltype(CloseHandle) *execexe_CloseHandle_orig = nullptr;
|
||||
static decltype(GetCommandLineA) *GetCommandLineA_orig = nullptr;
|
||||
|
||||
static HANDLE WINAPI execexe_CreateFileA_hook(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
|
||||
LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition,
|
||||
DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) {
|
||||
const auto lpFileNameW = s2ws(lpFileName);
|
||||
if (lpFileNameW == portName) {
|
||||
if (!isOpened) {
|
||||
isOpened = acioHandle->open(portName.c_str());
|
||||
} else {
|
||||
// NOTE: For some reason, QKS repeatedly opens and closes the COM port.
|
||||
// In the existing implementation of `icca.cpp`, the game side would specify an impossible
|
||||
// number of units and call `log_fatal()`, causing the game to crash, so the handle is reused.
|
||||
log_info("qks", "ignored handle open. ({})", ws2s(portName));
|
||||
}
|
||||
SetLastError(0);
|
||||
return (HANDLE)acioHandle;
|
||||
}
|
||||
return execexe_CreateFileA_orig(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes,
|
||||
dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
|
||||
}
|
||||
|
||||
static WINBOOL WINAPI execexe_CloseHandle_hook(HANDLE hObject) {
|
||||
if (hObject == acioHandle && isOpened) {
|
||||
log_info("qks", "ignored handle close. ({})", ws2s(portName));
|
||||
return TRUE;
|
||||
}
|
||||
return execexe_CloseHandle_orig(hObject);
|
||||
}
|
||||
|
||||
static int WINAPI ShowCursor_hook(BOOL bShow) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static PVOID WINAPI AddVectoredExceptionHandler_hook(ULONG First, PVECTORED_EXCEPTION_HANDLER Handler) {
|
||||
return INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
static LPSTR WINAPI GetCommandLineA_hook() {
|
||||
return (LPSTR)commandLine.c_str();
|
||||
}
|
||||
|
||||
void QKSGame::attach() {
|
||||
Game::attach();
|
||||
|
||||
// create required files
|
||||
fileutils::dir_create_recursive("dev/raw/log");
|
||||
fileutils::bin_write("dev/raw/log/output_log.txt", nullptr, 0);
|
||||
|
||||
// create optional files (suppress warning)
|
||||
fileutils::bin_write("dev/raw/error.txt", nullptr, 0);
|
||||
|
||||
// preload libraries
|
||||
libutils::load_library("execexe.dll");
|
||||
libutils::load_library("libaio.dll");
|
||||
libutils::load_library("libaio-iob.dll");
|
||||
libutils::load_library("libaio-iob2_video.dll");
|
||||
libutils::load_library("virtualsurroundnative.dll");
|
||||
|
||||
detour::trampoline_try("execexe.dll", MAKEINTRESOURCE(7),
|
||||
execexe_CloseHandle_hook,&execexe_CloseHandle_orig);
|
||||
detour::trampoline_try("execexe.dll", MAKEINTRESOURCE(9),
|
||||
execexe_CreateFileA_hook,&execexe_CreateFileA_orig);
|
||||
|
||||
// insert BI2X hooks
|
||||
bi2x_hook_init();
|
||||
|
||||
// add card reader
|
||||
acioHandle = new acioemu::ACIOHandle(portName.c_str());
|
||||
devicehook_init_trampoline();
|
||||
devicehook_add(acioHandle);
|
||||
}
|
||||
|
||||
void QKSGame::post_attach() {
|
||||
Game::post_attach();
|
||||
|
||||
detour::trampoline_try("kernel32.dll", "AddVectoredExceptionHandler",
|
||||
AddVectoredExceptionHandler_hook, &AddVectoredExceptionHandler_orig);
|
||||
detour::trampoline_try("kernel32.dll", "GetCommandLineA",
|
||||
GetCommandLineA_hook, &GetCommandLineA_orig);
|
||||
|
||||
if (GRAPHICS_SHOW_CURSOR) {
|
||||
detour::trampoline_try("user32.dll", "ShowCursor",
|
||||
ShowCursor_hook, &ShowCursor_orig);
|
||||
}
|
||||
|
||||
commandLine =
|
||||
std::format("{} {}{}",
|
||||
GetCommandLineA_orig(),
|
||||
QKS_INJECT_ARGS,
|
||||
unity_utils::get_unity_player_args());
|
||||
log_info("qks", "unity player args: ```{}```", commandLine);
|
||||
}
|
||||
|
||||
void QKSGame::detach() {
|
||||
Game::detach();
|
||||
|
||||
devicehook_dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "games/game.h"
|
||||
|
||||
namespace games::qks {
|
||||
extern std::string QKS_INJECT_ARGS;
|
||||
|
||||
class QKSGame : public games::Game {
|
||||
public:
|
||||
QKSGame() : Game("QuizKnock STADIUM") {}
|
||||
|
||||
virtual void attach() override;
|
||||
virtual void post_attach() override;
|
||||
virtual void detach() override;
|
||||
};
|
||||
}
|
||||
+1
-1
@@ -63,7 +63,7 @@ namespace games::qma {
|
||||
touch_attach_dx_hook();
|
||||
|
||||
// cursor
|
||||
if (!is_touch_available()) {
|
||||
if (!is_touch_available("QMATouchDevice::open")) {
|
||||
ShowCursor(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "games/game.h"
|
||||
|
||||
namespace games::rb {
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@ bool games::rb::ReflecBeatTouchDeviceHandle::open(LPCWSTR lpFileName) {
|
||||
}
|
||||
|
||||
// show cursor on window if mouse is used
|
||||
if (!is_touch_available()) {
|
||||
if (!is_touch_available("ReflecBeatTouchDeviceHandle::open")) {
|
||||
ShowCursor(TRUE);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@ public:
|
||||
touch_attach_dx_hook();
|
||||
|
||||
// cursor
|
||||
if (!is_touch_available()) {
|
||||
if (!is_touch_available("TwTouchDevice::open")) {
|
||||
ShowCursor(true);
|
||||
}
|
||||
}
|
||||
|
||||
+16
-23
@@ -10,6 +10,8 @@
|
||||
#include "launcher/options.h"
|
||||
#include "io.h"
|
||||
#include "games/sdvx/sdvx.h"
|
||||
#include "util/tapeled.h"
|
||||
#include "acioemu/icca.h"
|
||||
|
||||
namespace games::sdvx {
|
||||
constexpr bool BI2X_PASSTHROUGH = false;
|
||||
@@ -293,12 +295,9 @@ namespace games::sdvx {
|
||||
static struct TapeLedMapping {
|
||||
size_t data_size;
|
||||
int index_r, index_g, index_b;
|
||||
float avg_mult;
|
||||
|
||||
TapeLedMapping(size_t data_size, int index_r, int index_g, int index_b)
|
||||
: data_size(data_size), index_r(index_r), index_g(index_g), index_b(index_b) {
|
||||
avg_mult = 1.f / (data_size * 255);
|
||||
}
|
||||
: data_size(data_size), index_r(index_r), index_g(index_g), index_b(index_b) {}
|
||||
|
||||
} mapping[] = {
|
||||
{ 74, Lights::TITLE_AVG_R, Lights::TITLE_AVG_G, Lights::TITLE_AVG_B },
|
||||
@@ -314,32 +313,20 @@ namespace games::sdvx {
|
||||
};
|
||||
|
||||
// check index bounds
|
||||
if (index < std::size(mapping)) {
|
||||
if (tapeledutils::is_enabled() && index < std::size(mapping)) {
|
||||
auto &map = mapping[index];
|
||||
|
||||
// calculate average color
|
||||
size_t avg_ri = 0;
|
||||
size_t avg_gi = 0;
|
||||
size_t avg_bi = 0;
|
||||
for (size_t i = 0; i < map.data_size; i++) {
|
||||
auto color = &data[i * 3];
|
||||
avg_ri += color[0];
|
||||
avg_gi += color[1];
|
||||
avg_bi += color[2];
|
||||
}
|
||||
float avg_r = avg_ri * map.avg_mult;
|
||||
float avg_g = avg_gi * map.avg_mult;
|
||||
float avg_b = avg_bi * map.avg_mult;
|
||||
// pick a color to use
|
||||
const auto rgb = tapeledutils::pick_color_from_led_tape(data, map.data_size);
|
||||
|
||||
// set average color
|
||||
// program the lights into API
|
||||
auto &lights = get_lights();
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_r], avg_r);
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_g], avg_g);
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_b], avg_b);
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_r], rgb.r);
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_g], rgb.g);
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_b], rgb.b);
|
||||
}
|
||||
|
||||
if (This != custom_node) {
|
||||
|
||||
// send tape data to real device
|
||||
return AIO_IOB2_BI2X_UFC__SetTapeLedData_orig(This, index, data);
|
||||
}
|
||||
@@ -366,6 +353,12 @@ namespace games::sdvx {
|
||||
}
|
||||
log_info("bi2x_hook", "aioNMgrIob2_Create");
|
||||
BI2X_INITIALIZED = true;
|
||||
|
||||
// enable hack to make PIN pad work for KFC in BI2X mode
|
||||
// this explicit check in the I/O init path is necessary
|
||||
// (as opposed to just doing a check for "isValkyrieCabMode?")
|
||||
// because there are hex edits that allow you to use legacy (KFC/BIO2) IO while in Valk mode
|
||||
acioemu::ICCA_DEVICE_HACK = true;
|
||||
return &aioNmgrIob2;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,20 @@ std::vector<Button> &games::sdvx::get_buttons() {
|
||||
return buttons;
|
||||
}
|
||||
|
||||
std::string games::sdvx::get_buttons_help() {
|
||||
// keep to max 100 characters wide
|
||||
return
|
||||
" Start\n"
|
||||
"\n"
|
||||
" VOL-L VOL_R\n"
|
||||
"\n"
|
||||
" BT-A BT-B BT-C BT-D\n"
|
||||
" FX-L FX-R \n"
|
||||
"\n"
|
||||
"For controllers, use Analog tab for knobs."
|
||||
;
|
||||
}
|
||||
|
||||
std::vector<Analog> &games::sdvx::get_analogs() {
|
||||
static std::vector<Analog> analogs;
|
||||
|
||||
|
||||
@@ -110,6 +110,7 @@ namespace games::sdvx {
|
||||
|
||||
// getters
|
||||
std::vector<Button> &get_buttons();
|
||||
std::string get_buttons_help();
|
||||
std::vector<Analog> &get_analogs();
|
||||
std::vector<Light> &get_lights();
|
||||
}
|
||||
|
||||
+82
-19
@@ -8,8 +8,10 @@
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "hooks/devicehook.h"
|
||||
#include "hooks/libraryhook.h"
|
||||
#include "hooks/graphics/nvapi_hook.h"
|
||||
#include "hooks/powrprof.h"
|
||||
#include "hooks/sleephook.h"
|
||||
#include "hooks/winuser.h"
|
||||
#include "touch/touch.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
@@ -21,6 +23,7 @@
|
||||
#include "camera.h"
|
||||
#include "io.h"
|
||||
#include "acioemu/handle.h"
|
||||
#include "cfg/configurator.h"
|
||||
|
||||
static decltype(RegCloseKey) *RegCloseKey_orig = nullptr;
|
||||
static decltype(RegEnumKeyA) *RegEnumKeyA_orig = nullptr;
|
||||
@@ -40,6 +43,7 @@ namespace games::sdvx {
|
||||
bool NATIVETOUCH = false;
|
||||
uint8_t DIGITAL_KNOB_SENS = 16;
|
||||
SdvxOverlayPosition OVERLAY_POS = SDVX_OVERLAY_BOTTOM;
|
||||
bool ENABLE_COM_PORT_SCAN_HOOK = false;
|
||||
|
||||
std::optional<std::string> SOUND_OUTPUT_DEVICE = std::nullopt;
|
||||
std::optional<std::string> ASIO_DRIVER = std::nullopt;
|
||||
@@ -66,6 +70,7 @@ namespace games::sdvx {
|
||||
static LONG WINAPI RegOpenKeyExA_hook(HKEY hKey, LPCSTR lpSubKey, DWORD ulOptions, REGSAM samDesired,
|
||||
PHKEY phkResult)
|
||||
{
|
||||
// ASIO hook
|
||||
if (lpSubKey != nullptr && phkResult != nullptr) {
|
||||
if (hKey == PARENT_ASIO_REG_HANDLE &&
|
||||
ASIO_DRIVER.has_value() &&
|
||||
@@ -74,12 +79,36 @@ namespace games::sdvx {
|
||||
*phkResult = DEVICE_ASIO_REG_HANDLE;
|
||||
|
||||
log_info("sdvx::asio", "replacing '{}' with '{}'", lpSubKey, ASIO_DRIVER.value());
|
||||
|
||||
return RegOpenKeyExA_orig(real_asio_reg_handle, ASIO_DRIVER.value().c_str(), ulOptions, samDesired,
|
||||
const auto result = RegOpenKeyExA_orig(
|
||||
real_asio_reg_handle,
|
||||
ASIO_DRIVER.value().c_str(),
|
||||
ulOptions,
|
||||
samDesired,
|
||||
&real_asio_device_reg_handle);
|
||||
|
||||
if (result != ERROR_SUCCESS) {
|
||||
log_warning(
|
||||
"sdvx::asio",
|
||||
"failed to open registry subkey '{}', error=0x{:x}",
|
||||
ASIO_DRIVER.value().c_str(), result);
|
||||
log_warning(
|
||||
"sdvx::asio",
|
||||
"due to improper ASIO setting, game will likely fall back to WASAPI; double check -sdvxasio and the registry",
|
||||
ASIO_DRIVER.value().c_str(), result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// COM hook
|
||||
if (ENABLE_COM_PORT_SCAN_HOOK &&
|
||||
lpSubKey != nullptr && phkResult != nullptr &&
|
||||
_stricmp(lpSubKey, "HARDWARE\\DEVICEMAP\\SERIALCOMM") == 0) {
|
||||
log_info("sdvx::io", "failing HKLM\\HARDWARE\\DEVICEMAP\\SERIALCOMM to force COM1 ICCA");
|
||||
return 2; //ERROR_FILE_NOT_FOUND
|
||||
}
|
||||
|
||||
return RegOpenKeyExA_orig(hKey, lpSubKey, ulOptions, samDesired, phkResult);
|
||||
}
|
||||
|
||||
@@ -111,9 +140,9 @@ namespace games::sdvx {
|
||||
log_info("sdvx::asio", "RegQueryValueExA({}, \"{}\")", fmt::ptr((void *) hKey), lpValueName);
|
||||
|
||||
if (_stricmp(lpValueName, "Description") == 0) {
|
||||
// sdvx does a comparison against "XONAR SOUND CARD(64)"
|
||||
// otherwise you end up with this error:
|
||||
// M:BMSoundLib: ASIODriver: No such driver: XONAR SOUND CARD(64)
|
||||
// sdvx does a comparison against hardcoded string "XONAR SOUND CARD(64)" (same as iidx31)
|
||||
// so what's in the registry must be overridden with "XONAR SOUND CARD(64)"
|
||||
// otherwise you end up with this error: M:BMSoundLib: ASIODriver: No such driver
|
||||
memcpy(lpData, ORIGINAL_ASIO_DEVICE_NAME, strlen(ORIGINAL_ASIO_DEVICE_NAME) + 1);
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
@@ -144,7 +173,7 @@ namespace games::sdvx {
|
||||
}
|
||||
|
||||
#ifdef SPICE64
|
||||
static bool sdvx5_spam_remover(void *user, const std::string &data, logger::Style style, std::string &out) {
|
||||
static bool sdvx64_spam_remover(void *user, const std::string &data, logger::Style style, std::string &out) {
|
||||
if (data.empty() || data[0] != '[') {
|
||||
return false;
|
||||
}
|
||||
@@ -176,6 +205,11 @@ namespace games::sdvx {
|
||||
out = "";
|
||||
return true;
|
||||
}
|
||||
// M:AppConfig: [env/APPDATA]=C:\Users\username\AppData\Roaming
|
||||
if (data.find("M:AppConfig: [env/") != std::string::npos) {
|
||||
out = "";
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -223,6 +257,25 @@ namespace games::sdvx {
|
||||
}
|
||||
#endif
|
||||
|
||||
void SDVXGame::pre_attach() {
|
||||
// for whatever reason, sdvx latches onto cards for much longer than other games
|
||||
// needed because the game waits forever on the game over screen until a card is not detected
|
||||
AUTO_INSERT_CARD_COOLDOWN = 15.f;
|
||||
// check bad model name
|
||||
if (!cfg::CONFIGURATOR_STANDALONE && avs::game::is_model("UFC")) {
|
||||
log_fatal(
|
||||
"sdvx",
|
||||
"BAD MODEL NAME ERROR\n\n\n"
|
||||
"!!! model name set to UFC, this is WRONG and will break your game !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! If you are trying to boot Valkyrie Model, !!!\n"
|
||||
"!!! change <spec> from F to G. !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! model name set to UFC, this is WRONG and will break your game !!!\n\n\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void SDVXGame::attach() {
|
||||
Game::attach();
|
||||
|
||||
@@ -260,14 +313,20 @@ namespace games::sdvx {
|
||||
|
||||
// enable 9on12 for AMD
|
||||
if (!libutils::try_library("nvapi64.dll")) {
|
||||
log_info("sdvx", "nvapi64.dll not found, force enabling 9on12");
|
||||
GRAPHICS_9_ON_12 = true;
|
||||
log_info(
|
||||
"sdvx",
|
||||
"nvapi64.dll not found; for non-NVIDIA GPUs, requesting 9on12 to be enabled");
|
||||
GRAPHICS_9_ON_12_REQUESTED_BY_GAME = true;
|
||||
} else {
|
||||
// don't let nvapi mess with display settings
|
||||
nvapi_hook::initialize(avs::game::DLL_INSTANCE);
|
||||
}
|
||||
|
||||
// check for Valkyrie cabinet mode
|
||||
if (isValkyrieCabinetMode) {
|
||||
// hook touch window
|
||||
if (!NATIVETOUCH) {
|
||||
// in windowed mode, game can accept mouse input on the second screen
|
||||
if (!NATIVETOUCH && !GRAPHICS_WINDOWED) {
|
||||
wintouchemu::FORCE = true;
|
||||
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
||||
wintouchemu::hook_title_ends(
|
||||
@@ -282,10 +341,13 @@ namespace games::sdvx {
|
||||
// add card readers
|
||||
devicehook_init(aio);
|
||||
devicehook_add(new acioemu::ACIOHandle(L"COM1"));
|
||||
} else {
|
||||
// don't let nvapi mess with display settings (force failure)
|
||||
libraryhook_hook_proc("nvapi_QueryInterface", static_cast<void *>(nullptr));
|
||||
libraryhook_enable(avs::game::DLL_INSTANCE);
|
||||
|
||||
// this is needed because on some newer versions of SDVX6, soundvoltex.dll will open
|
||||
// HKLM\HARDWARE\DEVICEMAP\SERIALCOMM, go through some of the keys, and depending on
|
||||
// what is present, pick a port other than COM1 (seemingly the highest port
|
||||
// available). We want the game to pick COM1 still, so a workaround is needed to
|
||||
// fool the game.
|
||||
ENABLE_COM_PORT_SCAN_HOOK = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -304,6 +366,7 @@ namespace games::sdvx {
|
||||
|
||||
#ifdef SPICE64
|
||||
powrprof_hook_init(avs::game::DLL_INSTANCE);
|
||||
winuser_hook_init(avs::game::DLL_INSTANCE);
|
||||
|
||||
// hook camera
|
||||
if (!DISABLECAMS) {
|
||||
@@ -316,11 +379,11 @@ namespace games::sdvx {
|
||||
"418D480484C074218D51FD",
|
||||
"????????????9090??????",
|
||||
0, 0)) {
|
||||
log_warning("sdvx", "failed to insert camera error fix");
|
||||
log_info("sdvx", "did not find matching signature for camera error patch");
|
||||
}
|
||||
|
||||
// remove log spam
|
||||
logger::hook_add(sdvx5_spam_remover, nullptr);
|
||||
logger::hook_add(sdvx64_spam_remover, nullptr);
|
||||
|
||||
#endif
|
||||
}
|
||||
@@ -366,7 +429,7 @@ namespace games::sdvx {
|
||||
// calculate target RVA
|
||||
auto volume_set_rva = libutils::offset2rva(MODULE_PATH / avs::game::DLL_NAME, rva);
|
||||
if (volume_set_rva == -1) {
|
||||
log_warning("sdvx", "failed to insert set volume hook (convert rva {})", rva);
|
||||
log_info("sdvx", "could not apply volume hook patch (convert rva {})", rva);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -374,7 +437,7 @@ namespace games::sdvx {
|
||||
auto *volume_set_ptr = reinterpret_cast<uint16_t *>(
|
||||
reinterpret_cast<intptr_t>(avs::game::DLL_INSTANCE) + volume_set_rva);
|
||||
if (volume_set_ptr[0] != 0x8B48) {
|
||||
log_warning("sdvx", "failed to insert set volume hook (invalid target)");
|
||||
log_info("sdvx", "could not apply volume hook patch (invalid target)", rva);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -384,7 +447,7 @@ namespace games::sdvx {
|
||||
volume_set_hook,
|
||||
&volume_set_orig))
|
||||
{
|
||||
log_warning("sdvx", "failed to insert set volume hook (insert trampoline)");
|
||||
log_info("sdvx", "could not apply volume hook patch (insert trampoline)", rva);
|
||||
}
|
||||
|
||||
// success
|
||||
@@ -395,7 +458,7 @@ namespace games::sdvx {
|
||||
|
||||
// check if version not found
|
||||
if (!volume_hook_found) {
|
||||
log_warning("sdvx", "volume hook unavailable for this game version");
|
||||
log_info("sdvx", "volume hook unavailable for this game version");
|
||||
|
||||
// set volumes to sdvx 4 defaults
|
||||
auto &lights = games::sdvx::get_lights();
|
||||
|
||||
+5
-1
@@ -2,6 +2,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <cstdint>
|
||||
|
||||
#include "games/game.h"
|
||||
|
||||
@@ -10,7 +11,9 @@ namespace games::sdvx {
|
||||
enum SdvxOverlayPosition {
|
||||
SDVX_OVERLAY_TOP,
|
||||
SDVX_OVERLAY_MIDDLE,
|
||||
SDVX_OVERLAY_BOTTOM
|
||||
SDVX_OVERLAY_BOTTOM,
|
||||
SDVX_OVERLAY_BOTTOM_LEFT,
|
||||
SDVX_OVERLAY_BOTTOM_RIGHT
|
||||
};
|
||||
|
||||
// settings
|
||||
@@ -24,6 +27,7 @@ namespace games::sdvx {
|
||||
class SDVXGame : public games::Game {
|
||||
public:
|
||||
SDVXGame();
|
||||
virtual void pre_attach() override;
|
||||
virtual void attach() override;
|
||||
virtual void post_attach() override;
|
||||
virtual void detach() override;
|
||||
|
||||
Reference in New Issue
Block a user