Initial re-upload of spice2x-24-08-24

This commit is contained in:
2024-08-28 11:10:34 -04:00
commit caa9e02285
1181 changed files with 380065 additions and 0 deletions

44
games/bbc/bbc.cpp Normal file
View File

@@ -0,0 +1,44 @@
#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"
#include "util/sigscan.h"
namespace games::bbc {
BBCGame::BBCGame() : Game("Bishi Bashi Channel") {
}
void BBCGame::attach() {
Game::attach();
devicehook_init();
devicehook_add(new games::shared::LCDHandle());
// window patch
if (GRAPHICS_WINDOWED) {
unsigned char pattern[] = {
0x48, 0x8D, 0x54, 0x24, 0x40, 0x41, 0x8D, 0x4E, 0x20, 0x41, 0x0F, 0xB6, 0xE8
};
unsigned char replace[] = {
0x48, 0x8D, 0x54, 0x24, 0x40, 0x41, 0x8D, 0x4E, 0x20, 0x31, 0xED, 0x90, 0x90
};
std::string mask = "XXXXXXXXXXXXX";
if (!replace_pattern(
avs::game::DLL_INSTANCE,
pattern,
mask.c_str(),
0,
0,
replace,
mask.c_str()))
log_warning("bbc", "windowed mode failed");
}
}
}

13
games/bbc/bbc.h Normal file
View File

@@ -0,0 +1,13 @@
#pragma once
#include "games/game.h"
namespace games::bbc {
class BBCGame : public games::Game {
public:
BBCGame();
virtual void attach() override;
};
}

105
games/bbc/io.cpp Normal file
View File

@@ -0,0 +1,105 @@
#include "io.h"
std::vector<Button> &games::bbc::get_buttons() {
static std::vector<Button> buttons;
if (buttons.empty()) {
buttons = GameAPI::Buttons::getButtons("Bishi Bashi Channel");
GameAPI::Buttons::sortButtons(
&buttons,
"Service",
"Test",
"P1 R",
"P1 G",
"P1 B",
"P1 Disk-",
"P1 Disk+",
"P1 Disk -/+ Slowdown",
"P2 R",
"P2 G",
"P2 B",
"P2 Disk-",
"P2 Disk+",
"P2 Disk -/+ Slowdown",
"P3 R",
"P3 G",
"P3 B",
"P3 Disk-",
"P3 Disk+",
"P3 Disk -/+ Slowdown",
"P4 R",
"P4 G",
"P4 B",
"P4 Disk-",
"P4 Disk+",
"P4 Disk -/+ Slowdown"
);
}
return buttons;
}
std::vector<Analog> &games::bbc::get_analogs() {
static std::vector<Analog> analogs;
if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Bishi Bashi Channel");
GameAPI::Analogs::sortAnalogs(
&analogs,
"P1 Disk",
"P2 Disk",
"P3 Disk",
"P4 Disk"
);
}
return analogs;
}
std::vector<Light> &games::bbc::get_lights() {
static std::vector<Light> lights;
if (lights.empty()) {
lights = GameAPI::Lights::getLights("Bishi Bashi Channel");
GameAPI::Lights::sortLights(
&lights,
"P1 R",
"P1 B",
"P1 Disc R",
"P1 Disc G",
"P1 Disc B",
"P2 R",
"P2 B",
"P2 Disc R",
"P2 Disc G",
"P2 Disc B",
"P3 R",
"P3 B",
"P3 Disc R",
"P3 Disc G",
"P3 Disc B",
"P4 R",
"P4 B",
"P4 Disc R",
"P4 Disc G",
"P4 Disc B",
"IC Card R",
"IC Card G",
"IC Card B",
"Under LED1 R",
"Under LED1 G",
"Under LED1 B",
"Under LED2 R",
"Under LED2 G",
"Under LED2 B",
"Under LED3 R",
"Under LED3 G",
"Under LED3 B"
);
}
return lights;
}

92
games/bbc/io.h Normal file
View File

@@ -0,0 +1,92 @@
#pragma once
#include <vector>
#include "cfg/api.h"
namespace games::bbc {
// all buttons in correct order
namespace Buttons {
enum {
Service,
Test,
P1_R,
P1_G,
P1_B,
P1_DiskMinus,
P1_DiskPlus,
P1_DiskSlowdown,
P2_R,
P2_G,
P2_B,
P2_DiskMinus,
P2_DiskPlus,
P2_DiskSlowdown,
P3_R,
P3_G,
P3_B,
P3_DiskMinus,
P3_DiskPlus,
P3_DiskSlowdown,
P4_R,
P4_G,
P4_B,
P4_DiskMinus,
P4_DiskPlus,
P4_DiskSlowdown,
};
}
// all analogs in correct order
namespace Analogs {
enum {
P1_Disk,
P2_Disk,
P3_Disk,
P4_Disk,
};
}
// all lights in correct order
namespace Lights {
enum {
P1_R,
P1_B,
P1_DISC_R,
P1_DISC_G,
P1_DISC_B,
P2_R,
P2_B,
P2_DISC_R,
P2_DISC_G,
P2_DISC_B,
P3_R,
P3_B,
P3_DISC_R,
P3_DISC_G,
P3_DISC_B,
P4_R,
P4_B,
P4_DISC_R,
P4_DISC_G,
P4_DISC_B,
IC_CARD_R,
IC_CARD_G,
IC_CARD_B,
UNDER_LED1_R,
UNDER_LED1_G,
UNDER_LED1_B,
UNDER_LED2_R,
UNDER_LED2_G,
UNDER_LED2_B,
UNDER_LED3_R,
UNDER_LED3_G,
UNDER_LED3_B,
};
}
// getters
std::vector<Button> &get_buttons();
std::vector<Analog> &get_analogs();
std::vector<Light> &get_lights();
}

35
games/bc/bc.cpp Normal file
View File

@@ -0,0 +1,35 @@
#include "bc.h"
#include "hooks/setupapihook.h"
#include "util/libutils.h"
#include "acio2emu/handle.h"
#include "acioemu/handle.h"
#include "node.h"
namespace games::bc {
void BCGame::attach() {
Game::attach();
setupapihook_init(libutils::load_library("libaio.dll"));
SETUPAPI_SETTINGS settings {};
const char hwid[] = "USB\\VID_1CCF&PID_8050\\0000";
memcpy(settings.property_hardwareid, hwid, sizeof(hwid));
const char comport[] = "COM3";
memcpy(settings.interface_detail, comport, sizeof(comport));
setupapihook_add(settings);
auto iob = new acio2emu::IOBHandle(L"COM3");
iob->register_node(std::make_unique<TBSNode>());
devicehook_init();
devicehook_add(iob);
devicehook_add(new acioemu::ACIOHandle(L"COM1"));
}
void BCGame::detach() {
Game::detach();
devicehook_dispose();
}
}

13
games/bc/bc.h Normal file
View File

@@ -0,0 +1,13 @@
#pragma once
#include "games/game.h"
namespace games::bc {
class BCGame : public games::Game {
public:
BCGame() : Game("Busou Shinki: Armored Princess Battle Conductor") {}
virtual void attach() override;
virtual void detach() override;
};
}

44
games/bc/io.cpp Normal file
View File

@@ -0,0 +1,44 @@
#include "io.h"
std::vector<Button> &games::bc::get_buttons() {
static std::vector<Button> buttons;
if (buttons.empty()) {
buttons = GameAPI::Buttons::getButtons("Busou Shinki: Armored Princess Battle Conductor");
GameAPI::Buttons::sortButtons(
&buttons,
"Service",
"Test",
"Up",
"Down",
"Left",
"Right",
"Joystick Button",
"Trigger 1",
"Trigger 2",
"Button 1",
"Button 2",
"Button 3",
"Button 4"
);
}
return buttons;
}
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;
}

35
games/bc/io.h Normal file
View File

@@ -0,0 +1,35 @@
#pragma once
#include <vector>
#include "cfg/api.h"
namespace games::bc {
namespace Buttons {
enum {
Service,
Test,
Up,
Down,
Left,
Right,
JoystickButton,
Trigger1,
Trigger2,
Button1,
Button2,
Button3,
Button4,
};
}
namespace Analogs {
enum {
StickX,
StickY,
};
}
std::vector<Button> &get_buttons();
std::vector<Analog> &get_analogs();
}

111
games/bc/node.h Normal file
View File

@@ -0,0 +1,111 @@
#pragma once
#include <cstdint>
#include "io.h"
#include "acio2emu/firmware/bi2x.h"
#include "misc/eamuse.h"
#include "util/logging.h"
namespace games::bc {
class TBSNode : public acio2emu::firmware::BI2XNode {
private:
uint8_t coins_ = 0;
public:
void read_firmware_version(std::vector<uint8_t> &buffer) {
static constexpr uint8_t ver[] = {
0x0D, 0x06, 0x00, 0x01,
0x00, 0x01, 0x02, 0x08,
0x42, 0x49, 0x32, 0x58,
0x01, 0x94, 0xF1, 0x8E,
0x00, 0x00, 0x01, 0x0B,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0xA8, 0x24, 0x8E, 0xE2,
};
buffer.insert(buffer.end(), ver, &ver[sizeof(ver)]);
}
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);
buffer.push_back(0);
buffer.push_back(0);
buffer.push_back(coins_);
uint8_t b = 0;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test])) {
b |= 1;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service])) {
b |= (1 << 2);
}
buffer.push_back(b);
buffer.push_back(0);
int16_t joy = INT16_MAX;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Left])) {
joy += INT16_MAX;
}
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));
joy = INT16_MAX;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Down])) {
joy += INT16_MAX;
}
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));
b = 0;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::JoystickButton])) {
b |= 1;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Trigger1])) {
b |= (1 << 1);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Trigger2])) {
b |= (1 << 2);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button1])) {
b |= (1 << 3);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button2])) {
b |= (1 << 4);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button3])) {
b |= (1 << 5);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button4])) {
b |= (1 << 6);
}
buffer.push_back(b);
return true;
}
int write_output(std::span<const uint8_t> buffer) {
return 8;
}
};
}

53
games/bs/bs.cpp Normal file
View File

@@ -0,0 +1,53 @@
#include "bs.h"
#include "avs/game.h"
#include "misc/wintouchemu.h"
#include "launcher/launcher.h"
#include "rawinput/rawinput.h"
#include "util/detour.h"
#include "util/logging.h"
namespace games::bs {
BSGame::BSGame() : Game("Beatstream") {
}
inline bool touchscreen_has_bug() {
// get devices
auto devices = RI_MGR->devices_get();
for (auto &device : devices) {
// filter by HID
if (device.type == rawinput::HID) {
auto &hid = device.hidInfo;
auto &attributes = hid->attributes;
// P2314T
// it apparently cannot do holds using Beatstream's wintouch code
if (attributes.VendorID == 0x2149 && attributes.ProductID == 0x2316)
return true;
// P2418HT
// it apparently cannot do holds using Beatstream's wintouch code
if (attributes.VendorID == 0x1FD2 && attributes.ProductID == 0x6103)
return true;
}
}
// looks all clean
return false;
}
void BSGame::attach() {
Game::attach();
// for bugged touch screens
if (touchscreen_has_bug()) {
log_info("bs", "detected bugged touchscreen, forcing wintouchemu");
wintouchemu::FORCE = true;
}
// for mouse support
wintouchemu::hook("BeatStream", avs::game::DLL_INSTANCE);
}
}

12
games/bs/bs.h Normal file
View File

@@ -0,0 +1,12 @@
#pragma once
#include "games/game.h"
namespace games::bs {
class BSGame : public games::Game {
public:
BSGame();
virtual void attach() override;
};
}

41
games/bs/io.cpp Normal file
View File

@@ -0,0 +1,41 @@
#include "io.h"
std::vector<Button> &games::bs::get_buttons() {
static std::vector<Button> buttons;
if (buttons.empty()) {
buttons = GameAPI::Buttons::getButtons("Beatstream");
GameAPI::Buttons::sortButtons(
&buttons,
"Service",
"Test",
"Coin Mech"
);
}
return buttons;
}
std::vector<Light> &games::bs::get_lights() {
static std::vector<Light> lights;
if (lights.empty()) {
lights = GameAPI::Lights::getLights("Beatstream");
GameAPI::Lights::sortLights(
&lights,
"Bottom R",
"Bottom G",
"Bottom B",
"Left R",
"Left G",
"Left B",
"Right R",
"Right G",
"Right B"
);
}
return lights;
}

35
games/bs/io.h Normal file
View File

@@ -0,0 +1,35 @@
#pragma once
#include <vector>
#include "cfg/api.h"
namespace games::bs {
// all buttons in correct order
namespace Buttons {
enum {
Service,
Test,
CoinMech,
};
}
// all lights in correct order
namespace Lights {
enum {
BottomR,
BottomG,
BottomB,
LeftR,
LeftG,
LeftB,
RightR,
RightG,
RightB,
};
}
// getters
std::vector<Button> &get_buttons();
std::vector<Light> &get_lights();
}

624
games/ccj/bi2x_hook.cpp Normal file
View File

@@ -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);
}
}

5
games/ccj/bi2x_hook.h Normal file
View File

@@ -0,0 +1,5 @@
#pragma once
namespace games::ccj {
void bi2x_hook_init();
}

101
games/ccj/ccj.cpp Normal file
View File

@@ -0,0 +1,101 @@
#include "ccj.h"
#include <format>
#include "util/libutils.h"
#include "util/fileutils.h"
#include "util/utils.h"
#include "util/detour.h"
#include "acioemu/handle.h"
#include "misc/wintouchemu.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);
detour::trampoline_try("user32.dll", "ShowCursor",
(void*)ShowCursor_hook, (void**)&ShowCursor_orig);
commandLine = std::format("{} {}", GetCommandLineA_orig(), CCJ_INJECT_ARGS);
trackball_thread_start();
}
void CCJGame::detach() {
Game::detach();
devicehook_dispose();
trackball_thread_stop();
}
}

16
games/ccj/ccj.h Normal file
View File

@@ -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;
};
}

73
games/ccj/io.cpp Normal file
View File

@@ -0,0 +1,73 @@
#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::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;
}

58
games/ccj/io.h Normal file
View File

@@ -0,0 +1,58 @@
#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::vector<Analog> &get_analogs();
std::vector<Light> &get_lights();
}

267
games/ccj/trackball.cpp Normal file
View File

@@ -0,0 +1,267 @@
#include "trackball.h"
#include <chrono>
#include <thread>
#include "util/detour.h"
#include "util/logging.h"
#include "rawinput/rawinput.h"
#include "games/io.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 = GetAsyncKeyState(VK_RBUTTON) & 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;
}
}

12
games/ccj/trackball.h Normal file
View File

@@ -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();
}

171
games/ddr/ddr.cpp Normal file
View File

@@ -0,0 +1,171 @@
#include "ddr.h"
#include "acioemu/handle.h"
#include "avs/game.h"
#include "hooks/devicehook.h"
#include "hooks/setupapihook.h"
#include "hooks/sleephook.h"
#include "hooks/input/dinput8/hook.h"
#include "util/utils.h"
#include "util/libutils.h"
#include "util/fileutils.h"
#include "util/detour.h"
#include "cfg/configurator.h"
#include "io.h"
#include "p3io/foot.h"
#include "p3io/p3io.h"
#include "p3io/sate.h"
#include "p3io/usbmem.h"
#include "p4io/p4io.h"
using namespace acioemu;
namespace games::ddr {
// settings
bool SDMODE = false;
static decltype(SendMessage) *SendMessage_real = nullptr;
static SHORT WINAPI GetAsyncKeyState_hook(int vKey) {
// disable debug keys
return 0;
}
static LRESULT WINAPI SendMessage_hook(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
// ignore broadcasts
if (hWnd == HWND_BROADCAST) {
return 1;
}
// fallback
return SendMessage_real(hWnd, Msg, wParam, lParam);
}
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();
// dinput hook on this dll since the game dll doesn't load it
auto game_mdx = libutils::try_library(MODULE_PATH / "gamemdx.dll");
hooks::input::dinput8::init(game_mdx);
// init device hook
devicehook_init();
// 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());
devicehook_add(new DDRUSBMEMHandle());
devicehook_add(new DDRP3IOHandle());
}
// has nothing to do with P3IO, but is enough to trick the game into SD/HD mode
const char *settings_property = ddr::SDMODE ? "Generic Television" : "Generic Monitor";
const char settings_detail[] = R"(\\.\P3IO)";
// settings 1
SETUPAPI_SETTINGS settings1 {};
settings1.class_guid[0] = 0x1FA4A480;
settings1.class_guid[1] = 0x40C7AC60;
settings1.class_guid[2] = 0x7952ACA7;
settings1.class_guid[3] = 0x5A57340F;
memcpy(settings1.property_devicedesc, settings_property, strlen(settings_property) + 1);
memcpy(settings1.interface_detail, settings_detail, sizeof(settings_detail));
// settings 2
SETUPAPI_SETTINGS settings2 {};
settings2.class_guid[0] = 0x4D36E96E;
settings2.class_guid[1] = 0x11CEE325;
settings2.class_guid[2] = 0x8C1BF;
settings2.class_guid[3] = 0x1803E12B;
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);
// DDR ACE actually uses another DLL for things
if (game_mdx != nullptr) {
setupapihook_init(game_mdx);
}
// add settings
setupapihook_add(settings1);
setupapihook_add(settings2);
setupapihook_add(settingsp4io);
// misc hooks
detour::iat_try("GetAsyncKeyState", GetAsyncKeyState_hook, avs::game::DLL_INSTANCE);
detour::iat_try("GetKeyState", GetAsyncKeyState_hook, avs::game::DLL_INSTANCE);
SendMessage_real = detour::iat_try("SendMessageW", SendMessage_hook, avs::game::DLL_INSTANCE);
detour::iat_try("SendMessageA", SendMessage_hook, avs::game::DLL_INSTANCE);
}
void DDRGame::detach() {
Game::detach();
// dispose device hook
devicehook_dispose();
}
}

17
games/ddr/ddr.h Normal file
View File

@@ -0,0 +1,17 @@
#pragma once
#include "games/game.h"
namespace games::ddr {
// settings
extern bool SDMODE;
class DDRGame : public games::Game {
public:
DDRGame();
virtual void pre_attach() override;
virtual void attach() override;
virtual void detach() override;
};
}

178
games/ddr/io.cpp Normal file
View File

@@ -0,0 +1,178 @@
#include "io.h"
std::vector<Button> &games::ddr::get_buttons() {
static std::vector<Button> analogs;
if (analogs.empty()) {
analogs = GameAPI::Buttons::getButtons("Dance Dance Revolution");
GameAPI::Buttons::sortButtons(
&analogs,
"Service",
"Test",
"Coin Mech",
"P1 Start",
"P1 Panel Up",
"P1 Panel Down",
"P1 Panel Left",
"P1 Panel Right",
"P1 Menu Up",
"P1 Menu Down",
"P1 Menu Left",
"P1 Menu Right",
"P2 Start",
"P2 Panel Up",
"P2 Panel Down",
"P2 Panel Left",
"P2 Panel Right",
"P2 Menu Up",
"P2 Menu Down",
"P2 Menu Left",
"P2 Menu Right"
);
}
return analogs;
}
std::vector<Light> &games::ddr::get_lights() {
static std::vector<Light> lights;
if (lights.empty()) {
lights = GameAPI::Lights::getLights("Dance Dance Revolution");
GameAPI::Lights::sortLights(
&lights,
"P1 Foot Left",
"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 Menu Left-Right",
"HD P1 Menu Up-Down",
"HD P2 Start",
"HD P2 Menu Left-Right",
"HD P2 Menu 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 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"
);
}
return lights;
}

181
games/ddr/io.h Normal file
View File

@@ -0,0 +1,181 @@
#pragma once
#include <vector>
#include "cfg/api.h"
namespace games::ddr {
// all buttons in correct order
namespace Buttons {
enum {
SERVICE,
TEST,
COIN_MECH,
P1_START,
P1_PANEL_UP,
P1_PANEL_DOWN,
P1_PANEL_LEFT,
P1_PANEL_RIGHT,
P1_MENU_UP,
P1_MENU_DOWN,
P1_MENU_LEFT,
P1_MENU_RIGHT,
P2_START,
P2_PANEL_UP,
P2_PANEL_DOWN,
P2_PANEL_LEFT,
P2_PANEL_RIGHT,
P2_MENU_UP,
P2_MENU_DOWN,
P2_MENU_LEFT,
P2_MENU_RIGHT,
};
}
// all lights in correct order
namespace Lights {
enum {
P1_FOOT_LEFT,
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,
};
}
// getters
std::vector<Button> &get_buttons();
std::vector<Light> &get_lights();
}

92
games/ddr/p3io/foot.cpp Normal file
View File

@@ -0,0 +1,92 @@
#include "foot.h"
#include "rawinput/rawinput.h"
#include "util/logging.h"
#include "../ddr.h"
#include "../io.h"
bool games::ddr::DDRFOOTHandle::open(LPCWSTR lpFileName) {
if (wcscmp(lpFileName, L"COM1") != 0) {
return false;
}
log_info("ddr", "Opened COM1 (FOOT)");
return true;
}
int games::ddr::DDRFOOTHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) {
// check data trigger and buffer size
if (data_trigger && nNumberOfBytesToRead >= 1) {
data_trigger = false;
memset(lpBuffer, 0x11, 1);
return 1;
}
// no data
return 0;
}
int games::ddr::DDRFOOTHandle::write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) {
// check buffer size for lights
if (nNumberOfBytesToWrite == 4) {
// mappings
static const size_t mapping_bits[] = {
4, 6, 3, 5, // P1 L, U, R, D,
12, 14, 11, 13, // P2 L, U, R, D
22 // NEON
};
static const size_t mapping[] = {
Lights::P1_FOOT_LEFT,
Lights::P1_FOOT_UP,
Lights::P1_FOOT_RIGHT,
Lights::P1_FOOT_DOWN,
Lights::P2_FOOT_LEFT,
Lights::P2_FOOT_UP,
Lights::P2_FOOT_RIGHT,
Lights::P2_FOOT_DOWN,
Lights::NEON
};
// get light bits
uint32_t light_bits = *((uint32_t*) lpBuffer);
// get lights
auto &lights = get_lights();
// bit scan
for (size_t i = 0; i < 8; i++) {
float value = (light_bits & (1 << mapping_bits[i])) ? 1.f : 0.f;
GameAPI::Lights::writeLight(RI_MGR, lights.at(mapping[i]), value);
}
// only set the neon if in SD mode
// p3io sets it for HD mode.
if (games::ddr::SDMODE) {
float value = (light_bits & (1 << mapping_bits[8])) ? 1.f : 0.f;
GameAPI::Lights::writeLight(RI_MGR, lights.at(mapping[8]), value);
}
// flush
RI_MGR->devices_flush_output();
}
// trigger out data
data_trigger = true;
// return all data written
return (int) nNumberOfBytesToWrite;
}
int games::ddr::DDRFOOTHandle::device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer,
DWORD nOutBufferSize) {
return -1;
}
bool games::ddr::DDRFOOTHandle::close() {
log_info("ddr", "Closed COM1 (FOOT).");
return true;
}

23
games/ddr/p3io/foot.h Normal file
View File

@@ -0,0 +1,23 @@
#pragma once
#include "hooks/devicehook.h"
namespace games::ddr {
class DDRFOOTHandle : public CustomHandle {
private:
bool data_trigger = false;
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;
};
}

550
games/ddr/p3io/p3io.cpp Normal file
View File

@@ -0,0 +1,550 @@
#include "p3io.h"
#include "cfg/api.h"
#include "rawinput/rawinput.h"
#include "util/logging.h"
#include "util/utils.h"
#include "../ddr.h"
#include "../io.h"
using namespace acioemu;
games::ddr::DDRP3IOHandle::HDXSDevice::HDXSDevice() {
this->node_count = 1;
}
bool games::ddr::DDRP3IOHandle::HDXSDevice::parse_msg(
MessageData* msg_in,
circular_buffer<uint8_t> *response_buffer
) {
#ifdef ACIOEMU_LOG
log_info("ddr", "HDXS ADDR: {}, CMD: 0x{:x}", msg_in->addr, msg_in->cmd.code);
#endif
// handle command
switch (msg_in->cmd.code) {
case ACIO_CMD_GET_VERSION: {
// send version data
auto msg = this->create_msg(msg_in, MSG_VERSION_SIZE);
this->set_version(msg, 0x204, 0, 1, 6, 0, "HDXS");
write_msg(msg, response_buffer);
delete msg;
break;
}
case ACIO_CMD_KEEPALIVE: {
// send empty message
auto msg = this->create_msg(msg_in, 0);
write_msg(msg, response_buffer);
delete msg;
break;
}
case 0x0112: { // LED
static const size_t hd_button_static_mapping[] {
Lights::HD_P1_START,
Lights::HD_P1_UP_DOWN,
Lights::HD_P1_LEFT_RIGHT,
Lights::HD_P2_START,
Lights::HD_P2_UP_DOWN,
Lights::HD_P2_LEFT_RIGHT,
};
static const size_t hd_panel_static_mapping[] {
Lights::HD_P1_SPEAKER_F_G,
Lights::HD_P1_SPEAKER_F_R,
Lights::HD_P1_SPEAKER_F_B,
Lights::HD_P2_SPEAKER_F_G,
Lights::HD_P2_SPEAKER_F_R,
Lights::HD_P2_SPEAKER_F_B,
Lights::HD_P1_SPEAKER_W_G,
Lights::HD_P1_SPEAKER_W_R,
Lights::HD_P1_SPEAKER_W_B,
Lights::HD_P2_SPEAKER_W_G,
Lights::HD_P2_SPEAKER_W_R,
Lights::HD_P2_SPEAKER_W_B,
};
// get lights
auto &lights = games::ddr::get_lights();
// check to see mode at runtime.
if (!games::ddr::SDMODE) {
const auto &data = &msg_in->cmd.raw[1];
// button LEDs
for (size_t i = 0; i < std::size(hd_button_static_mapping); i++) {
const float value = (data[i] & 0x80) ? 1.f : 0.f;
GameAPI::Lights::writeLight(RI_MGR, lights[hd_button_static_mapping[i]], value);
}
// speaker LEDs
for (size_t i = 0; i < std::size(hd_panel_static_mapping) / 3; i++) {
const size_t light_index = i * 3;
const float g = static_cast<float>(data[light_index + 0] & 0x7f) / 127.f;
const float r = static_cast<float>(data[light_index + 1] & 0x7f) / 127.f;
const float b = static_cast<float>(data[light_index + 2] & 0x7f) / 127.f;
GameAPI::Lights::writeLight(RI_MGR, lights[hd_panel_static_mapping[light_index + 0]], g);
GameAPI::Lights::writeLight(RI_MGR, lights[hd_panel_static_mapping[light_index + 1]], r);
GameAPI::Lights::writeLight(RI_MGR, lights[hd_panel_static_mapping[light_index + 2]], b);
}
}
// flush
RI_MGR->devices_flush_output();
// send status 0
auto msg = this->create_msg_status(msg_in, 0x00);
write_msg(msg, response_buffer);
delete msg;
break;
}
case ACIO_CMD_CLEAR:
case ACIO_CMD_STARTUP:
case 0x0110: // ???
case 0x0128: // ???
case 0xFF: // BROADCAST
{
// send status 0
auto msg = this->create_msg_status(msg_in, 0x00);
write_msg(msg, response_buffer);
delete msg;
break;
}
default:
return false;
}
// mark as handled
return true;
}
void games::ddr::DDRP3IOHandle::write_msg(const uint8_t *data, size_t len) {
read_buf.put(0xAA);
read_buf.put((uint8_t) len);
for (size_t i = 0; i < len; i++) {
uint8_t b = data[i];
if (b == 0xAA || b == 0xFF) {
read_buf.put(0xFF);
b = ~b;
}
read_buf.put(b);
}
}
bool games::ddr::DDRP3IOHandle::open(LPCWSTR lpFileName) {
if (wcscmp(lpFileName, L"\\\\.\\P3IO\\p3io") != 0)
return false;
if (!acio_emu) {
acio_emu = new acioemu::ACIOEmu();
// DO NOT CHANGE THE ORDER OF THESE
// (until ICCA is split into separate devices with individual unit ids)
acio_emu->add_device(new acioemu::ICCADevice(false, true, 2));
acio_emu->add_device(new HDXSDevice());
}
log_info("ddr", "Opened P3IO");
return true;
}
int games::ddr::DDRP3IOHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) {
auto buffer = reinterpret_cast<uint8_t *>(lpBuffer);
// read from emu
DWORD bytes_read = 0;
while (!read_buf.empty() && bytes_read < nNumberOfBytesToRead) {
buffer[bytes_read++] = read_buf.get();
}
// this mustn't happen ever
if (bytes_read == 0) {
log_fatal("ddr", "p3io received no data");
}
// return amount of bytes read
return (int) bytes_read;
}
int games::ddr::DDRP3IOHandle::write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) {
auto buffer = reinterpret_cast<const uint8_t *>(lpBuffer);
// check header
if (nNumberOfBytesToWrite < 2 || buffer[0] != 0xAA || buffer[1] > 0x7F) {
log_warning("ddr", "p3io has the wrong header: {}", bin2hex(buffer, (int) nNumberOfBytesToWrite));
return (int) nNumberOfBytesToWrite;
}
// parse data
std::vector<uint8_t> parsed;
for (DWORD i = 2; i < nNumberOfBytesToWrite; i++) {
uint8_t b = buffer[i];
if (b == 0xFFu && i + 1 < nNumberOfBytesToWrite) {
b = ~buffer[++i];
}
parsed.push_back(b);
}
// check message size
if (parsed.size() != buffer[1]) {
log_warning("ddr", "p3io has wrong message size: {}/{}", parsed.size(), (int) buffer[1]);
return (int) nNumberOfBytesToWrite;
}
// check command
switch (parsed[1]) {
case 0x01: { // VERSION
uint8_t data[8];
memset(data, 0, 8);
// device code
strncpy((char*) &data[2], "JDX", 4);
// write message
write_msg(data, sizeof(data));
break;
}
case 0x05: { // WATCHDOG
uint8_t data[] = {0x00, 0x00};
write_msg(data, sizeof(data));
break;
}
case 0x24: { // SET LIGHTS
// hd mappings
static const size_t hd_mapping_bits[] {
0x1000000, // HD SPOT RED
0x2000000, // HD SPOT BLUE
0x4000000, // HD TOP SPOT RED
0x8000000, // HD TOP SPOT BLUE
};
static const size_t hd_mapping[] {
Lights::SPOT_RED,
Lights::SPOT_BLUE,
Lights::TOP_SPOT_RED,
Lights::TOP_SPOT_BLUE
};
// sd mappings, bits are reused according to gamemode
static const size_t sd_mapping_bits[] {
0x01000000, // SD P1 BUTTON
0x02000000, // SD P2 BUTTON
0x40000000, // SD P1 HALOGEN LOWER
0x80000000, // SD P1 HALOGEN UPPER
0x10000000, // SD P2 HALOGEN LOWER
0x20000000, // SD P2 HALOGEN UPPER
};
static const size_t sd_mapping[] {
Lights::P1_BUTTON,
Lights::P2_BUTTON,
Lights::P1_HALOGEN_LOWER,
Lights::P1_HALOGEN_UPPER,
Lights::P2_HALOGEN_LOWER,
Lights::P2_HALOGEN_UPPER
};
static const size_t hd_to_sd_mapping[] {
Lights::P1_HALOGEN_LOWER,
Lights::P1_HALOGEN_UPPER,
Lights::P2_HALOGEN_LOWER,
Lights::P2_HALOGEN_UPPER
};
// get light bits
uint32_t light_bits = *reinterpret_cast<uint32_t *>(&parsed[3]);
// get lights
auto &lights = get_lights();
// check to see mode at runtime.
if (games::ddr::SDMODE) {
// bit scan
for (size_t i = 0; i < 6; i++) {
float value = (light_bits & sd_mapping_bits[i]) ? 1.f : 0.f;
GameAPI::Lights::writeLight(RI_MGR, lights[sd_mapping[i]], value);
}
} else {
// bit scan
for (size_t i = 0; i < 4; i++) {
float value = (light_bits & hd_mapping_bits[i]) ? 1.f : 0.f;
GameAPI::Lights::writeLight(RI_MGR, lights[hd_mapping[i]], value);
// perform HD->SD light mappings for older cabinet styles.
GameAPI::Lights::writeLight(RI_MGR, lights[hd_to_sd_mapping[i]], value);
}
// use both sat spots for a neon pulse
float value_neon = (light_bits & hd_mapping_bits[0] && hd_mapping_bits[1]) ? 1.f : 0.f;
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::NEON], value_neon);
}
// flush
RI_MGR->devices_flush_output();
uint8_t data[] = {0x00, 0x00};
write_msg(data, sizeof(data));
break;
}
case 0x25: { // SEC PLUG
uint8_t data[43] {};
// plug present
data[2] = 1;
// copy data
if (parsed[2] & 0x10) {
static const uint8_t black_data[] {
0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7,
0xBE, 0xB5, 0xB2, 0xAC, 0x16, 0x8C, 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, 0xF7
};
memcpy(&data[3], black_data, sizeof(black_data));
} else {
static const uint8_t white_data[] {
0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7,
0xC3, 0xD4, 0x45, 0xE8, 0x7C, 0x17, 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, 0x1B
};
memcpy(&data[3], white_data, sizeof(white_data));
}
// write data
write_msg(data, sizeof(data));
break;
}
case 0x27: { // CABINET TYPE
if (games::ddr::SDMODE) {
uint8_t data[] = {0x00, 0x00};
write_msg(data, sizeof(data));
} else {
uint8_t data[] = {0x00, 0x01};
write_msg(data, sizeof(data));
}
break;
}
case 0x29: { // VIDEO FREQUENCY
uint8_t data[] = {0x00, 0x00, 0x00};
write_msg(data, sizeof(data));
break;
}
case 0x2B: { // ???
uint8_t data[] = {0x00, 0x00};
write_msg(data, sizeof(data));
break;
}
case 0x2F: { // MODE
uint8_t data[] = {0x00, 0x00};
write_msg(data, sizeof(data));
break;
}
case 0x31: { // COIN STOCK
uint8_t data[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
// TODO
write_msg(data, sizeof(data));
break;
}
case 0x32: { // ???
uint8_t data[] = {0x00, 0x00};
write_msg(data, sizeof(data));
break;
}
case 0x38: { // PORT OPERATION
uint8_t op = parsed[3];
uint8_t port = parsed[2];
//uint8_t baud = parsed[4];
// open port
if (op == 0x00) {
log_info("ddr", "opened p3io remote port #{}", (int) port);
uint8_t data[] = {0x00, 0x00, 0x00};
write_msg(data, sizeof(data));
break;
}
// close port
if (op == 0xFF) {
log_info("ddr", "closed p3io remote port #{}", (int) port);
uint8_t data[] = {0x00, 0x00, 0x00};
write_msg(data, sizeof(data));
break;
}
// error
uint8_t data[] = {0x00, 0x00, 0xFF};
write_msg(data, sizeof(data));
break;
}
case 0x3A: { // REMOTE PORT WRITE
uint8_t len = parsed[3];
// check length
if (len > parsed.size() - 4) {
log_fatal("ddr", "p3io remote port data too small");
}
// pass data to ACIO
for (int i = 4; i < len + 4; i++) {
acio_emu->write(parsed[i]);
}
// no error
uint8_t data[] = {0x00, 0x00, len};
write_msg(data, sizeof(data));
break;
}
case 0x3B: { // REMOTE PORT READ
uint8_t len = parsed[3];
// build msg data
std::vector<uint8_t> msg_data;
msg_data.reserve(static_cast<size_t>(len) + 3);
msg_data.push_back(0x00);
msg_data.push_back(0x00);
// placeholder for ACIO message size
msg_data.push_back(len);
// read data from ACIO
uint8_t acio_len = 0;
while (acio_len < len && acio_len < 0xFF) {
auto cur_byte = acio_emu->read();
if (cur_byte.has_value()) {
msg_data.push_back(cur_byte.value());
acio_len++;
} else {
break;
}
}
// update placeholder with actual length
msg_data[2] = acio_len;
// write message
write_msg(msg_data.data(), msg_data.size());
break;
}
default: {
log_fatal("ddr", "p3io unknown command: 0x{:x}", parsed[1]);
}
}
// return all data written
return (int) nNumberOfBytesToWrite;
}
int games::ddr::DDRP3IOHandle::device_io(
DWORD dwIoControlCode,
LPVOID lpInBuffer,
DWORD nInBufferSize,
LPVOID lpOutBuffer,
DWORD nOutBufferSize
) {
// check buffer size
if (nOutBufferSize >= 4) {
// cool down
Sleep(1);
// get controls as single variable (4 bytes)
auto &controls = *(uint32_t*) lpOutBuffer;
// reset
controls = 0;
/*
* P3IO DDR Bit Mappings
* 4 bytes, from low to high order (0-31)
* all bits represent the inverted state
*
*
* 30 - SERVICE
* 28 - TEST
* 29 - COIN MECH
* 8 - P1 START
* 9 - P1 PANEL UP
* 10 - P1 PANEL DOWN
* 11 - P1 PANEL LEFT
* 12 - P1 PANEL RIGHT
* 24 - P1 MENU UP
* 25 - P1 MENU DOWN
* 14 - P1 MENU LEFT
* 15 - P1 MENU RIGHT
* 16 - P2 START
* 17 - P2 PANEL UP
* 18 - P2 PANEL DOWN
* 19 - P2 PANEL LEFT
* 20 - P2 PANEL RIGHT
* 26 - P2 MENU UP
* 27 - P2 MENU DOWN
* 22 - P2 MENU LEFT
* 23 - P2 MENU RIGHT
*/
// shift table
static size_t shift_table[] = {
30, 28, 29, 8, 9, 10, 11, 12, 24, 25, 14, 15, 16, 17, 18, 19, 20, 26, 27, 22, 23
};
static size_t button_table[] = {
Buttons::SERVICE,
Buttons::TEST,
Buttons::COIN_MECH,
Buttons::P1_START,
Buttons::P1_PANEL_UP,
Buttons::P1_PANEL_DOWN,
Buttons::P1_PANEL_LEFT,
Buttons::P1_PANEL_RIGHT,
Buttons::P1_MENU_UP,
Buttons::P1_MENU_DOWN,
Buttons::P1_MENU_LEFT,
Buttons::P1_MENU_RIGHT,
Buttons::P2_START,
Buttons::P2_PANEL_UP,
Buttons::P2_PANEL_DOWN,
Buttons::P2_PANEL_LEFT,
Buttons::P2_PANEL_RIGHT,
Buttons::P2_MENU_UP,
Buttons::P2_MENU_DOWN,
Buttons::P2_MENU_LEFT,
Buttons::P2_MENU_RIGHT,
};
// update states
auto &buttons = get_buttons();
size_t count = 0;
for (auto shift : shift_table) {
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_table[count++]))) {
controls |= 1 << shift;
}
}
// invert controls
controls = ~controls;
// return data size
return 4;
}
// fail
return -1;
}
bool games::ddr::DDRP3IOHandle::close() {
log_info("ddr", "Closed P3IO");
return true;
}

37
games/ddr/p3io/p3io.h Normal file
View File

@@ -0,0 +1,37 @@
#pragma once
#include "acioemu/acioemu.h"
#include "hooks/devicehook.h"
namespace games::ddr {
class DDRP3IOHandle : public CustomHandle {
private:
acioemu::ACIOEmu *acio_emu;
circular_buffer<uint8_t> read_buf = circular_buffer<uint8_t>(1024);
class HDXSDevice : public acioemu::ACIODeviceEmu {
public:
HDXSDevice();
bool parse_msg(
acioemu::MessageData* msg_in,
circular_buffer<uint8_t> *response_buffer
) override;
};
void write_msg(const uint8_t *data, size_t len);
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;
};
}

102
games/ddr/p3io/sate.cpp Normal file
View File

@@ -0,0 +1,102 @@
#include "sate.h"
#include "util/logging.h"
using namespace acioemu;
games::ddr::DDRSATEHandle::SATEDevice::SATEDevice() {
this->node_count = 7;
}
bool games::ddr::DDRSATEHandle::SATEDevice::parse_msg(
acioemu::MessageData *msg_in,
circular_buffer<uint8_t> *response_buffer
) {
// check command
switch (msg_in->cmd.code) {
case ACIO_CMD_GET_VERSION: {
// send version data
auto msg = this->create_msg(msg_in, MSG_VERSION_SIZE);
this->set_version(msg, 0x105, 0, 1, 1, 0, "DDRS");
write_msg(msg, response_buffer);
delete msg;
break;
}
case ACIO_CMD_CLEAR:
case ACIO_CMD_STARTUP:
case 0xFF: // BROADCAST
{
// send status 0
auto msg = this->create_msg_status(msg_in, 0x00);
write_msg(msg, response_buffer);
delete msg;
break;
}
default:
return false;
}
// mark as handled
return true;
}
bool games::ddr::DDRSATEHandle::open(LPCWSTR lpFileName) {
if (wcscmp(lpFileName, L"COM2") != 0) {
return false;
}
log_info("ddr", "Opened COM2 (SATE)");
// ACIO device
acio_emu.add_device(new SATEDevice());
return true;
}
int games::ddr::DDRSATEHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) {
auto buffer = reinterpret_cast<uint8_t *>(lpBuffer);
// read from emu
DWORD bytes_read = 0;
while (bytes_read < nNumberOfBytesToRead) {
auto cur_byte = acio_emu.read();
if (cur_byte.has_value()) {
buffer[bytes_read++] = cur_byte.value();
} else {
break;
}
}
// return amount of bytes read
return (int) bytes_read;
}
int games::ddr::DDRSATEHandle::write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) {
auto buffer = reinterpret_cast<const uint8_t *>(lpBuffer);
// write to emu
for (DWORD i = 0; i < nNumberOfBytesToWrite; i++) {
acio_emu.write(buffer[i]);
}
// return all data written
return (int) nNumberOfBytesToWrite;
}
int games::ddr::DDRSATEHandle::device_io(
DWORD dwIoControlCode,
LPVOID lpInBuffer,
DWORD nInBufferSize,
LPVOID lpOutBuffer,
DWORD nOutBufferSize
) {
return -1;
}
bool games::ddr::DDRSATEHandle::close() {
log_info("ddr", "Closed COM2 (SATE).");
return true;
}

31
games/ddr/p3io/sate.h Normal file
View File

@@ -0,0 +1,31 @@
#pragma once
#include "hooks/devicehook.h"
#include "acioemu/acioemu.h"
namespace games::ddr {
class DDRSATEHandle : public CustomHandle {
private:
acioemu::ACIOEmu acio_emu;
class SATEDevice : public acioemu::ACIODeviceEmu {
public:
SATEDevice();
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;
};
}

60
games/ddr/p3io/usbmem.cpp Normal file
View File

@@ -0,0 +1,60 @@
#include "usbmem.h"
#include "util/logging.h"
void games::ddr::DDRUSBMEMHandle::respond(const char *data) {
size_t len = strlen(data) + 1;
memcpy(response_data, data, len);
memcpy(response_data + len, "\r>", 2);
response_data_size = len + 2;
}
bool games::ddr::DDRUSBMEMHandle::open(LPCWSTR lpFileName) {
if (wcscmp(lpFileName, L"COM3") != 0) {
return false;
}
log_info("ddr", "Opened COM3 (USBMEM)");
return true;
}
int games::ddr::DDRUSBMEMHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) {
// check for response
if (response_data_size && nNumberOfBytesToRead >= response_data_size) {
size_t bytes_read = response_data_size;
memcpy(lpBuffer, response_data, response_data_size);
response_data_size = 0;
return (int) bytes_read;
}
// no data
return 0;
}
int games::ddr::DDRUSBMEMHandle::write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) {
// check CMD
if (!memcmp(lpBuffer, "sver", 4))
respond("done GQHDXJAA SPICE");
else if (!memcmp(lpBuffer, "on_a", 4) ||
!memcmp(lpBuffer, "on_b", 4) ||
!memcmp(lpBuffer, "offa", 4) ||
!memcmp(lpBuffer, "offb", 4) ||
!memcmp(lpBuffer, "lma ", 4) ||
!memcmp(lpBuffer, "lmb ", 4))
respond("done");
else
respond("not connected");
// return all data written
return (int) nNumberOfBytesToWrite;
}
int games::ddr::DDRUSBMEMHandle::device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize,
LPVOID lpOutBuffer, DWORD nOutBufferSize) {
return -1;
}
bool games::ddr::DDRUSBMEMHandle::close() {
log_info("ddr", "Closed COM3 (USBMEM).");
return true;
}

26
games/ddr/p3io/usbmem.h Normal file
View File

@@ -0,0 +1,26 @@
#pragma once
#include "hooks/devicehook.h"
namespace games::ddr {
class DDRUSBMEMHandle : public CustomHandle {
private:
char response_data[256]{};
size_t response_data_size = 0;
void respond(const char *data);
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;
};
}

352
games/ddr/p4io/p4io.cpp Normal file
View File

@@ -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;
}
}

36
games/ddr/p4io/p4io.h Normal file
View File

@@ -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;
};
}

26
games/dea/dea.cpp Normal file
View File

@@ -0,0 +1,26 @@
#include "dea.h"
#include "launcher/launcher.h"
#include "util/libutils.h"
#include "util/logging.h"
namespace games::dea {
DEAGame::DEAGame() : Game("Dance Evolution") {
}
void DEAGame::attach() {
Game::attach();
// since this DLL isn't automatically loaded the hooks don't work unless we load it manually
libutils::try_library(MODULE_PATH / "gamekdm.dll");
// Has the user actually performed first-time setup?
auto kinect10 = libutils::try_library("Kinect10.dll");
if (!kinect10) {
log_warning("dea", "Failed to load 'Kinect10.dll'. Game will boot in Test Mode. Have you installed the Kinect SDK?");
} else {
FreeLibrary(kinect10);
}
}
}

12
games/dea/dea.h Normal file
View File

@@ -0,0 +1,12 @@
#pragma once
#include "games/game.h"
namespace games::dea {
class DEAGame : public games::Game {
public:
DEAGame();
virtual void attach() override;
};
}

68
games/dea/io.cpp Normal file
View File

@@ -0,0 +1,68 @@
#include "io.h"
std::vector<Button> &games::dea::get_buttons() {
static std::vector<Button> buttons;
if (buttons.empty()) {
buttons = GameAPI::Buttons::getButtons("Dance Evolution");
GameAPI::Buttons::sortButtons(
&buttons,
"Service",
"Test",
"P1 Start",
"P1 Left",
"P1 Right",
"P2 Start",
"P2 Left",
"P2 Right"
);
}
return buttons;
}
std::vector<Light> &games::dea::get_lights() {
static std::vector<Light> lights;
if (lights.empty()) {
lights = GameAPI::Lights::getLights("Dance Evolution");
GameAPI::Lights::sortLights(
&lights,
"Title R",
"Title G",
"Title B",
"Side Upper Left R",
"Side Upper Left G",
"Side Upper Left B",
"Side Upper Right R",
"Side Upper Right G",
"Side Upper Right B",
"P1 Start",
"P1 L/R Button",
"P2 Start",
"P2 L/R Button",
"Side Lower Left 1 R",
"Side Lower Left 1 G",
"Side Lower Left 1 B",
"Side Lower Left 2 R",
"Side Lower Left 2 G",
"Side Lower Left 2 B",
"Side Lower Left 3 R",
"Side Lower Left 3 G",
"Side Lower Left 3 B",
"Side Lower Right 1 R",
"Side Lower Right 1 G",
"Side Lower Right 1 B",
"Side Lower Right 2 R",
"Side Lower Right 2 G",
"Side Lower Right 2 B",
"Side Lower Right 3 R",
"Side Lower Right 3 G",
"Side Lower Right 3 B"
);
}
return lights;
}

62
games/dea/io.h Normal file
View File

@@ -0,0 +1,62 @@
#pragma once
#include <vector>
#include "cfg/api.h"
namespace games::dea {
// all buttons in correct order
namespace Buttons {
enum {
Service,
Test,
P1Start,
P1Left,
P1Right,
P2Start,
P2Left,
P2Right
};
}
// all lights in correct order
namespace Lights {
enum {
TitleR,
TitleG,
TitleB,
SideUpperLeftR,
SideUpperLeftG,
SideUpperLeftB,
SideUpperRightR,
SideUpperRightG,
SideUpperRightB,
P1Start,
P1LRButton,
P2Start,
P2LRButton,
SideLowerLeft1R,
SideLowerLeft1G,
SideLowerLeft1B,
SideLowerLeft2R,
SideLowerLeft2G,
SideLowerLeft2B,
SideLowerLeft3R,
SideLowerLeft3G,
SideLowerLeft3B,
SideLowerRight1R,
SideLowerRight1G,
SideLowerRight1B,
SideLowerRight2R,
SideLowerRight2G,
SideLowerRight2B,
SideLowerRight3R,
SideLowerRight3G,
SideLowerRight3B,
};
}
// getters
std::vector<Button> &get_buttons();
std::vector<Light> &get_lights();
}

466
games/drs/drs.cpp Normal file
View File

@@ -0,0 +1,466 @@
#include "drs.h"
#include <windows.h>
#include <thread>
#include "avs/game.h"
#include "games/game.h"
#include "util/detour.h"
#include "util/logging.h"
#include "util/memutils.h"
#include "misc/vrutil.h"
#pragma pack(push)
typedef struct {
union {
struct {
WORD unk1;
WORD unk2;
WORD device_id;
WORD vid;
WORD pid;
WORD pvn;
WORD max_point_num;
};
uint8_t raw[2356];
};
} dev_info_t;
typedef struct {
DWORD cid;
DWORD type;
DWORD unused;
DWORD y;
DWORD x;
DWORD height;
DWORD width;
DWORD unk8;
} touch_data_t;
#pragma pack(pop)
enum touch_type {
TS_DOWN = 1,
TS_MOVE = 2,
TS_UP = 3,
};
void *user_data = nullptr;
void (*touch_callback)(
dev_info_t *dev_info,
const touch_data_t *touch_data,
int touch_points,
int unk1,
const void *user_data);
namespace games::drs {
void* TouchSDK_Constructor(void* in) {
return in;
}
bool TouchSDK_SendData(dev_info_t*,
unsigned char * const, int, unsigned char * const,
unsigned char* output, int output_size) {
// fake success
if (output_size >= 4) {
output[0] = 0xfc;
output[1] = 0xa5;
}
return true;
}
bool TouchSDK_SetSignalInit(dev_info_t*, int) {
return true;
}
void TouchSDK_Destructor(void* This) {
}
int TouchSDK_GetYLedTotal(dev_info_t*, int) {
return 53;
}
int TouchSDK_GetXLedTotal(dev_info_t*, int) {
return 41;
}
bool TouchSDK_DisableTouch(dev_info_t*, int) {
return true;
}
bool TouchSDK_DisableDrag(dev_info_t*, int) {
return true;
}
bool TouchSDK_DisableWheel(dev_info_t*, int) {
return true;
}
bool TouchSDK_DisableRightClick(dev_info_t*, int) {
return true;
}
bool TouchSDK_SetMultiTouchMode(dev_info_t*, int) {
return true;
}
bool TouchSDK_EnableTouchWidthData(dev_info_t*, int) {
return true;
}
bool TouchSDK_EnableRawData(dev_info_t*, int) {
return true;
}
bool TouchSDK_SetAllEnable(dev_info_t*, bool, int) {
return true;
}
int TouchSDK_GetTouchDeviceCount(void* This) {
return 1;
}
unsigned int TouchSDK_GetTouchSDKVersion(void) {
return 0x01030307;
}
int TouchSDK_InitTouch(void* This, dev_info_t *devices, int max_devices, void* touch_event_cb,
void* hotplug_callback, void* userdata) {
// fake touch device
memset(devices, 0, sizeof(devices[0].raw));
devices[0].unk1 = 0x1122;
devices[0].unk2 = 0x3344;
devices[0].device_id = 0;
devices[0].vid = 0xDEAD;
devices[0].pid = 0xBEEF;
devices[0].pvn = 0xC0DE;
devices[0].max_point_num = 16;
// remember provided callback and userdata
touch_callback = (decltype(touch_callback)) touch_event_cb;
user_data = userdata;
// success
return 1;
}
char DRS_TAPELED[38 * 49][3] {};
bool VR_STARTED = false;
bool DISABLE_TOUCH = false;
bool TRANSPOSE_TOUCH = 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},
};
std::vector<TouchEvent> TOUCH_EVENTS;
inline DWORD scale_double_to_xy(double val) {
return static_cast<DWORD>(val * 32768);
}
inline DWORD scale_double_to_height(double val) {
return static_cast<DWORD>(val * 1312);
}
inline DWORD scale_double_to_width(double val) {
return static_cast<DWORD>(val * 1696);
}
void fire_touches(drs_touch_t *events, size_t event_count) {
// check callback first
if (!touch_callback) {
return;
}
// generate touch data
auto game_touches = std::make_unique<touch_data_t[]>(event_count);
for (size_t i = 0; i < event_count; i++) {
// initialize touch value
game_touches[i].cid = (DWORD) events[i].id;
game_touches[i].unk8 = 0;
// copy scaled values
game_touches[i].x = scale_double_to_xy(events[i].x);
game_touches[i].y = scale_double_to_xy(events[i].y);
game_touches[i].width = scale_double_to_width(events[i].width);
game_touches[i].height = scale_double_to_height(events[i].height);
// decide touch type
switch(events[i].type) {
case DRS_DOWN:
game_touches[i].type = TS_DOWN;
break;
case DRS_UP:
game_touches[i].type = TS_UP;
break;
case DRS_MOVE:
game_touches[i].type = TS_MOVE;
break;
default:
break;
}
}
// build device information
dev_info_t dev;
dev.unk1 = 0;
dev.unk2 = 0;
dev.device_id = 0;
dev.vid = 0xDEAD;
dev.pid = 0xBEEF;
dev.pvn = 0xC0DE;
dev.max_point_num = 16;
// fire callback
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;
}
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;
}
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;
}
}
}
// slow down
vrutil::scan();
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
VR_STARTED = false;
return nullptr;
});
t.detach();
}
}
void start_touch() {
std::thread t([] {
log_info("drs", "starting touch input thread");
// main loop
while (TRUE) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
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;
// 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);
}
}
return nullptr;
});
t.detach();
}
DRSGame::DRSGame() : Game("DANCERUSH") {
}
void DRSGame::attach() {
Game::attach();
// TouchSDK hooks
detour::iat("??0TouchSDK@@QEAA@XZ",
(void *) &TouchSDK_Constructor, avs::game::DLL_INSTANCE);
detour::iat("?SendData@TouchSDK@@QEAA_NU_DeviceInfo@@QEAEH1HH@Z",
(void *) &TouchSDK_SendData, avs::game::DLL_INSTANCE);
detour::iat("?SetSignalInit@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z",
(void *) &TouchSDK_SetSignalInit, avs::game::DLL_INSTANCE);
detour::iat("??1TouchSDK@@QEAA@XZ",
(void *) &TouchSDK_Destructor, avs::game::DLL_INSTANCE);
detour::iat("?GetYLedTotal@TouchSDK@@QEAAHU_DeviceInfo@@H@Z",
(void *) &TouchSDK_GetYLedTotal, avs::game::DLL_INSTANCE);
detour::iat("?GetXLedTotal@TouchSDK@@QEAAHU_DeviceInfo@@H@Z",
(void *) &TouchSDK_GetXLedTotal, avs::game::DLL_INSTANCE);
detour::iat("?DisableTouch@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z",
(void *) &TouchSDK_DisableTouch, avs::game::DLL_INSTANCE);
detour::iat("?DisableDrag@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z",
(void *) &TouchSDK_DisableDrag, avs::game::DLL_INSTANCE);
detour::iat("?DisableWheel@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z",
(void *) &TouchSDK_DisableWheel, avs::game::DLL_INSTANCE);
detour::iat("?DisableRightClick@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z",
(void *) &TouchSDK_DisableRightClick, avs::game::DLL_INSTANCE);
detour::iat("?SetMultiTouchMode@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z",
(void *) &TouchSDK_SetMultiTouchMode, avs::game::DLL_INSTANCE);
detour::iat("?EnableTouchWidthData@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z",
(void *) &TouchSDK_EnableTouchWidthData, avs::game::DLL_INSTANCE);
detour::iat("?EnableRawData@TouchSDK@@QEAA_NU_DeviceInfo@@H@Z",
(void *) &TouchSDK_EnableRawData, avs::game::DLL_INSTANCE);
detour::iat("?SetAllEnable@TouchSDK@@QEAA_NU_DeviceInfo@@_NH@Z",
(void *) &TouchSDK_SetAllEnable, avs::game::DLL_INSTANCE);
detour::iat("?GetTouchDeviceCount@TouchSDK@@QEAAHXZ",
(void *) &TouchSDK_GetTouchDeviceCount, avs::game::DLL_INSTANCE);
detour::iat("?GetTouchSDKVersion@TouchSDK@@QEAAIXZ",
(void *) &TouchSDK_GetTouchSDKVersion, avs::game::DLL_INSTANCE);
detour::iat("?InitTouch@TouchSDK@@QEAAHPEAU_DeviceInfo@@HP6AXU2@PEBU_TouchPointData@@HHPEBX@ZP6AX1_N3@ZPEAX@Z",
(void *) &TouchSDK_InitTouch, avs::game::DLL_INSTANCE);
if (vrutil::STATUS == vrutil::VRStatus::Running) {
start_vr();
} else if (!DISABLE_TOUCH) {
start_touch();
} else {
log_info("drs", "no native input method detected");
}
}
void DRSGame::detach() {
Game::detach();
}
}

56
games/drs/drs.h Normal file
View File

@@ -0,0 +1,56 @@
#pragma once
#include "games/game.h"
#include "external/linalg.h"
#include "touch/touch.h"
namespace games::drs {
enum DRS_TOUCH_TYPE {
DRS_DOWN = 0,
DRS_UP = 1,
DRS_MOVE = 2,
};
typedef struct drs_touch {
int type = DRS_UP;
int id = 0;
double x = 0.0;
double y = 0.0;
double width = 1;
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);
};
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];
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:
DRSGame();
virtual void attach() override;
virtual void detach() override;
};
}

57
games/drs/io.cpp Normal file
View File

@@ -0,0 +1,57 @@
#include "io.h"
std::vector<Button> &games::drs::get_buttons() {
static std::vector<Button> buttons;
if (buttons.empty()) {
buttons = GameAPI::Buttons::getButtons("DANCERUSH");
GameAPI::Buttons::sortButtons(
&buttons,
"Service",
"Test",
"Coin Mech",
"P1 Start",
"P1 Up",
"P1 Down",
"P1 Left",
"P1 Right",
"P2 Start",
"P2 Up",
"P2 Down",
"P2 Left",
"P2 Right"
);
}
return buttons;
}
std::vector<Light> &games::drs::get_lights() {
static std::vector<Light> 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;
}

52
games/drs/io.h Normal file
View File

@@ -0,0 +1,52 @@
#pragma once
#include <vector>
#include "cfg/api.h"
namespace games::drs {
// all buttons in correct order
namespace Buttons {
enum {
Service,
Test,
CoinMech,
P1_Start,
P1_Up,
P1_Down,
P1_Left,
P1_Right,
P2_Start,
P2_Up,
P2_Down,
P2_Left,
P2_Right
};
}
// 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::vector<Light> &get_lights();
}

152
games/ftt/ftt.cpp Normal file
View File

@@ -0,0 +1,152 @@
#include "ftt.h"
#include "nui.h"
#include "launcher/launcher.h"
#include "util/detour.h"
#include "util/libutils.h"
#include "util/logging.h"
#include "util/sigscan.h"
#include "util/memutils.h"
typedef HRESULT (WINAPI *NuiGetSensorCount_t)(int *pCount);
typedef HRESULT (WINAPI *NuiCreateSensorByIndex_t)(int index, INuiSensor **ppNuiSensor);
static NuiGetSensorCount_t NuiGetSensorCount_orig = nullptr;
static NuiCreateSensorByIndex_t NuiCreateSensorByIndex_orig = nullptr;
static VTBL_TYPE(INuiSensor, NuiInitialize) INuiSensor_NuiInitialize_orig = nullptr;
static VTBL_TYPE(INuiSensor, NuiImageStreamOpen) INuiSensor_NuiImageStreamOpen_orig = nullptr;
static HRESULT __stdcall INuiSensor_NuiInitialize_hook(INuiSensor *This, DWORD dwFlags) {
log_misc("ftt", "INuiSensor::NuiInitialize hook hit (dwFlags: {})", dwFlags);
// call original function
HRESULT ret = INuiSensor_NuiInitialize_orig(This, dwFlags);
// check return value
if (FAILED(ret)) {
log_warning("ftt", "INuiSensor::NuiInitialize failed, hr={}", FMT_HRESULT(ret));
}
return ret;
}
static HRESULT __stdcall INuiSensor_NuiImageStreamOpen_hook(
INuiSensor *This,
NUI_IMAGE_TYPE eImageType,
NUI_IMAGE_RESOLUTION eResolution,
DWORD dwImageFrameFlags,
DWORD dwFrameLimit,
HANDLE hNextFrameEvent,
HANDLE *phStreamHandle) {
log_misc("ftt", "INuiSensor::NuiImageStreamOpen hook hit");
// fix unsupported flag
if (eImageType == NUI_IMAGE_TYPE_DEPTH_AND_PLAYER_INDEX) {
dwImageFrameFlags = 0;
}
// call original function
HRESULT ret = INuiSensor_NuiImageStreamOpen_orig(This, eImageType, eResolution, dwImageFrameFlags, dwFrameLimit, hNextFrameEvent, phStreamHandle);
// check return value
if (FAILED(ret)) {
log_warning("ftt", "INuiSensor::NuiImageStreamOpen failed, hr={}", FMT_HRESULT(ret));
}
return ret;
}
static HRESULT WINAPI NuiGetSensorCount_hook(int *pCount) {
log_misc("ftt", "NuiGetSensorCount hook hit");
// call original function
HRESULT ret = NuiGetSensorCount_orig(pCount);
// check return value
if (FAILED(ret)) {
log_warning("ftt", "NuiGetSensorCount failed, hr={}", FMT_HRESULT(ret));
return ret;
}
if (pCount == nullptr) {
return ret;
}
log_info("ftt", "found {} Kinect sensors", *pCount);
return ret;
}
static HRESULT WINAPI NuiCreateSensorByIndex_hook(int index, INuiSensor **ppNuiSensor) {
log_misc("ftt", "NuiCreateSensorByIndex hook hit");
// call original function
HRESULT ret = NuiCreateSensorByIndex_orig(index, ppNuiSensor);
// check return value
if (FAILED(ret)) {
log_warning("ftt", "NuiCreateSensorByIndex failed, hr={}", FMT_HRESULT(ret));
return ret;
}
if (ppNuiSensor == nullptr) {
return ret;
}
// save original functions
INuiSensor *pNuiSensor = *ppNuiSensor;
if (INuiSensor_NuiInitialize_orig == nullptr) {
INuiSensor_NuiInitialize_orig = pNuiSensor->lpVtbl->NuiInitialize;
}
if (INuiSensor_NuiImageStreamOpen_orig == nullptr) {
INuiSensor_NuiImageStreamOpen_orig = pNuiSensor->lpVtbl->NuiImageStreamOpen;
}
// unlock interface
memutils::VProtectGuard pNuiSensor_guard(pNuiSensor->lpVtbl);
// hook interface
pNuiSensor->lpVtbl->NuiInitialize = INuiSensor_NuiInitialize_hook;
pNuiSensor->lpVtbl->NuiImageStreamOpen = INuiSensor_NuiImageStreamOpen_hook;
return ret;
}
namespace games::ftt {
FTTGame::FTTGame() : Game("FutureTomTom") {
}
void FTTGame::attach() {
Game::attach();
// load main game module so patches work
auto game_dll = libutils::load_library(MODULE_PATH / "gamemmd.dll");
// patch kinect to always poll
if (!replace_pattern(game_dll,
"78??837B340775??488BCBE8",
"????????????9090????????",
0,
0))
{
log_warning("ftt", "kinect polling patch failed");
}
// patch kinect to not time out
if (!replace_pattern(game_dll,
"3D3075000072??C74334060000004883C420",
"??????????????90909090909090????????",
0,
0))
{
log_warning("ftt", "kinect timeout patch failed");
}
// hook Kinect calls
NuiGetSensorCount_orig = (NuiGetSensorCount_t) detour::iat_ordinal(
"Kinect10.dll", 5, (void *) NuiGetSensorCount_hook, game_dll);
NuiCreateSensorByIndex_orig = (NuiCreateSensorByIndex_t) detour::iat_ordinal(
"Kinect10.dll", 6, (void *) NuiCreateSensorByIndex_hook, game_dll);
}
}

12
games/ftt/ftt.h Normal file
View File

@@ -0,0 +1,12 @@
#pragma once
#include "games/game.h"
namespace games::ftt {
class FTTGame : public games::Game {
public:
FTTGame();
virtual void attach() override;
};
}

65
games/ftt/io.cpp Normal file
View File

@@ -0,0 +1,65 @@
#include "io.h"
std::vector<Button> &games::ftt::get_buttons() {
static std::vector<Button> buttons;
if (buttons.empty()) {
buttons = GameAPI::Buttons::getButtons("FutureTomTom");
GameAPI::Buttons::sortButtons(
&buttons,
"Service",
"Test",
"Pad 1",
"Pad 2",
"Pad 3",
"Pad 4"
);
}
return buttons;
}
std::vector<Analog> &games::ftt::get_analogs() {
static std::vector<Analog> analogs;
if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("FutureTomTom");
GameAPI::Analogs::sortAnalogs(
&analogs,
"Pad 1",
"Pad 2",
"Pad 3",
"Pad 4"
);
}
return analogs;
}
std::vector<Light> &games::ftt::get_lights() {
static std::vector<Light> lights;
if (lights.empty()) {
lights = GameAPI::Lights::getLights("FutureTomTom");
GameAPI::Lights::sortLights(
&lights,
"Pad 1 Red",
"Pad 1 Green",
"Pad 1 Blue",
"Pad 2 Red",
"Pad 2 Green",
"Pad 2 Blue",
"Pad 3 Red",
"Pad 3 Green",
"Pad 3 Blue",
"Pad 4 Red",
"Pad 4 Green",
"Pad 4 Blue"
);
}
return lights;
}

52
games/ftt/io.h Normal file
View File

@@ -0,0 +1,52 @@
#pragma once
#include <vector>
#include "cfg/api.h"
namespace games::ftt {
// all buttons in correct order
namespace Buttons {
enum {
Service,
Test,
Pad1,
Pad2,
Pad3,
Pad4,
};
}
// all analogs in correct order
namespace Analogs {
enum {
Pad1,
Pad2,
Pad3,
Pad4,
};
}
// all lights in correct order
namespace Lights {
enum {
Pad1_R,
Pad1_G,
Pad1_B,
Pad2_R,
Pad2_G,
Pad2_B,
Pad3_R,
Pad3_G,
Pad3_B,
Pad4_R,
Pad4_G,
Pad4_B,
};
}
// getters
std::vector<Button> &get_buttons();
std::vector<Analog> &get_analogs();
std::vector<Light> &get_lights();
}

183
games/ftt/nui.h Normal file
View File

@@ -0,0 +1,183 @@
#pragma once
#include <windows.h>
#include <unknwn.h>
enum NUI_IMAGE_TYPE {
NUI_IMAGE_TYPE_DEPTH_AND_PLAYER_INDEX = 0x0,
NUI_IMAGE_TYPE_COLOR = 0x1,
NUI_IMAGE_TYPE_COLOR_YUV = 0x2,
NUI_IMAGE_TYPE_COLOR_RAW_YUV = 0x3,
NUI_IMAGE_TYPE_DEPTH = 0x4,
NUI_IMAGE_TYPE_COLOR_INFRARED = 0x5,
NUI_IMAGE_TYPE_COLOR_RAW_BAYER = 0x6,
};
enum NUI_IMAGE_RESOLUTION {
NUI_IMAGE_RESOLUTION_80x60 = 0x0,
NUI_IMAGE_RESOLUTION_320x240 = 0x1,
NUI_IMAGE_RESOLUTION_640x480 = 0x2,
NUI_IMAGE_RESOLUTION_1280x960 = 0x3,
NUI_IMAGE_RESOLUTION_INVALID = 0xFFFFFFFF,
};
#pragma pack(push, 8)
struct INuiSensor {
struct INuiSensorVtbl *lpVtbl;
};
struct INuiSensorVtbl {
HRESULT (__stdcall *QueryInterface)(IUnknown *This, const IID *const riid, void **ppvObject);
ULONG (__stdcall *AddRef)(IUnknown *This);
ULONG (__stdcall *Release)(IUnknown *This);
HRESULT (__stdcall *NuiInitialize)(
INuiSensor *This,
DWORD dwFlags);
void (__stdcall *NuiShutdown)(INuiSensor *This);
HRESULT (__stdcall *NuiSetFrameEndEvent)(
INuiSensor *This,
HANDLE hEvent,
DWORD dwFrameEventFlag);
HRESULT (__stdcall *NuiImageStreamOpen)(
INuiSensor *This,
NUI_IMAGE_TYPE eImageType,
NUI_IMAGE_RESOLUTION eResolution,
DWORD dwImageFrameFlags,
DWORD dwFrameLimit,
HANDLE hNextFrameEvent,
HANDLE *phStreamHandle);
HRESULT (__stdcall *NuiImageStreamSetImageFrameFlags)(
INuiSensor *This,
HANDLE hStream,
DWORD dwImageFrameFlags);
HRESULT (__stdcall *NuiImageStreamGetImageFrameFlags)(
INuiSensor *This,
HANDLE hStream,
DWORD *pdwImageFrameFlags);
HRESULT (__stdcall *NuiImageStreamGetNextFrame)(
INuiSensor *This,
HANDLE hStream,
DWORD dwMillisecondsToWait,
void *pImageFrame);
HRESULT (__stdcall *NuiImageStreamReleaseFrame)(
INuiSensor *This,
HANDLE hStream,
void *pImageFrame);
HRESULT (__stdcall *NuiImageGetColorPixelCoordinatesFromDepthPixel)(
INuiSensor *This,
DWORD eColorResolution,
const void *pcViewArea,
LONG lDepthX,
LONG lDepthY,
USHORT usDepthValue,
LONG *plColorX,
LONG *plColorY);
HRESULT (__stdcall *NuiImageGetColorPixelCoordinatesFromDepthPixelAtResolution)(
INuiSensor *This,
DWORD eColorResolution,
DWORD eDepthResolution,
const void *pcViewArea,
LONG lDepthX,
LONG lDepthY,
USHORT usDepthValue,
LONG *plColorX,
LONG *plColorY);
HRESULT (__stdcall *NuiImageGetColorPixelCoordinateFrameFromDepthPixelFrameAtResolution)(
INuiSensor *This,
DWORD eColorResolution,
DWORD eDepthResolution,
DWORD cDepthValues,
USHORT *pDepthValues,
DWORD cColorCoordinates,
LONG *pColorCoordinates);
HRESULT (__stdcall *NuiCameraElevationSetAngle)(
INuiSensor *This,
LONG lAngleDegrees);
HRESULT (__stdcall *NuiCameraElevationGetAngle)(
INuiSensor *This,
LONG *plAngleDegrees);
HRESULT (__stdcall *NuiSkeletonTrackingEnable)(
INuiSensor *This,
HANDLE hNextFrameEvent,
DWORD dwFlags);
HRESULT (__stdcall *NuiSkeletonTrackingDisable)(
INuiSensor *This);
HRESULT (__stdcall *NuiSkeletonSetTrackedSkeletons)(
INuiSensor *This,
DWORD *TrackingIDs);
HRESULT (__stdcall *NuiSkeletonGetNextFrame)(
INuiSensor *This,
DWORD dwMillisecondsToWait,
void *pSkeletonFrame);
HRESULT (__stdcall *NuiTransformSmooth)(
INuiSensor *This,
void *pSkeletonFrame,
const void *pSmoothingParams);
HRESULT (__stdcall *NuiGetAudioSource)(
INuiSensor *This,
void **ppDmo);
int (__stdcall *NuiInstanceIndex)(
INuiSensor *This);
BSTR (__stdcall *NuiDeviceConnectionId)(
INuiSensor *This);
BSTR (__stdcall *NuiUniqueId)(
INuiSensor *This);
BSTR (__stdcall *NuiAudioArrayId)(
INuiSensor *This);
HRESULT (__stdcall *NuiStatus)(
INuiSensor *This);
DWORD (__stdcall *NuiInitializationFlags)(
INuiSensor *This);
HRESULT (__stdcall *NuiGetCoordinateMapper)(
INuiSensor *This,
void **pMapping);
HRESULT (__stdcall *NuiImageFrameGetDepthImagePixelFrameTexture)(
INuiSensor *This,
HANDLE hStream,
void *pImageFrame,
BOOL *pNearMode,
void **ppFrameTexture);
HRESULT (__stdcall *NuiGetColorCameraSettings)(
INuiSensor *This,
void **pCameraSettings);
BOOL (__stdcall *NuiGetForceInfraredEmitterOff)(
INuiSensor *This);
HRESULT (__stdcall *NuiSetForceInfraredEmitterOff)(
INuiSensor *This,
BOOL fForceInfraredEmitterOff);
HRESULT (__stdcall *NuiAccelerometerGetCurrentReading)(
INuiSensor *This,
void *pReading);
};
#pragma pack(pop)

22
games/game.cpp Normal file
View File

@@ -0,0 +1,22 @@
#include "game.h"
#include "util/logging.h"
games::Game::Game(std::string name) {
this->name = name;
}
void games::Game::attach() {
log_info("game", "attach: {}", name);
}
void games::Game::pre_attach() {
log_info("game", "pre_attach: {}", name);
}
void games::Game::post_attach() {
log_info("game", "post_attach: {}", name);
}
void games::Game::detach() {
log_info("game", "detach: {}", name);
}

24
games/game.h Normal file
View File

@@ -0,0 +1,24 @@
#pragma once
#include <string>
namespace games {
class Game {
private:
std::string name;
public:
Game(std::string name);
virtual ~Game() = default;
// where the main magic will happen
virtual void attach();
// optional
virtual void pre_attach();
virtual void post_attach();
virtual void detach();
};
}

246
games/gitadora/gitadora.cpp Normal file
View File

@@ -0,0 +1,246 @@
#include "gitadora.h"
#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"
#include "util/sigscan.h"
namespace games::gitadora {
// settings
bool TWOCHANNEL = false;
std::optional<unsigned int> CAB_TYPE = std::nullopt;
/*
* GitaDora checks if the IP address has changed, and if it has it throws 5-1506-0000 like jubeat.
* We don't want this so we patch it out.
*/
static char __cdecl eam_network_detected_ip_change() {
return 0;
}
/*
* GitaDora checks if the server it connects to is in the 192.168.0.0/16 or 169.254.0.0/16 subnet.
* If it is, it downright refuses to use it and errors with no visible indication.
* We don't want this so we patch it out.
*/
static char __cdecl eam_network_settings_conflict() {
return 0;
}
/*
* Prevent GitaDora from changing the volume setting.
*/
static long __cdecl bmsd2_set_windows_volume(int volume) {
return 0;
}
#ifdef SPICE64
/*
* Two Channel Audio Mode
* We proxy bmsd2_boot_hook and modify the last parameter which is apparently the channel count.
* Since this apparently isn't the only thing required we need a signature scan to modify a value as well.
*/
typedef int (__cdecl *bmsd2_boot_t)(long a1, int a2, long a3, char channel_count);
static bmsd2_boot_t bmsd2_boot_orig = nullptr;
static int __cdecl bmsd2_boot_hook(long a1, int a2, long a3, char channel_count) {
return bmsd2_boot_orig(a1, a2, a3, 2);
}
#endif
/*
* Command Line Arguments
* We hook this to override specific values.
* This currently disables the ability to specify your own in the app-config.xml (param/cmdline __type="str")
*/
static bool __cdecl sys_code_get_cmdline(const char *cmdline) {
if (strcmp(cmdline, "-d") == 0) {
return true;
} else if (strcmp(cmdline, "-DM") == 0) {
return true;
} else if (strcmp(cmdline, "-WINDOW") == 0) {
return GRAPHICS_WINDOWED;
} else if (strcmp(cmdline, "-LOGOUT") == 0) {
return false;
} else if (strcmp(cmdline, "-AOU") == 0) {
return false;
} else if (strcmp(cmdline, "-QCMODE") == 0) {
return false;
} else if (strcmp(cmdline, "-FACTORY") == 0) {
return false;
}
return false;
}
/*
* System Setting Parameter Overrides
*/
static std::unordered_map<std::string, long> SYS_SETTINGS;
static std::unordered_map<std::string, long> SYS_DEBUG_DIPS;
static long __cdecl sys_setting_get_param(const char *param) {
// overrides
if (strcmp(param, "PRODUCTION_MODE") == 0) {
return 0;
} else if (strcmp(param, "ENABLE_DISP_ID") == 0) {
return 0;
} else if (CAB_TYPE.has_value() && strcmp(param, "VER_MACHINE") == 0) {
return CAB_TYPE.value() << 12;
}
// map lookup
auto it = SYS_SETTINGS.find(param);
if (it != SYS_SETTINGS.end()) {
return it->second;
}
return -1;
}
static long __cdecl sys_setting_set_param(const char *param, long value) {
SYS_SETTINGS[std::string(param)] = value;
return 1;
}
static long __cdecl sys_debug_dip_get_param(const char *param) {
// overrides
if (strcmp(param, "sysinfo") == 0) {
return 0;
} else if (strcmp(param, "jobbar1") == 0) {
return 0;
} else if (strcmp(param, "jobbar2") == 0) {
return 0;
} else if (strcmp(param, "serial") == 0) {
return 0;
} else if (strcmp(param, "warnvpf") == 0) {
return 0;
} else if (strcmp(param, "scrshot") == 0) {
return 0;
} else if (strcmp(param, "eamxml") == 0) {
return 0;
} else if (strcmp(param, "offset") == 0) {
return 0;
} else if (strcmp(param, "autodbg") == 0) {
return 0;
} else if (strcmp(param, "develop") == 0) {
return 0;
} else if (strcmp(param, "effect_test") == 0) {
return 0;
} else if (strcmp(param, "voice_type2") == 0) {
return 0;
}
// map lookup
auto it = SYS_DEBUG_DIPS.find(param);
if (it != SYS_DEBUG_DIPS.end()) {
return it->second;
}
return -1;
}
static long __cdecl sys_debug_dip_set_param(const char *param, long value) {
SYS_DEBUG_DIPS[std::string(param)] = value;
return 1;
}
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 bmsd2_module = libutils::try_module("libbmsd2.dll");
HMODULE system_module = libutils::try_module("libsystem.dll");
// patches
detour::inline_hook((void *) eam_network_detected_ip_change, libutils::try_proc(
sharepj_module, "eam_network_detected_ip_change"));
detour::inline_hook((void *) eam_network_settings_conflict, libutils::try_proc(
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 *) sys_code_get_cmdline, libutils::try_proc(
system_module, "sys_code_get_cmdline"));
detour::inline_hook((void *) sys_setting_get_param, libutils::try_proc(
system_module, "sys_setting_get_param"));
detour::inline_hook((void *) sys_setting_set_param, libutils::try_proc(
system_module, "sys_setting_set_param"));
detour::inline_hook((void *) sys_debug_dip_get_param, libutils::try_proc(
system_module, "sys_debug_dip_get_param"));
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,
"754185ED753D8B4118BF0000CB02",
"9090????9090??????????????12", 0, 0))
{
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);
if (!(replace_pattern(bmsd_engine_module, "33000000488D", "03??????????", 0, 0) ||
replace_pattern(bmsd_engine_module, "330000000F10", "03??????????", 0, 0)))
{
log_warning("gitadora", "two channel mode failed");
}
}
#endif
}
}

19
games/gitadora/gitadora.h Normal file
View File

@@ -0,0 +1,19 @@
#pragma once
#include <optional>
#include "games/game.h"
namespace games::gitadora {
// settings
extern bool TWOCHANNEL;
extern std::optional<unsigned int> CAB_TYPE;
class GitaDoraGame : public games::Game {
public:
GitaDoraGame();
virtual void pre_attach() override;
virtual void attach() override;
};
}

197
games/gitadora/io.cpp Normal file
View File

@@ -0,0 +1,197 @@
#include "io.h"
std::vector<Button> &games::gitadora::get_buttons() {
static std::vector<Button> buttons;
if (buttons.empty()) {
buttons = GameAPI::Buttons::getButtons("GitaDora");
GameAPI::Buttons::sortButtons(&buttons,
"Service",
"Test",
"Coin",
"Guitar P1 Start",
"Guitar P1 Up",
"Guitar P1 Down",
"Guitar P1 Left",
"Guitar P1 Right",
"Guitar P1 Help",
"Guitar P1 Effect 1",
"Guitar P1 Effect 2",
"Guitar P1 Effect 3",
"Guitar P1 Effect Pedal",
"Guitar P1 Button Extra 1",
"Guitar P1 Button Extra 2",
"Guitar P1 Pick Up",
"Guitar P1 Pick Down",
"Guitar P1 R",
"Guitar P1 G",
"Guitar P1 B",
"Guitar P1 Y",
"Guitar P1 P",
"Guitar P1 Knob Up",
"Guitar P1 Knob Down",
"Guitar P1 Wail Up",
"Guitar P1 Wail Down",
"Guitar P2 Start",
"Guitar P2 Up",
"Guitar P2 Down",
"Guitar P2 Left",
"Guitar P2 Right",
"Guitar P2 Help",
"Guitar P2 Effect 1",
"Guitar P2 Effect 2",
"Guitar P2 Effect 3",
"Guitar P2 Effect Pedal",
"Guitar P2 Button Extra 1",
"Guitar P2 Button Extra 2",
"Guitar P2 Pick Up",
"Guitar P2 Pick Down",
"Guitar P2 R",
"Guitar P2 G",
"Guitar P2 B",
"Guitar P2 Y",
"Guitar P2 P",
"Guitar P2 Knob Up",
"Guitar P2 Knob Down",
"Guitar P2 Wail Up",
"Guitar P2 Wail Down",
"Drum Start",
"Drum Up",
"Drum Down",
"Drum Left",
"Drum Right",
"Drum Help",
"Drum Button Extra 1",
"Drum Button Extra 2",
"Drum Hi-Hat",
"Drum Hi-Hat Closed",
"Drum Hi-Hat Half-Open",
"Drum Snare",
"Drum Hi-Tom",
"Drum Low-Tom",
"Drum Right Cymbal",
"Drum Bass Pedal",
"Drum Left Cymbal",
"Drum Left Pedal",
"Drum Floor Tom"
);
}
return buttons;
}
std::vector<Analog> &games::gitadora::get_analogs() {
static std::vector<Analog> analogs;
if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("GitaDora");
GameAPI::Analogs::sortAnalogs(&analogs,
"Guitar P1 Wail X",
"Guitar P1 Wail Y",
"Guitar P1 Wail Z",
"Guitar P1 Knob",
"Guitar P2 Wail X",
"Guitar P2 Wail Y",
"Guitar P2 Wail Z",
"Guitar P2 Knob"
);
}
return analogs;
}
std::vector<Light> &games::gitadora::get_lights() {
static std::vector<Light> lights;
if (lights.empty()) {
lights = GameAPI::Lights::getLights("GitaDora");
GameAPI::Lights::sortLights(&lights,
"Guitar P1 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)"
);
}
return lights;
}

200
games/gitadora/io.h Normal file
View File

@@ -0,0 +1,200 @@
#pragma once
#include <vector>
#include "cfg/api.h"
namespace games::gitadora {
// all buttons in correct order
namespace Buttons {
enum {
Service,
Test,
Coin,
GuitarP1Start,
GuitarP1Up,
GuitarP1Down,
GuitarP1Left,
GuitarP1Right,
GuitarP1Help,
GuitarP1Effect1,
GuitarP1Effect2,
GuitarP1Effect3,
GuitarP1EffectPedal,
GuitarP1ButtonExtra1,
GuitarP1ButtonExtra2,
GuitarP1PickUp,
GuitarP1PickDown,
GuitarP1R,
GuitarP1G,
GuitarP1B,
GuitarP1Y,
GuitarP1P,
GuitarP1KnobUp,
GuitarP1KnobDown,
GuitarP1WailUp,
GuitarP1WailDown,
GuitarP2Start,
GuitarP2Up,
GuitarP2Down,
GuitarP2Left,
GuitarP2Right,
GuitarP2Help,
GuitarP2Effect1,
GuitarP2Effect2,
GuitarP2Effect3,
GuitarP2EffectPedal,
GuitarP2ButtonExtra1,
GuitarP2ButtonExtra2,
GuitarP2PickUp,
GuitarP2PickDown,
GuitarP2R,
GuitarP2G,
GuitarP2B,
GuitarP2Y,
GuitarP2P,
GuitarP2KnobUp,
GuitarP2KnobDown,
GuitarP2WailUp,
GuitarP2WailDown,
DrumStart,
DrumUp,
DrumDown,
DrumLeft,
DrumRight,
DrumHelp,
DrumButtonExtra1,
DrumButtonExtra2,
DrumHiHat,
DrumHiHatClosed,
DrumHiHatHalfOpen,
DrumSnare,
DrumHiTom,
DrumLowTom,
DrumRightCymbal,
DrumBassPedal,
DrumLeftCymbal,
DrumLeftPedal,
DrumFloorTom
};
}
// all analogs in correct order
namespace Analogs {
enum {
GuitarP1WailX,
GuitarP1WailY,
GuitarP1WailZ,
GuitarP1Knob,
GuitarP2WailX,
GuitarP2WailY,
GuitarP2WailZ,
GuitarP2Knob
};
}
// all lights in correct order
namespace Lights {
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::vector<Analog> &get_analogs();
std::vector<Light> &get_lights();
}

38
games/hpm/hpm.cpp Normal file
View File

@@ -0,0 +1,38 @@
#include "avs/game.h"
#include "hpm.h"
#include "util/detour.h"
#include "util/libutils.h"
#include "util/logging.h"
#include <mmsystem.h>
namespace games::hpm {
BOOL WINAPI SetCurrentDirectoryW_hook(LPCTSTR lpPathName) {
return true;
}
static decltype(mixerSetControlDetails)* mixerSetControlDetails_real = nullptr;
static MMRESULT WINAPI mixerSetControlDetails_hook(HMIXEROBJ hmxobj, LPMIXERCONTROLDETAILS pmxcd, DWORD fdwDetails) {
mixerSetControlDetails_real(hmxobj, pmxcd, fdwDetails);
return MMSYSERR_NOERROR;
}
HPMGame::HPMGame() : Game("HELLO! Pop'n Music") {
}
void HPMGame::attach() {
Game::attach();
HMODULE kernel32_module = libutils::try_module("kernel32.dll");
// patches
detour::inline_hook((void *) SetCurrentDirectoryW_hook, libutils::try_proc(
kernel32_module, "SetCurrentDirectoryW"));
mixerSetControlDetails_real = detour::iat_try("mixerSetControlDetails", mixerSetControlDetails_hook, avs::game::DLL_INSTANCE);
}
}

13
games/hpm/hpm.h Normal file
View File

@@ -0,0 +1,13 @@
#pragma once
#include "games/game.h"
namespace games::hpm {
class HPMGame : public games::Game {
public:
HPMGame();
virtual void attach() override;
};
}

54
games/hpm/io.cpp Normal file
View File

@@ -0,0 +1,54 @@
#include "io.h"
std::vector<Button> &games::hpm::get_buttons() {
static std::vector<Button> buttons;
if (buttons.empty()) {
buttons = GameAPI::Buttons::getButtons("HELLO! Pop'n Music");
GameAPI::Buttons::sortButtons(
&buttons,
"Service",
"Test",
"Coin Mech",
"P1 Start",
"P1 1",
"P1 2",
"P1 3",
"P1 4",
"P2 Start",
"P2 1",
"P2 2",
"P2 3",
"P2 4"
);
}
return buttons;
}
std::vector<Light> &games::hpm::get_lights() {
static std::vector<Light> lights;
if (lights.empty()) {
lights = GameAPI::Lights::getLights("HELLO! Pop'n Music");
GameAPI::Lights::sortLights(
&lights,
"Speaker Red",
"Speaker Orange",
"Speaker Blue",
"P1 Start",
"P1 Red & P2 Green",
"P1 Blue",
"P1 Yellow",
"P1 Green",
"P2 Start",
"P2 Red",
"P2 Blue",
"P2 Yellow"
);
}
return lights;
}

48
games/hpm/io.h Normal file
View File

@@ -0,0 +1,48 @@
#pragma once
#include <vector>
#include "cfg/api.h"
namespace games::hpm {
// all buttons in correct order
namespace Buttons {
enum {
Service,
Test,
CoinMech,
P1_Start,
P1_1,
P1_2,
P1_3,
P1_4,
P2_Start,
P2_1,
P2_2,
P2_3,
P2_4,
};
}
// all lights in correct order
namespace Lights {
enum {
SPEAKER_RED,
SPEAKER_ORANGE,
SPEAKER_BLUE,
P1_START,
P1_RED_P2_GREEN,
P1_BLUE,
P1_YELLOW,
P1_GREEN,
P2_START,
P2_RED,
P2_BLUE,
P2_YELLOW,
};
}
// getters
std::vector<Button> &get_buttons();
std::vector<Light> &get_lights();
}

246
games/iidx/bi2a.cpp Normal file
View File

@@ -0,0 +1,246 @@
#include "bi2a.h"
#include "misc/eamuse.h"
#include "rawinput/rawinput.h"
#include "util/utils.h"
#include "iidx.h"
#include "io.h"
using namespace acioemu;
games::iidx::IIDXFMSerialHandle::FMSerialDevice::FMSerialDevice() {
this->node_count = 1;
}
bool games::iidx::IIDXFMSerialHandle::FMSerialDevice::parse_msg(
MessageData *msg_in,
circular_buffer<uint8_t> *response_buffer
) {
#ifdef ACIOEMU_LOG
log_info("iidx", "BI2A ADDR: {}, CMD: 0x{:04x}", msg_in->addr, msg_in->cmd.code);
#endif
// check command
switch (msg_in->cmd.code) {
case ACIO_CMD_GET_VERSION: {
// send version data
auto msg = this->create_msg(msg_in, MSG_VERSION_SIZE);
this->set_version(msg, 0x03, 0, 4, 2, 0, "BI2A");
write_msg(msg, response_buffer);
delete msg;
break;
}
case 0x0153:
if (DISABLE_ESPEC_IO) {
return false;
}
[[fallthrough]]; // to 0x0152
case 0x0152: { // STATUS
// check input data length
if (msg_in->cmd.data_size == 0x30) {
// LED TICKER
IIDX_LED_TICKER_LOCK.lock();
if (!IIDXIO_LED_TICKER_READONLY)
memcpy(IIDXIO_LED_TICKER, &msg_in->cmd.raw[0x17], 9);
IIDX_LED_TICKER_LOCK.unlock();
// NEON LIGHT
bool neon_light = msg_in->cmd.raw[0x24] != 0;
write_top_neon(neon_light ? (uint8_t) 0xFF : (uint8_t) 0);
// SPOT LIGHT
uint8_t spot_light_bits = 0;
for (size_t i = 0; i < 8; i++) {
auto index = i > 3 ? i + 1 : i;
if (msg_in->cmd.raw[0x20 + index] != 0)
spot_light_bits |= 1 << i;
}
write_top_lamp(spot_light_bits);
// SWLED (DECK)
uint16_t lamp_bits = 0;
for (size_t i = 0; i < 14; i++) {
if (msg_in->cmd.raw[0x07 + i] != 0)
lamp_bits |= 1 << i;
}
write_lamp(lamp_bits);
// SWLED (PANEL)
uint8_t led_bits = 0;
for (size_t i = 0; i < 4; i++) {
if (msg_in->cmd.raw[0x03 + i] != 0)
led_bits |= 1 << i;
}
write_led(led_bits);
// flush device output
RI_MGR->devices_flush_output();
}
// sleep - otherwise the IO thread will go too hard on the CPU
Sleep(1);
// generate message
auto msg = this->create_msg(msg_in, 0x2E);
// get buttons
auto &buttons = get_buttons();
// player 1 buttons
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_1)))
ARRAY_SETB(msg->cmd.raw, 151);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_2)))
ARRAY_SETB(msg->cmd.raw, 167);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_3)))
ARRAY_SETB(msg->cmd.raw, 183);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_4)))
ARRAY_SETB(msg->cmd.raw, 199);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_5)))
ARRAY_SETB(msg->cmd.raw, 215);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_6)))
ARRAY_SETB(msg->cmd.raw, 231);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_7)))
ARRAY_SETB(msg->cmd.raw, 247);
// player 2 buttons
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_1)))
ARRAY_SETB(msg->cmd.raw, 263);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_2)))
ARRAY_SETB(msg->cmd.raw, 279);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_3)))
ARRAY_SETB(msg->cmd.raw, 295);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_4)))
ARRAY_SETB(msg->cmd.raw, 311);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_5)))
ARRAY_SETB(msg->cmd.raw, 327);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_6)))
ARRAY_SETB(msg->cmd.raw, 343);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_7)))
ARRAY_SETB(msg->cmd.raw, 359);
// player 1 start
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_Start)))
ARRAY_SETB(msg->cmd.raw, 79);
// player 2 start
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_Start)))
ARRAY_SETB(msg->cmd.raw, 78);
// VEFX
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::VEFX)))
ARRAY_SETB(msg->cmd.raw, 77);
// EFFECT
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::Effect)))
ARRAY_SETB(msg->cmd.raw, 76);
// service
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::Service)))
ARRAY_SETB(msg->cmd.raw, 10);
// test
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::Test)))
ARRAY_SETB(msg->cmd.raw, 11);
// turntables
msg->cmd.raw[16] = get_tt(0, true);
msg->cmd.raw[17] = get_tt(1, true);
// slider
msg->cmd.raw[0] |= get_slider(0) << 4;
msg->cmd.raw[2] |= get_slider(1) << 4;
msg->cmd.raw[4] |= get_slider(2) << 4;
msg->cmd.raw[6] |= get_slider(3) << 4;
msg->cmd.raw[7] |= get_slider(4) << 4;
// coin
this->coin_counter += eamuse_coin_consume_stock();
msg->cmd.raw[8] |= this->coin_counter;
/*
* TODO
* bit 9 appears to be the coin mech
*/
// write message
write_msg(msg, response_buffer);
delete msg;
break;
}
case ACIO_CMD_CLEAR:
case ACIO_CMD_STARTUP:
case 0x0120: // WD COMMAND
case 0xFF: // BROADCAST
{
// send status 0
auto msg = this->create_msg_status(msg_in, 0);
write_msg(msg, response_buffer);
delete msg;
break;
}
default:
return false;
}
// mark as handled
return true;
}
bool games::iidx::IIDXFMSerialHandle::open(LPCWSTR lpFileName) {
if (wcscmp(lpFileName, L"COM2") != 0) {
return false;
}
log_info("iidx", "Opened COM2 (FM DEVICE)");
// ACIO device
acio_emu.add_device(new FMSerialDevice());
return true;
}
int games::iidx::IIDXFMSerialHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) {
auto buffer = reinterpret_cast<uint8_t *>(lpBuffer);
// read from emu
DWORD bytes_read = 0;
while (bytes_read < nNumberOfBytesToRead) {
auto cur_byte = acio_emu.read();
if (cur_byte.has_value()) {
buffer[bytes_read++] = cur_byte.value();
} else {
break;
}
}
// return amount of bytes read
return (int) bytes_read;
}
int games::iidx::IIDXFMSerialHandle::write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) {
auto buffer = reinterpret_cast<const uint8_t *>(lpBuffer);
// write to emu
for (DWORD i = 0; i < nNumberOfBytesToWrite; i++) {
acio_emu.write(buffer[i]);
}
// return all data written
return (int) nNumberOfBytesToWrite;
}
int games::iidx::IIDXFMSerialHandle::device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize,
LPVOID lpOutBuffer, DWORD nOutBufferSize) {
return -1;
}
bool games::iidx::IIDXFMSerialHandle::close() {
log_info("iidx", "Closed COM2 (FM DEVICE)");
return true;
}

30
games/iidx/bi2a.h Normal file
View File

@@ -0,0 +1,30 @@
#pragma once
#include "acioemu/acioemu.h"
#include "hooks/devicehook.h"
namespace games::iidx {
class IIDXFMSerialHandle : public CustomHandle {
private:
acioemu::ACIOEmu acio_emu;
class FMSerialDevice : public acioemu::ACIODeviceEmu {
private:
uint8_t coin_counter = 0;
public:
FMSerialDevice();
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;
};
}

253
games/iidx/bi2x.cpp Normal file
View File

@@ -0,0 +1,253 @@
#include "bi2x.h"
#include "misc/eamuse.h"
#include "rawinput/rawinput.h"
#include "util/utils.h"
#include "iidx.h"
#include "io.h"
using namespace acioemu;
games::iidx::BI2XSerialHandle::BI2XDevice::BI2XDevice() {
this->node_count = 1;
}
bool games::iidx::BI2XSerialHandle::BI2XDevice::parse_msg(
MessageData *msg_in,
circular_buffer<uint8_t> *response_buffer
) {
#ifdef ACIOEMU_LOG
log_info("iidx", "BI2X ADDR: {}, CMD: 0x{:04x}", msg_in->addr, msg_in->cmd.code);
#endif
// check command
switch (msg_in->cmd.code) {
case ACIO_CMD_GET_VERSION: {
// send version data
auto msg = this->create_msg(msg_in, MSG_VERSION_SIZE);
this->set_version(msg, 0x03, 0, 4, 2, 0, "BI2X");
write_msg(msg, response_buffer);
delete msg;
break;
}
case 0x0153:
if (DISABLE_ESPEC_IO) {
return false;
}
[[fallthrough]]; // to 0x0152
case 0x0152: { // STATUS
// check input data length
if (msg_in->cmd.data_size == 0x30) {
// LED TICKER
IIDX_LED_TICKER_LOCK.lock();
if (!IIDXIO_LED_TICKER_READONLY)
memcpy(IIDXIO_LED_TICKER, &msg_in->cmd.raw[0x17], 9);
IIDX_LED_TICKER_LOCK.unlock();
// NEON LIGHT
bool neon_light = msg_in->cmd.raw[0x24] != 0;
write_top_neon(neon_light ? (uint8_t) 0xFF : (uint8_t) 0);
// SPOT LIGHT
uint8_t spot_light_bits = 0;
for (size_t i = 0; i < 8; i++) {
auto index = i > 3 ? i + 1 : i;
if (msg_in->cmd.raw[0x20 + index] != 0)
spot_light_bits |= 1 << i;
}
write_top_lamp(spot_light_bits);
// SWLED (DECK)
uint16_t lamp_bits = 0;
for (size_t i = 0; i < 14; i++) {
if (msg_in->cmd.raw[0x07 + i] != 0)
lamp_bits |= 1 << i;
}
write_lamp(lamp_bits);
// SWLED (PANEL)
uint8_t led_bits = 0;
for (size_t i = 0; i < 4; i++) {
if (msg_in->cmd.raw[0x03 + i] != 0)
led_bits |= 1 << i;
}
write_led(led_bits);
// flush device output
RI_MGR->devices_flush_output();
}
// sleep - otherwise the IO thread will go too hard on the CPU
Sleep(1);
// generate message
auto msg = this->create_msg(msg_in, 0x2E);
// get buttons
auto &buttons = get_buttons();
// player 1 buttons
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_1)))
ARRAY_SETB(msg->cmd.raw, 151);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_2)))
ARRAY_SETB(msg->cmd.raw, 167);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_3)))
ARRAY_SETB(msg->cmd.raw, 183);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_4)))
ARRAY_SETB(msg->cmd.raw, 199);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_5)))
ARRAY_SETB(msg->cmd.raw, 215);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_6)))
ARRAY_SETB(msg->cmd.raw, 231);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_7)))
ARRAY_SETB(msg->cmd.raw, 247);
// player 2 buttons
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_1)))
ARRAY_SETB(msg->cmd.raw, 263);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_2)))
ARRAY_SETB(msg->cmd.raw, 279);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_3)))
ARRAY_SETB(msg->cmd.raw, 295);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_4)))
ARRAY_SETB(msg->cmd.raw, 311);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_5)))
ARRAY_SETB(msg->cmd.raw, 327);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_6)))
ARRAY_SETB(msg->cmd.raw, 343);
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_7)))
ARRAY_SETB(msg->cmd.raw, 359);
// player 1 start
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_Start)))
ARRAY_SETB(msg->cmd.raw, 79);
// player 2 start
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_Start)))
ARRAY_SETB(msg->cmd.raw, 78);
// VEFX
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::VEFX)))
ARRAY_SETB(msg->cmd.raw, 77);
// EFFECT
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::Effect)))
ARRAY_SETB(msg->cmd.raw, 76);
// service
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::Service)))
ARRAY_SETB(msg->cmd.raw, 10);
// test
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::Test)))
ARRAY_SETB(msg->cmd.raw, 11);
// turntables
msg->cmd.raw[16] = get_tt(0, true);
msg->cmd.raw[17] = get_tt(1, true);
// slider
msg->cmd.raw[0] |= get_slider(0) << 4;
msg->cmd.raw[2] |= get_slider(1) << 4;
msg->cmd.raw[4] |= get_slider(2) << 4;
msg->cmd.raw[6] |= get_slider(3) << 4;
msg->cmd.raw[7] |= get_slider(4) << 4;
// coin
this->coin_counter += eamuse_coin_consume_stock();
msg->cmd.raw[8] |= this->coin_counter;
/*
* TODO
* bit 9 appears to be the coin mech
*/
// write message
write_msg(msg, response_buffer);
delete msg;
break;
}
case ACIO_CMD_CLEAR:
case ACIO_CMD_STARTUP:
case 0x0120: // WD COMMAND
case 0xFF: // BROADCAST
{
// send status 0
auto msg = this->create_msg_status(msg_in, 0);
write_msg(msg, response_buffer);
delete msg;
break;
}
default:
return false;
}
// mark as handled
return true;
}
bool games::iidx::BI2XSerialHandle::open(LPCWSTR lpFileName) {
if (wcscmp(lpFileName, L"COM3") != 0) {
return false;
}
log_info("iidx", "Opened COM3 (BIO2)");
// ACIO device
//acio_emu.add_device(new FMSerialDevice());
return true;
}
int games::iidx::BI2XSerialHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) {
//auto buffer = reinterpret_cast<uint8_t *>(lpBuffer);
// read from emu
/*
DWORD bytes_read = 0;
while (bytes_read < nNumberOfBytesToRead) {
auto cur_byte = acio_emu.read();
if (cur_byte.has_value()) {
buffer[bytes_read++] = cur_byte.value();
} else {
break;
}
}
*/
// return amount of bytes read
//return (int) bytes_read;
return (int) nNumberOfBytesToRead;
}
int games::iidx::BI2XSerialHandle::write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) {
auto buffer = reinterpret_cast<const uint8_t *>(lpBuffer);
log_info("iidx", "BIO2 Data: {}", bin2hex(buffer, nNumberOfBytesToWrite));
// write to emu
/*
for (DWORD i = 0; i < nNumberOfBytesToWrite; i++) {
acio_emu.write(buffer[i]);
}
*/
// return all data written
return (int) nNumberOfBytesToWrite;
}
int games::iidx::BI2XSerialHandle::device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize,
LPVOID lpOutBuffer, DWORD nOutBufferSize) {
return -1;
}
bool games::iidx::BI2XSerialHandle::close() {
log_info("iidx", "Closed COM3 (BIO2)");
return true;
}

30
games/iidx/bi2x.h Normal file
View File

@@ -0,0 +1,30 @@
#pragma once
#include "acioemu/acioemu.h"
#include "hooks/devicehook.h"
namespace games::iidx {
class BI2XSerialHandle : public CustomHandle {
private:
acioemu::ACIOEmu acio_emu;
class BI2XDevice : public acioemu::ACIODeviceEmu {
private:
uint8_t coin_counter = 0;
public:
BI2XDevice();
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;
};
}

547
games/iidx/bi2x_hook.cpp Normal file
View File

@@ -0,0 +1,547 @@
#include "bi2x_hook.h"
#include <cstdint>
#include "util/detour.h"
#include "util/logging.h"
#include "util/utils.h"
#include "rawinput/rawinput.h"
#include "misc/eamuse.h"
#include "games/io.h"
#include "launcher/options.h"
#include "io.h"
#include "iidx.h"
#include "util/tapeled.h"
namespace games::iidx {
constexpr bool BI2X_PASSTHROUGH = false;
/*
* class definitions
*/
struct AC_HNDLIF {
// dummy
uint8_t data[0x10];
};
struct AIO_NMGR_IOB2 {
uint8_t dummy0[0x50];
void (__fastcall *pAIO_NMGR_IOB_BeginManage)(int64_t a1);
uint8_t dummy1[0x9A0];
};
struct AIO_IOB2_BI2X_TDJ {
// who knows
uint8_t data[0x13F8];
};
struct AIO_IOB2_BI2X_TDJ__DEVSTATUS {
// of course you could work with variables here
uint8_t buffer[0xCA];
};
/*
* typedefs
*/
// libaio-iob2_video.dll
typedef AIO_IOB2_BI2X_TDJ* (__fastcall *aioIob2Bi2xTDJ_Create_t)(AIO_NMGR_IOB2 *nmgr, int a2);
typedef void (__fastcall *AIO_IOB2_BI2X_TDJ__GetDeviceStatus_t)(AIO_IOB2_BI2X_TDJ *This,
AIO_IOB2_BI2X_TDJ__DEVSTATUS *a2);
typedef void (__fastcall *AIO_IOB2_BI2X_TDJ__IoReset_t)(AIO_IOB2_BI2X_TDJ *This,
unsigned int a2);
typedef void (__fastcall *AIO_IOB2_BI2X_TDJ__SetWatchDogTimer_t)(AIO_IOB2_BI2X_TDJ *This,
uint8_t a2);
typedef void (__fastcall *AIO_IOB2_BI2X_TDJ__ControlCoinBlocker_t)(AIO_IOB2_BI2X_TDJ *This,
int index, uint8_t state);
typedef void (__fastcall *AIO_IOB2_BI2X_TDJ__AddCounter_t)(AIO_IOB2_BI2X_TDJ *This,
int a2, int a3);
typedef void (__fastcall *AIO_IOB2_BI2X_TDJ__SetStartLamp_t)(AIO_IOB2_BI2X_TDJ *This,
int player, uint8_t state);
typedef void (__fastcall *AIO_IOB2_BI2X_TDJ__SetVefxButtonLamp_t)(AIO_IOB2_BI2X_TDJ *This,
uint8_t state);
typedef void (__fastcall *AIO_IOB2_BI2X_TDJ__SetEffectButtonLamp_t)(AIO_IOB2_BI2X_TDJ *This,
uint8_t state);
typedef void (__fastcall *AIO_IOB2_BI2X_TDJ__SetPlayerButtonLamp_t)(AIO_IOB2_BI2X_TDJ *This,
int player, int button, uint8_t state);
typedef void (__fastcall *AIO_IOB2_BI2X_TDJ__SetWooferLED_t)(AIO_IOB2_BI2X_TDJ *This,
unsigned int index, uint32_t color);
typedef void (__fastcall *AIO_IOB2_BI2X_TDJ__SetIccrLed_t)(AIO_IOB2_BI2X_TDJ *This,
unsigned int index, uint32_t color);
typedef void (__fastcall *AIO_IOB2_BI2X_TDJ__SetTurnTableLed_t)(AIO_IOB2_BI2X_TDJ *This,
unsigned int index, uint32_t color);
typedef void (__fastcall *AIO_IOB2_BI2X_TDJ__SetTurnTableResist_t)(AIO_IOB2_BI2X_TDJ *This,
unsigned int index, uint8_t resistance);
typedef void (__fastcall *AIO_IOB2_BI2X_TDJ__SetTapeLedData_t)(AIO_IOB2_BI2X_TDJ *This,
unsigned int index, uint8_t *data);
// libaio-iob.dll
typedef AC_HNDLIF* (__fastcall *aioIob2Bi2x_OpenSciUsbCdc_t)(uint8_t device_num);
typedef int64_t (__fastcall *aioIob2Bi2x_WriteFirmGetState_t)(int64_t a1);
typedef AIO_NMGR_IOB2** (__fastcall *aioNMgrIob2_Create_t)(AC_HNDLIF *a1, unsigned int a2);
/*
* function pointers
*/
// libaio-iob2_video.dll
static aioIob2Bi2xTDJ_Create_t aioIob2Bi2xTDJ_Create_orig = nullptr;
static AIO_IOB2_BI2X_TDJ__GetDeviceStatus_t AIO_IOB2_BI2X_TDJ__GetDeviceStatus_orig = nullptr;
static AIO_IOB2_BI2X_TDJ__IoReset_t AIO_IOB2_BI2X_TDJ__IoReset_orig = nullptr;
static AIO_IOB2_BI2X_TDJ__SetWatchDogTimer_t AIO_IOB2_BI2X_TDJ__SetWatchDogTimer_orig = nullptr;
static AIO_IOB2_BI2X_TDJ__ControlCoinBlocker_t AIO_IOB2_BI2X_TDJ__ControlCoinBlocker_orig = nullptr;
static AIO_IOB2_BI2X_TDJ__AddCounter_t AIO_IOB2_BI2X_TDJ__AddCounter_orig = nullptr;
static AIO_IOB2_BI2X_TDJ__SetStartLamp_t AIO_IOB2_BI2X_TDJ__SetStartLamp_orig = nullptr;
static AIO_IOB2_BI2X_TDJ__SetVefxButtonLamp_t AIO_IOB2_BI2X_TDJ__SetVefxButtonLamp_orig = nullptr;
static AIO_IOB2_BI2X_TDJ__SetEffectButtonLamp_t AIO_IOB2_BI2X_TDJ__SetEffectButtonLamp_orig = nullptr;
static AIO_IOB2_BI2X_TDJ__SetPlayerButtonLamp_t AIO_IOB2_BI2X_TDJ__SetPlayerButtonLamp_orig = nullptr;
static AIO_IOB2_BI2X_TDJ__SetWooferLED_t AIO_IOB2_BI2X_TDJ__SetWooferLED_orig = nullptr;
static AIO_IOB2_BI2X_TDJ__SetIccrLed_t AIO_IOB2_BI2X_TDJ__SetIccrLed_orig = nullptr;
static AIO_IOB2_BI2X_TDJ__SetTurnTableLed_t AIO_IOB2_BI2X_TDJ__SetTurnTableLed_orig = nullptr;
static AIO_IOB2_BI2X_TDJ__SetTurnTableResist_t AIO_IOB2_BI2X_TDJ__SetTurnTableResist_orig = nullptr;
static AIO_IOB2_BI2X_TDJ__SetTapeLedData_t AIO_IOB2_BI2X_TDJ__SetTapeLedData_orig = nullptr;
// libaio-iob.dll
static aioIob2Bi2x_OpenSciUsbCdc_t aioIob2Bi2x_OpenSciUsbCdc_orig = nullptr;
static aioIob2Bi2x_WriteFirmGetState_t aioIob2Bi2x_WriteFirmGetState_orig = nullptr;
static aioNMgrIob2_Create_t aioNMgrIob2_Create_orig = nullptr;
/*
* variables
*/
AIO_IOB2_BI2X_TDJ *custom_node = nullptr;
AC_HNDLIF *acHndlif = nullptr;
AIO_NMGR_IOB2 *aioNmgrIob2 = nullptr;
/*
* implementations
*/
static AIO_IOB2_BI2X_TDJ* __fastcall aioIob2Bi2xTDJ_Create(AIO_NMGR_IOB2 *nmgr, int a2) {
if (!BI2X_PASSTHROUGH) {
custom_node = new AIO_IOB2_BI2X_TDJ;
memset(&custom_node->data, 0, sizeof(custom_node->data));
return custom_node;
} else {
// call original
return aioIob2Bi2xTDJ_Create_orig(nmgr, a2);
}
}
static void __fastcall AIO_IOB2_BI2X_TDJ__GetDeviceStatus(AIO_IOB2_BI2X_TDJ *This,
AIO_IOB2_BI2X_TDJ__DEVSTATUS *status) {
// flush raw input
RI_MGR->devices_flush_output();
// check handle
if (This == custom_node) {
// clear input data
memset(status, 0x00, sizeof(AIO_IOB2_BI2X_TDJ__DEVSTATUS));
} else {
// get data from real device
AIO_IOB2_BI2X_TDJ__GetDeviceStatus_orig(This, status);
}
// get buttons
auto &buttons = get_buttons();
// control buttons
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test]))
status->buffer[4] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service]))
status->buffer[5] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::CoinMech]))
status->buffer[6] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VEFX]))
status->buffer[10] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Effect]))
status->buffer[11] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P1_Headphone]))
status->buffer[12] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P2_Headphone]))
status->buffer[13] = 0xFF;
// coin stock
status->buffer[22] += eamuse_coin_get_stock();
// player 1 buttons
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P1_Start]))
status->buffer[8] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P1_1]))
status->buffer[27] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P1_2]))
status->buffer[28] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P1_3]))
status->buffer[29] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P1_4]))
status->buffer[30] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P1_5]))
status->buffer[31] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P1_6]))
status->buffer[32] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P1_7]))
status->buffer[33] = 0xFF;
// player 2 buttons
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P2_Start]))
status->buffer[9] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P2_1]))
status->buffer[34] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P2_2]))
status->buffer[35] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P2_3]))
status->buffer[36] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P2_4]))
status->buffer[37] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P2_5]))
status->buffer[38] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P2_6]))
status->buffer[39] = 0xFF;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::P2_7]))
status->buffer[40] = 0xFF;
// turntables
status->buffer[20] += get_tt(0, false);
status->buffer[21] += get_tt(1, false);
return;
}
static void __fastcall AIO_IOB2_BI2X_TDJ__IoReset(AIO_IOB2_BI2X_TDJ *This,
unsigned int a2)
{
if (This != custom_node) {
return AIO_IOB2_BI2X_TDJ__IoReset_orig(This, a2);
}
}
static void __fastcall AIO_IOB2_BI2X_TDJ__SetWatchDogTimer(AIO_IOB2_BI2X_TDJ *This,
uint8_t a2)
{
if (This != custom_node) {
// comment this out if you want to disable the BI2X watchdog timer
return AIO_IOB2_BI2X_TDJ__SetWatchDogTimer_orig(This, a2);
}
}
static void __fastcall AIO_IOB2_BI2X_TDJ__ControlCoinBlocker(AIO_IOB2_BI2X_TDJ *This,
int index, uint8_t state) {
// coin blocker is closed when state is zero
eamuse_coin_set_block(state == 0);
if (This != custom_node) {
return AIO_IOB2_BI2X_TDJ__ControlCoinBlocker_orig(This, index, state);
}
}
static void __fastcall AIO_IOB2_BI2X_TDJ__AddCounter(AIO_IOB2_BI2X_TDJ *This,
int a2, int a3)
{
if (This != custom_node) {
return AIO_IOB2_BI2X_TDJ__AddCounter_orig(This, a2, a3);
}
}
static void __fastcall AIO_IOB2_BI2X_TDJ__SetStartLamp(AIO_IOB2_BI2X_TDJ *This,
int player, uint8_t state)
{
auto &lights = get_lights();
if (player == 0) {
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::P1_Start], state ? 1.f : 0.f);
} else if (player == 1) {
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::P2_Start], state ? 1.f : 0.f);
}
if (This != custom_node) {
return AIO_IOB2_BI2X_TDJ__SetStartLamp_orig(This, player, state);
}
}
static void __fastcall AIO_IOB2_BI2X_TDJ__SetVefxButtonLamp(AIO_IOB2_BI2X_TDJ *This,
uint8_t state)
{
auto &lights = get_lights();
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::VEFX], state ? 1.f : 0.f);
if (This != custom_node) {
return AIO_IOB2_BI2X_TDJ__SetVefxButtonLamp_orig(This, state);
}
}
static void __fastcall AIO_IOB2_BI2X_TDJ__SetEffectButtonLamp(AIO_IOB2_BI2X_TDJ *This,
uint8_t state)
{
auto &lights = get_lights();
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::Effect], state ? 1.f : 0.f);
if (This != custom_node) {
return AIO_IOB2_BI2X_TDJ__SetEffectButtonLamp_orig(This, state);
}
}
static void __fastcall AIO_IOB2_BI2X_TDJ__SetPlayerButtonLamp(AIO_IOB2_BI2X_TDJ *This,
int player, int button, uint8_t state)
{
auto &lights = get_lights();
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::P1_1 + player * 7 + button], state ? 1.f : 0.f);
if (This != custom_node) {
return AIO_IOB2_BI2X_TDJ__SetPlayerButtonLamp_orig(This, player, button, state);
}
}
static void __fastcall AIO_IOB2_BI2X_TDJ__SetWooferLED(AIO_IOB2_BI2X_TDJ *This,
unsigned int index, uint32_t color)
{
uint32_t col_r = (color & 0xFF0000) >> 16;
uint32_t col_g = (color & 0x00FF00) >> 8;
uint32_t col_b = (color & 0x0000FF) >> 0;
auto &lights = get_lights();
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::WooferR], col_r / 255.f);
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::WooferG], col_g / 255.f);
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::WooferB], col_b / 255.f);
if (This != custom_node) {
return AIO_IOB2_BI2X_TDJ__SetWooferLED_orig(This, index, color);
}
}
static void __fastcall AIO_IOB2_BI2X_TDJ__SetIccrLed(AIO_IOB2_BI2X_TDJ *This,
unsigned int index, uint32_t color)
{
uint32_t col_r = (color & 0xFF0000) >> 16;
uint32_t col_g = (color & 0x00FF00) >> 8;
uint32_t col_b = (color & 0x0000FF) >> 0;
auto &lights = get_lights();
if (index == 0) {
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::ICCR_P1_R], col_r / 255.f);
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::ICCR_P1_G], col_g / 255.f);
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::ICCR_P1_B], col_b / 255.f);
} else if (index == 1) {
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::ICCR_P2_R], col_r / 255.f);
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::ICCR_P2_G], col_g / 255.f);
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::ICCR_P2_B], col_b / 255.f);
}
if (This != custom_node) {
return AIO_IOB2_BI2X_TDJ__SetIccrLed_orig(This, index, color);
}
}
static void __fastcall AIO_IOB2_BI2X_TDJ__SetTurnTableLed(AIO_IOB2_BI2X_TDJ *This,
unsigned int index, uint32_t color)
{
uint32_t col_r = (color & 0xFF0000) >> 16;
uint32_t col_g = (color & 0x00FF00) >> 8;
uint32_t col_b = (color & 0x0000FF) >> 0;
auto &lights = get_lights();
if (index == 0) {
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::TT_P1_R], col_r / 255.f);
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::TT_P1_G], col_g / 255.f);
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::TT_P1_B], col_b / 255.f);
} else if (index == 1) {
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::TT_P2_R], col_r / 255.f);
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::TT_P2_G], col_g / 255.f);
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::TT_P2_B], col_b / 255.f);
}
if (This != custom_node) {
return AIO_IOB2_BI2X_TDJ__SetTurnTableLed_orig(This, index, color);
}
}
static void __fastcall AIO_IOB2_BI2X_TDJ__SetTurnTableResist(AIO_IOB2_BI2X_TDJ *This,
unsigned int index, uint8_t resistance)
{
auto &lights = get_lights();
if (index == 0) {
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::TT_P1_Resistance], resistance / 255.f);
} else if (index == 1) {
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::TT_P2_Resistance], resistance / 255.f);
}
if (This != custom_node) {
return AIO_IOB2_BI2X_TDJ__SetTurnTableResist_orig(This, index, resistance);
}
}
static void __fastcall AIO_IOB2_BI2X_TDJ__SetTapeLedData(AIO_IOB2_BI2X_TDJ *This,
unsigned int index, uint8_t *data)
{
/*
* index mapping
*
* 0 - stage left - 57 bytes - 19 colors
* 1 - stage right - 57 bytes - 19 colors
* 2 - cabinet left - 135 bytes - 45 colors
* 3 - cabinet right - 135 bytes - 45 colors
* 4 - control panel under - 63 bytes - 21 colors
* 5 - ceiling left - 162 bytes - 54 colors
* 6 - title left - 33 bytes - 11 colors
* 7 - title right - 33 bytes - 11 colors
* 8 - ceiling right - 162 bytes - 54 colors
* 9 - touch panel left - 51 bytes - 17 colors
* 10 - touch panel right - 51 bytes - 17 colors
* 11 - side panel left inner - 204 bytes - 68 colors
* 12 - side panel left outer - 204 bytes - 68 colors
* 13 - side panel left - 183 bytes - 61 colors
* 14 - side panel right outer - 204 bytes - 68 colors
* 15 - side panel right inner - 204 bytes - 68 colors
* 16 - side panel right - 183 bytes - 61 colors
*
* data is stored in RGB order, 3 bytes per color
*
* TODO: expose this data via API
*/
// data mapping
static struct TapeLedMapping {
size_t data_size;
int index_r, index_g, index_b;
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) {}
} mapping[] = {
{ 19, Lights::StageLeftAvgR, Lights::StageLeftAvgG, Lights::StageLeftAvgB },
{ 19, Lights::StageRightAvgR, Lights::StageRightAvgG, Lights::StageRightAvgB },
{ 45, Lights::CabinetLeftAvgR, Lights::CabinetLeftAvgG, Lights::CabinetLeftAvgB },
{ 45, Lights::CabinetRightAvgR, Lights::CabinetRightAvgG, Lights::CabinetRightAvgB },
{ 21, Lights::ControlPanelUnderAvgR, Lights::ControlPanelUnderAvgG, Lights::ControlPanelUnderAvgB },
{ 54, Lights::CeilingLeftAvgR, Lights::CeilingLeftAvgG, Lights::CeilingLeftAvgB },
{ 11, Lights::TitleLeftAvgR, Lights::TitleLeftAvgG, Lights::TitleLeftAvgB },
{ 11, Lights::TitleRightAvgR, Lights::TitleRightAvgG, Lights::TitleRightAvgB },
{ 54, Lights::CeilingRightAvgR, Lights::CeilingRightAvgG, Lights::CeilingRightAvgB },
{ 17, Lights::TouchPanelLeftAvgR, Lights::TouchPanelLeftAvgG, Lights::TouchPanelLeftAvgB },
{ 17, Lights::TouchPanelRightAvgR, Lights::TouchPanelRightAvgG, Lights::TouchPanelRightAvgB },
{ 68, Lights::SidePanelLeftInnerAvgR, Lights::SidePanelLeftInnerAvgG, Lights::SidePanelLeftInnerAvgB },
{ 68, Lights::SidePanelLeftOuterAvgR, Lights::SidePanelLeftOuterAvgG, Lights::SidePanelLeftOuterAvgB },
{ 61, Lights::SidePanelLeftAvgR, Lights::SidePanelLeftAvgG, Lights::SidePanelLeftAvgB },
{ 68, Lights::SidePanelRightOuterAvgR, Lights::SidePanelRightOuterAvgG, Lights::SidePanelRightOuterAvgB },
{ 68, Lights::SidePanelRightInnerAvgR, Lights::SidePanelRightInnerAvgG, Lights::SidePanelRightInnerAvgB },
{ 61, Lights::SidePanelRightAvgR, Lights::SidePanelRightAvgG, Lights::SidePanelRightAvgB },
};
// check index bounds
if (tapeledutils::is_enabled() && index < std::size(mapping)) {
auto &map = mapping[index];
// pick a color to use
const auto rgb = tapeledutils::pick_color_from_led_tape(data, map.data_size);
// program the lights into API
auto &lights = get_lights();
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);
}
}
static AC_HNDLIF* __fastcall aioIob2Bi2X_OpenSciUsbCdc(uint8_t device_num) {
if (acHndlif == nullptr) {
acHndlif = new AC_HNDLIF;
memset(acHndlif->data, 0x0, sizeof(acHndlif->data));
}
log_info("bi2x_hook", "aioIob2Bi2x_OpenSciUsbCdc");
return acHndlif;
}
static void __fastcall AIO_NMGR_IOB_BeginManageStub(int64_t a1) {
log_info("bi2x_hook", "AIO_NMGR_IOB::BeginManage");
};
static AIO_NMGR_IOB2** __fastcall aioNMgrIob2_Create(AC_HNDLIF *a1, unsigned int a2) {
if (aioNmgrIob2 == nullptr) {
aioNmgrIob2 = new AIO_NMGR_IOB2;
memset(aioNmgrIob2, 0x0, sizeof(AIO_NMGR_IOB2));
aioNmgrIob2->pAIO_NMGR_IOB_BeginManage = AIO_NMGR_IOB_BeginManageStub;
}
log_info("bi2x_hook", "aioNMgrIob2_Create");
return &aioNmgrIob2;
}
static int64_t __fastcall aioIob2Bi2x_WriteFirmGetState(int64_t a1) {
log_info("bi2x_hook", "aioIob2Bi2x_WriteFirmGetState");
return 8;
}
void bi2x_hook_init() {
// avoid double init
static bool initialized = false;
if (initialized) {
return;
} else {
initialized = true;
}
// announce
log_info("bi2x_hook", "init");
// hook IOB2 video
const auto libaioIob2VideoDll = "libaio-iob2_video.dll";
detour::trampoline_try(libaioIob2VideoDll, "aioIob2Bi2xTDJ_Create",
aioIob2Bi2xTDJ_Create, &aioIob2Bi2xTDJ_Create_orig);
detour::trampoline_try(libaioIob2VideoDll, "?GetDeviceStatus@AIO_IOB2_BI2X_TDJ@@QEBAXAEAUDEVSTATUS@1@@Z",
AIO_IOB2_BI2X_TDJ__GetDeviceStatus, &AIO_IOB2_BI2X_TDJ__GetDeviceStatus_orig);
detour::trampoline_try(libaioIob2VideoDll, "?IoReset@AIO_IOB2_BI2X_TDJ@@QEAAXI@Z",
AIO_IOB2_BI2X_TDJ__IoReset, &AIO_IOB2_BI2X_TDJ__IoReset_orig);
detour::trampoline_try(libaioIob2VideoDll, "?SetWatchDogTimer@AIO_IOB2_BI2X_TDJ@@QEAAXE@Z",
AIO_IOB2_BI2X_TDJ__SetWatchDogTimer, &AIO_IOB2_BI2X_TDJ__SetWatchDogTimer_orig);
detour::trampoline_try(libaioIob2VideoDll, "?ControlCoinBlocker@AIO_IOB2_BI2X_TDJ@@QEAAXI_N@Z",
AIO_IOB2_BI2X_TDJ__ControlCoinBlocker, &AIO_IOB2_BI2X_TDJ__ControlCoinBlocker_orig);
detour::trampoline_try(libaioIob2VideoDll, "?AddCounter@AIO_IOB2_BI2X_TDJ@@QEAAXII@Z",
AIO_IOB2_BI2X_TDJ__AddCounter, &AIO_IOB2_BI2X_TDJ__AddCounter_orig);
detour::trampoline_try(libaioIob2VideoDll, "?SetStartLamp@AIO_IOB2_BI2X_TDJ@@QEAAXI_N@Z",
AIO_IOB2_BI2X_TDJ__SetStartLamp, &AIO_IOB2_BI2X_TDJ__SetStartLamp_orig);
detour::trampoline_try(libaioIob2VideoDll, "?SetVefxButtonLamp@AIO_IOB2_BI2X_TDJ@@QEAAX_N@Z",
AIO_IOB2_BI2X_TDJ__SetVefxButtonLamp, &AIO_IOB2_BI2X_TDJ__SetVefxButtonLamp_orig);
detour::trampoline_try(libaioIob2VideoDll, "?SetEffectButtonLamp@AIO_IOB2_BI2X_TDJ@@QEAAX_N@Z",
AIO_IOB2_BI2X_TDJ__SetEffectButtonLamp, &AIO_IOB2_BI2X_TDJ__SetEffectButtonLamp_orig);
detour::trampoline_try(libaioIob2VideoDll, "?SetPlayerButtonLamp@AIO_IOB2_BI2X_TDJ@@QEAAXII_N@Z",
AIO_IOB2_BI2X_TDJ__SetPlayerButtonLamp, &AIO_IOB2_BI2X_TDJ__SetPlayerButtonLamp_orig);
detour::trampoline_try(libaioIob2VideoDll, "?SetWooferLed@AIO_IOB2_BI2X_TDJ@@QEAAXI@Z",
AIO_IOB2_BI2X_TDJ__SetWooferLED, &AIO_IOB2_BI2X_TDJ__SetWooferLED_orig);
detour::trampoline_try(libaioIob2VideoDll, "?SetIccrLed@AIO_IOB2_BI2X_TDJ@@QEAAXII@Z",
AIO_IOB2_BI2X_TDJ__SetIccrLed, &AIO_IOB2_BI2X_TDJ__SetIccrLed_orig);
detour::trampoline_try(libaioIob2VideoDll, "?SetTurnTableLed@AIO_IOB2_BI2X_TDJ@@QEAAXII@Z",
AIO_IOB2_BI2X_TDJ__SetTurnTableLed, &AIO_IOB2_BI2X_TDJ__SetTurnTableLed_orig);
detour::trampoline_try(libaioIob2VideoDll, "?SetTurnTableResist@AIO_IOB2_BI2X_TDJ@@QEAAXIE@Z",
AIO_IOB2_BI2X_TDJ__SetTurnTableResist, &AIO_IOB2_BI2X_TDJ__SetTurnTableResist_orig);
detour::trampoline_try(libaioIob2VideoDll, "?SetTapeLedData@AIO_IOB2_BI2X_TDJ@@QEAAXIPEBX@Z",
AIO_IOB2_BI2X_TDJ__SetTapeLedData, &AIO_IOB2_BI2X_TDJ__SetTapeLedData_orig);
// hook IOB
const auto libaioIobDll = "libaio-iob.dll";
detour::trampoline_try(libaioIobDll, "aioIob2Bi2x_OpenSciUsbCdc",
aioIob2Bi2X_OpenSciUsbCdc, &aioIob2Bi2x_OpenSciUsbCdc_orig);
detour::trampoline_try(libaioIobDll, "aioIob2Bi2x_WriteFirmGetState",
aioIob2Bi2x_WriteFirmGetState, &aioIob2Bi2x_WriteFirmGetState_orig);
detour::trampoline_try(libaioIobDll, "aioNMgrIob2_Create",
aioNMgrIob2_Create, &aioNMgrIob2_Create_orig);
}
}

6
games/iidx/bi2x_hook.h Normal file
View File

@@ -0,0 +1,6 @@
#pragma once
namespace games::iidx {
void bi2x_hook_init();
}

545
games/iidx/camera.cpp Normal file
View File

@@ -0,0 +1,545 @@
#include "camera.h"
#include <d3d9.h>
#include <mfapi.h>
#include <mfidl.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 "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 = std::filesystem::path(_wgetenv(L"APPDATA")) / L"spicetools_camera_control.json";
bool CAMERA_READY = false;
bool parse_cmd_params() {
const auto remove_spaces = [](const char& c) {
return c == ' ';
};
log_misc("iidx::tdjcam", "Using user-supplied addresses (-iidxtdjcamhookoffset)");
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::tdjcam", "failed to parse -iidxtdjcamhookoffset");
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::tdjcam", "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::tdjcam", "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::tdjcam", "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::tdjcam", "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::tdjcam", "failed to find hook: addr_hook_a");
log_warning("iidx::tdjcam", "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::tdjcam", "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::tdjcam", "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::tdjcam", "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::tdjcam", "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::tdjcam", "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::tdjcam", "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::tdjcam", "Created DeviceManager for DXVA");
} else {
log_warning("iidx::tdjcam", "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 = MFCreateAttributes(&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 = MFEnumDeviceSources(pAttributes, &ppDevices, &numDevices);
}
log_info("iidx::tdjcam", "MFEnumDeviceSources returned {} device(s)", numDevices);
if (numDevices > 0) {
hr = create_d3d_device_manager();
}
if (SUCCEEDED(hr)) {
int cameraIndex = 0;
for (size_t i = 0; i < numDevices && LOCAL_CAMERA_LIST.size() < 2; i++) {
// get camera activation
auto pActivate = ppDevices[i];
if ((cameraIndex == 0 && !FLIP_CAMS) || (cameraIndex == 1 && FLIP_CAMS)) {
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);
cameraIndex++;
} else {
top_camera->Release();
top_camera = nullptr;
}
} else {
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);
cameraIndex++;
} else {
front_camera->Release();
front_camera = nullptr;
}
}
}
}
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);
}
bool init_camera_hooks() {
bool result = find_camera_hooks();
if (result) {
log_misc(
"iidx::tdjcam",
"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();
}
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() {
log_info("iidx::tdjcam", "loading config");
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::tdjcam", "config parse error: {}", error);
return false;
}
// verify root is a dict
if (!doc.IsObject()) {
log_warning("iidx::tdjcam", "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::tdjcam", "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;
}
}
}
// 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::tdjcam", " >> done");
} else {
log_misc("iidx::tdjcam", "No previous config for: {}", symLink);
}
}
}
} catch (const std::exception& e) {
log_warning("iidx::tdjcam", "exception occurred while config: {}", e.what());
return false;
}
return true;
}
bool camera_config_save() {
log_info("iidx::tdjcam", "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::tdjcam", "existing config file found");
}
if (!doc.IsObject()) {
log_misc("iidx::tdjcam", "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]);
// 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::text_write(CAMERA_CONFIG_PATH, buffer.GetString())) {
} else {
log_warning("iidx::tdjcam", "unable to save config file to {}", CAMERA_CONFIG_PATH.string());
}
return true;
}
bool camera_config_reset() {
return false;
}
}

17
games/iidx/camera.h Normal file
View File

@@ -0,0 +1,17 @@
#pragma once
#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();
}

277
games/iidx/ezusb.cpp Normal file
View File

@@ -0,0 +1,277 @@
#include "ezusb.h"
#include "rawinput/rawinput.h"
#include "util/logging.h"
#include "iidx.h"
#include "io.h"
bool games::iidx::EZUSBHandle::open(LPCWSTR lpFileName) {
return wcscmp(lpFileName, L"\\\\.\\Ezusb-0") == 0;
}
int games::iidx::EZUSBHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) {
log_warning("iidx", "EZUSB invalid read operation!");
return -1;
}
int games::iidx::EZUSBHandle::write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) {
log_warning("iidx", "EZUSB invalid write operation!");
return -1;
}
int games::iidx::EZUSBHandle::device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize,
LPVOID lpOutBuffer, DWORD nOutBufferSize) {
// initialize check
if (!init) {
// TODO: initialize input
init = true;
init_success = true;
}
if (init_success) {
switch (dwIoControlCode) {
// device descriptor
case 0x222004:
// check output buffer size
if (nOutBufferSize >= 18) {
// set descriptor data
*((char *) lpOutBuffer + 8) = 0x47;
*((char *) lpOutBuffer + 9) = 0x05;
*((char *) lpOutBuffer + 10) = 0x35;
*((char *) lpOutBuffer + 11) = 0x22;
// return output buffer size
return 18;
} else // buffer too small
return -1;
// vendor
case 0x222014:
// check input buffer size
if (nInBufferSize >= 10)
return 0;
else
return -1;
// data read
case 0x22204E:
// check input buffer size
if (nInBufferSize < 4)
return -1;
// read 0x01
if (*(DWORD *) lpInBuffer == 0x01) {
*((DWORD *) lpOutBuffer) = get_pad();
*((char *) lpOutBuffer + 4) = FPGA_CMD_STATE;
*((char *) lpOutBuffer + 7) = get_tt(1, false);
*((char *) lpOutBuffer + 8) = get_tt(0, false);
*((char *) lpOutBuffer + 9) = FPGA_COUNTER++;
*((char *) lpOutBuffer + 11) = 1; // 1 success; 0 failed
*((char *) lpOutBuffer + 13) = get_slider(1) << 4 | get_slider(0);
*((char *) lpOutBuffer + 14) = get_slider(3) << 4 | get_slider(2);
*((char *) lpOutBuffer + 15) = get_slider(4);
return (int) nOutBufferSize;
}
// read 0x03
if (*(DWORD *) lpInBuffer == 0x03) {
// check output buffer size
if (nOutBufferSize < 64)
return -1;
// null node read
if (FPGA_CUR_NODE == 0) {
*(int *) lpOutBuffer = 0;
return (int) nOutBufferSize;
}
// check node size
if (FPGA_CUR_NODE != 64)
return -1;
// check command
if (SRAM_CMD != 2)
return -1;
// check if page exists
if (SRAM_PAGE >= 12)
return -1;
// write page
*((char *) lpOutBuffer + 1) = (char) SRAM_PAGE;
// copy page
memcpy((uint8_t*) lpOutBuffer + 2, SRAM_DATA + 62 * SRAM_PAGE, 62);
SRAM_PAGE++;
// write node
*((char *) lpOutBuffer) = (char) FPGA_CUR_NODE;
return (int) nOutBufferSize;
}
// unknown
return -1;
// data write
case 0x222051:
// check in buffer size
if (nInBufferSize < 4)
return -1;
// write 0x00
if (*(DWORD *) lpInBuffer == 0x00) {
// check out buffer size
if (nOutBufferSize < 10)
return -1;
// get data
uint16_t lamp = *((uint16_t *) lpOutBuffer + 0);
uint8_t led = *((uint8_t *) lpOutBuffer + 6);
uint8_t top_lamp = *((uint8_t *) lpOutBuffer + 8);
uint8_t top_neon = *((uint8_t *) lpOutBuffer + 9);
// process data
write_lamp(lamp);
write_led(led);
write_top_lamp(top_lamp);
write_top_neon(top_neon);
// flush device output
RI_MGR->devices_flush_output();
char write_node = *((char *) lpOutBuffer + 2);
if (write_node != 0) {
switch (write_node) {
case 4:
switch (*((char *) lpOutBuffer + 3)) {
case 1: // init
FPGA_CMD_STATE = 65;
break;
case 2: // check
FPGA_CMD_STATE = 66;
break;
case 3: // write
{
//int write_data = *((short *) lpOutBuffer + 4);
break;
}
case 4: // write done
FPGA_CMD_STATE = 67;
break;
default: // unknown
return -1;
}
break;
case 5:
break;
case 9:
break;
case 64: // SRAM command
SRAM_CMD = *((char *) lpOutBuffer + 3);
switch (SRAM_CMD) {
case 2: // read
SRAM_PAGE = 0;
break;
case 3: // write
SRAM_PAGE = 0;
break;
case 4: // done
break;
default: // unknown
return -1;
}
break;
default: // unknown node
return -1;
}
}
// save node
FPGA_CUR_NODE = write_node;
// return out buffer size
return (int) nOutBufferSize;
}
// write 0x02
if (*(DWORD *) lpInBuffer == 0x02) {
// check out buffer size
if (nOutBufferSize < 64)
return -1;
int cmd = *(char *) lpOutBuffer;
switch (cmd) {
case 0: // null write
break;
case 4: // unknown
break;
case 5: // 16seg
{
// write to status
IIDX_LED_TICKER_LOCK.lock();
if (!IIDXIO_LED_TICKER_READONLY)
memcpy(IIDXIO_LED_TICKER, (char *) lpOutBuffer + 2, 9);
IIDX_LED_TICKER_LOCK.unlock();
break;
}
case 64: // SRAM write
{
// get page
static char page = *((char *) lpOutBuffer + 1);
page = (char) SRAM_PAGE++;
// check page
if (page >= 12)
return -1;
// copy data
memcpy(SRAM_DATA + 62 * page, (uint8_t*) lpOutBuffer + 2, 62);
break;
}
default: // unknown node
return -1;
}
// return out buffer size
return (int) nOutBufferSize;
}
// unknown write
return -1;
// firmware upload
case 0x22206D:
// check buffer size
if (nInBufferSize >= 2)
return (int) nOutBufferSize;
else
return -1;
// unknown control code
default:
log_warning("iidx", "EZUSB unknown: {}", dwIoControlCode);
return -1;
}
} else
return -1;
}
bool games::iidx::EZUSBHandle::close() {
return true;
}

32
games/iidx/ezusb.h Normal file
View File

@@ -0,0 +1,32 @@
#pragma once
#include "hooks/devicehook.h"
namespace games::iidx {
class EZUSBHandle : public CustomHandle {
private:
// state
bool init = false;
bool init_success = false;
char FPGA_CMD_STATE = 67;
char FPGA_COUNTER = 0;
int FPGA_CUR_NODE = 0;
int SRAM_CMD = 0;
int SRAM_PAGE = 0;
char SRAM_DATA[62 * 12]{};
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;
};
}

733
games/iidx/iidx.cpp Normal file
View File

@@ -0,0 +1,733 @@
#include "iidx.h"
#include "acioemu/handle.h"
#include "avs/core.h"
#include "avs/game.h"
#include "cfg/configurator.h"
#include "games/io.h"
#ifdef SPICE64
#include "games/iidx/camera.h"
#endif
#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"
#include "touch/touch.h"
#include "misc/wintouchemu.h"
#include "misc/eamuse.h"
#include "util/detour.h"
#include "util/fileutils.h"
#include "util/libutils.h"
#include "util/memutils.h"
#include "util/sigscan.h"
#include "util/utils.h"
#include "bi2a.h"
#include "bi2x_hook.h"
#include "ezusb.h"
#include "io.h"
static decltype(RegCloseKey) *RegCloseKey_orig = nullptr;
static decltype(RegEnumKeyA) *RegEnumKeyA_orig = nullptr;
static decltype(RegOpenKeyA) *RegOpenKeyA_orig = nullptr;
static decltype(RegOpenKeyExA) *RegOpenKeyExA_orig = nullptr;
static decltype(RegQueryValueExA) *RegQueryValueExA_orig = nullptr;
namespace games::iidx {
// constants
const HKEY PARENT_ASIO_REG_HANDLE = reinterpret_cast<HKEY>(0x3001);
const HKEY DEVICE_ASIO_REG_HANDLE = reinterpret_cast<HKEY>(0x3002);
const char *ORIGINAL_ASIO_DEVICE_NAME = "XONAR SOUND CARD(64)";
// 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> 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;
static HKEY real_asio_device_reg_handle = nullptr;
static uint16_t IIDXIO_TT_STATE[2]{};
static int8_t IIDXIO_TT_DIRECTION[2]{1, 1};
static bool IIDXIO_TT_PRESSED[2]{};
static bool IIDXIO_TT_ALT_PRESSED[2]{};
char IIDXIO_LED_TICKER[10] = {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '\x00'};
bool IIDXIO_LED_TICKER_READONLY = false;
std::mutex IIDX_LED_TICKER_LOCK;
static LONG WINAPI RegOpenKeyA_hook(HKEY hKey, LPCSTR lpSubKey, PHKEY phkResult) {
if (lpSubKey != nullptr && phkResult != nullptr) {
if (hKey == HKEY_LOCAL_MACHINE &&
ASIO_DRIVER.has_value() &&
_stricmp(lpSubKey, "software\\asio") == 0)
{
*phkResult = PARENT_ASIO_REG_HANDLE;
return RegOpenKeyA_orig(hKey, lpSubKey, &real_asio_reg_handle);
}
}
return RegOpenKeyA_orig(hKey, lpSubKey, phkResult);
}
static LONG WINAPI RegOpenKeyExA_hook(HKEY hKey, LPCSTR lpSubKey, DWORD ulOptions, REGSAM samDesired,
PHKEY phkResult)
{
if (lpSubKey != nullptr && phkResult != nullptr) {
if (hKey == PARENT_ASIO_REG_HANDLE &&
ASIO_DRIVER.has_value() &&
_stricmp(lpSubKey, ORIGINAL_ASIO_DEVICE_NAME) == 0)
{
*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,
&real_asio_device_reg_handle);
}
}
return RegOpenKeyExA_orig(hKey, lpSubKey, ulOptions, samDesired, phkResult);
}
static LONG WINAPI RegEnumKeyA_hook(HKEY hKey, DWORD dwIndex, LPSTR lpName, DWORD cchName) {
if (hKey == PARENT_ASIO_REG_HANDLE && ASIO_DRIVER.has_value()) {
if (dwIndex == 0) {
auto ret = RegEnumKeyA_orig(real_asio_reg_handle, dwIndex, lpName, cchName);
if (ret == ERROR_SUCCESS && lpName != nullptr) {
log_info("iidx::asio", "stubbing '{}' with '{}'", lpName, ORIGINAL_ASIO_DEVICE_NAME);
strncpy(lpName, ORIGINAL_ASIO_DEVICE_NAME, cchName);
}
return ret;
} else {
return ERROR_NO_MORE_ITEMS;
}
}
return RegEnumKeyA_orig(hKey, dwIndex, lpName, cchName);
}
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) {
// 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 {
hKey = real_asio_device_reg_handle;
}
}
/*
* 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";
memcpy(lpData, port, sizeof(port));
return ERROR_SUCCESS;
}
}
// fallback
return RegQueryValueExA_orig(hKey, lpValueName, lpReserved, lpType, lpData, lpcbData);
}
static LONG WINAPI RegCloseKey_hook(HKEY hKey) {
if (hKey == PARENT_ASIO_REG_HANDLE || hKey == DEVICE_ASIO_REG_HANDLE) {
return ERROR_SUCCESS;
}
return RegCloseKey_orig(hKey);
}
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;
}
}
typedef void* (*aioIob2Bi2x_CreateWriteFirmContext_t)(unsigned int, int);
static aioIob2Bi2x_CreateWriteFirmContext_t aioIob2Bi2x_CreateWriteFirmContext_orig = nullptr;
static void* aioIob2Bi2x_CreateWriteFirmContext_hook(unsigned int a1, int a2) {
if (aioIob2Bi2x_CreateWriteFirmContext_orig) {
auto options = games::get_options(eamuse_get_game());
if (options->at(launcher::Options::IIDXBIO2FW).value_bool()) {
log_info("iidx", "CreateWriteFirmContext({}, {} -> 2)", a1, a2);
return aioIob2Bi2x_CreateWriteFirmContext_orig(a1, 2);
} else {
log_info("iidx", "CreateWriteFirmContext({}, {})", a1, a2);
return aioIob2Bi2x_CreateWriteFirmContext_orig(a1, a2);
}
}
log_warning("iidx", "CreateWriteFirmContext == nullptr");
return nullptr;
}
IIDXGame::IIDXGame() : Game("Beatmania IIDX") {
logger::hook_add(log_hook, this);
}
IIDXGame::~IIDXGame() {
logger::hook_remove(log_hook, this);
}
void IIDXGame::attach() {
Game::attach();
#ifdef SPICE64
if (find_pattern(
avs::game::DLL_INSTANCE,
"534F554E445F4F55545055545F444556494345", // SOUND_OUTPUT_DEVICE
"XXXXXXXXXXXXXXXXXXX",
0, 0)) {
log_misc("iidx", "This game accepts SOUND_OUTPUT_DEVICE environment variable");
} else {
log_warning("iidx", "This game does not accept SOUND_OUTPUT_DEVICE environment variable; it will be ignored");
log_warning("iidx", "Make sure you applied appropriate patches to use the correct sound device (ASIO or WASAPI)");
}
#endif
// IO boards
auto options = games::get_options(eamuse_get_game());
if (!options->at(launcher::Options::IIDXBIO2FW).value_bool()) {
// reduce boot wait time
hooks::sleep::init(1000, 1);
// add old IO board
SETUPAPI_SETTINGS settings1 {};
settings1.class_guid[0] = 0xAE18AA60;
settings1.class_guid[1] = 0x11D47F6A;
settings1.class_guid[2] = 0x0100DD97;
settings1.class_guid[3] = 0x59B92902;
const char property1[] = "Cypress EZ-USB (2235 - EEPROM missing)";
const char interface_detail1[] = "\\\\.\\Ezusb-0";
memcpy(settings1.property_devicedesc, property1, sizeof(property1));
memcpy(settings1.interface_detail, interface_detail1, sizeof(interface_detail1));
setupapihook_init(avs::game::DLL_INSTANCE);
setupapihook_add(settings1);
// IIDX <25 with EZUSB input device
devicehook_init();
devicehook_add(new EZUSBHandle());
// add new BIO2 I/O board
SETUPAPI_SETTINGS settings2 {};
settings2.class_guid[0] = 0x4D36E978;
settings2.class_guid[1] = 0x11CEE325;
settings2.class_guid[2] = 0x0008C1BF;
settings2.class_guid[3] = 0x1803E12B;
const char property2[] = "BIO2(VIDEO)";
const char interface_detail2[] = "COM2";
memcpy(settings2.property_devicedesc, property2, sizeof(property2));
memcpy(settings2.interface_detail, interface_detail2, sizeof(interface_detail2));
setupapihook_add(settings2);
// IIDX 25-27 BIO2 BI2A input device
devicehook_add(new IIDXFMSerialHandle());
}
// check for new I/O DLL
auto aio = libutils::try_library("libaio.dll");
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);
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")) {
DISABLE_CAMS = true;
}
}
// insert BI2X hooks
bi2x_hook_init();
// add card readers
devicehook_init(aio);
devicehook_add(new acioemu::ACIOHandle(L"COM1"));
// firmware upgrade hook
if (options->at(launcher::Options::IIDXBIO2FW).value_bool()) {
aioIob2Bi2x_CreateWriteFirmContext_orig = detour::iat(
"aioIob2Bi2x_CreateWriteFirmContext",
aioIob2Bi2x_CreateWriteFirmContext_hook);
//devicehook_add(new MITMHandle(L"#vid_1ccf&pid_8050", "", true));
} else {
/*
// add the BIO2 I/O board (with different firmware)
SETUPAPI_SETTINGS settings {};
settings.class_guid[0] = 0x0;
settings.class_guid[1] = 0x0;
settings.class_guid[2] = 0x0;
settings.class_guid[3] = 0x0;
const char property[] = "1CCF(8050)_000";
const char property_hardwareid[] = "USB\\VID_1CCF&PID_8050&MI_00\\000";
const char interface_detail[] = "COM3";
memcpy(settings.property_devicedesc, property, sizeof(property));
memcpy(settings.property_hardwareid, property_hardwareid, sizeof(property_hardwareid));
memcpy(settings.interface_detail, interface_detail, sizeof(interface_detail));
setupapihook_init(aio);
setupapihook_add(settings);
// IIDX 27 BIO2 BI2X input devce
devicehook_add(new BI2XSerialHandle());
*/
}
}
// ASIO device hook
RegCloseKey_orig = detour::iat_try(
"RegCloseKey", RegCloseKey_hook, avs::game::DLL_INSTANCE);
RegEnumKeyA_orig = detour::iat_try(
"RegEnumKeyA", RegEnumKeyA_hook, avs::game::DLL_INSTANCE);
RegOpenKeyA_orig = detour::iat_try(
"RegOpenKeyA", RegOpenKeyA_hook, avs::game::DLL_INSTANCE);
RegOpenKeyExA_orig = detour::iat_try(
"RegOpenKeyExA", RegOpenKeyExA_hook, avs::game::DLL_INSTANCE);
// IO device workaround
RegQueryValueExA_orig = detour::iat_try(
"RegQueryValueExA", RegQueryValueExA_hook, avs::game::DLL_INSTANCE);
// check if cam hook should be enabled
if (!DISABLE_CAMS) {
init_legacy_camera_hook(FLIP_CAMS);
}
#ifdef SPICE64
if (TDJ_CAMERA) {
init_camera_hooks();
}
if (TDJ_MODE && !D3D9_DEVICE_HOOK_DISABLE) {
nvenc_hook::initialize();
}
#endif
// init cfgmgr32 hooks
cfgmgr32hook_init(avs::game::DLL_INSTANCE);
}
void IIDXGame::pre_attach() {
Game::pre_attach();
// environment variables must be set before the DLL is loaded as the VC++ runtime copies all
// environment variables at startup
if (DISABLE_CAMS) {
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"
"!!! !!!\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"
);
}
}
void IIDXGame::detach() {
Game::detach();
devicehook_dispose();
}
void IIDXGame::detect_sound_output_device() {
// if the user specified a value (other than auto), use it as the environment var
// probably "wasapi" or "asio", but it's not explicitly checked here for forward compat
if (SOUND_OUTPUT_DEVICE.has_value() && SOUND_OUTPUT_DEVICE.value() != "auto") {
log_info(
"iidx",
"using user-supplied \"{}\" for SOUND_OUTPUT_DEVICE",
SOUND_OUTPUT_DEVICE.value());
SetEnvironmentVariable("SOUND_OUTPUT_DEVICE", SOUND_OUTPUT_DEVICE.value().c_str());
return;
}
// automatic detection
bool use_asio = false;
log_misc("iidx", "auto-detect SOUND_OUTPUT_DEVICE...");
if (ASIO_DRIVER.has_value()) {
log_misc(
"iidx",
"-iidxasio is set to \"{}\", use asio for SOUND_OUTPUT_DEVICE",
ASIO_DRIVER.value());
use_asio = true;
} else {
HKEY subkey;
LSTATUS result;
result = RegOpenKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\ASIO\\XONAR SOUND CARD(64)", &subkey);
if (result == ERROR_SUCCESS) {
RegCloseKey(subkey);
use_asio = true;
log_misc(
"iidx",
"found HKLM\\SOFTWARE\\ASIO\\XONAR SOUND CARD(64), using asio for SOUND_OUTPUT_DEVICE");
}
}
const char* device = "wasapi";
if (use_asio) {
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);
}
uint32_t get_pad() {
uint32_t pad = 0;
// get buttons
auto &buttons = get_buttons();
// player 1 buttons
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_1)))
pad |= 1 << 0x08;
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_2)))
pad |= 1 << 0x09;
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_3)))
pad |= 1 << 0x0A;
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_4)))
pad |= 1 << 0x0B;
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_5)))
pad |= 1 << 0x0C;
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_6)))
pad |= 1 << 0x0D;
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_7)))
pad |= 1 << 0x0E;
// player 2 buttons
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_1)))
pad |= 1 << 0x0F;
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_2)))
pad |= 1 << 0x10;
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_3)))
pad |= 1 << 0x11;
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_4)))
pad |= 1 << 0x12;
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_5)))
pad |= 1 << 0x13;
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_6)))
pad |= 1 << 0x14;
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_7)))
pad |= 1 << 0x15;
// player 1 start
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P1_Start)))
pad |= 1 << 0x18;
// player 2 start
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::P2_Start)))
pad |= 1 << 0x19;
// VEFX
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::VEFX)))
pad |= 1 << 0x1A;
// EFFECT
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::Effect)))
pad |= 1 << 0x1B;
// test
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::Test)))
pad |= 1 << 0x1C;
// service
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::Service)))
pad |= 1 << 0x1D;
return ~(pad & 0xFFFFFF00);
}
void write_lamp(uint16_t lamp) {
// mapping
static const size_t mapping[] = {
Lights::P1_1,
Lights::P1_2,
Lights::P1_3,
Lights::P1_4,
Lights::P1_5,
Lights::P1_6,
Lights::P1_7,
Lights::P2_1,
Lights::P2_2,
Lights::P2_3,
Lights::P2_4,
Lights::P2_5,
Lights::P2_6,
Lights::P2_7,
};
// get lights
auto &lights = get_lights();
// bit scan
for (int i = 0; i < 14; i++) {
float value = (lamp & (1 << i)) ? 1.f : 0.f;
GameAPI::Lights::writeLight(RI_MGR, lights.at(mapping[i]), value);
}
}
void write_led(uint8_t led) {
// mapping
static const size_t mapping[] = {
Lights::P1_Start,
Lights::P2_Start,
Lights::VEFX,
Lights::Effect,
};
// get lights
auto &lights = get_lights();
// bit scan
for (int i = 0; i < 4; i++) {
auto value = (led & (1 << i)) ? 1.f : 0.f;
GameAPI::Lights::writeLight(RI_MGR, lights.at(mapping[i]), value);
}
}
void write_top_lamp(uint8_t top_lamp) {
// mapping
static const size_t mapping[] = {
Lights::SpotLight1,
Lights::SpotLight2,
Lights::SpotLight3,
Lights::SpotLight4,
Lights::SpotLight5,
Lights::SpotLight6,
Lights::SpotLight7,
Lights::SpotLight8,
};
// get lights
auto &lights = get_lights();
// bit scan
for (int i = 0; i < 8; i++) {
auto value = (top_lamp & (1 << i)) ? 1.f : 0.f;
GameAPI::Lights::writeLight(RI_MGR, lights.at(mapping[i]), value);
}
}
void write_top_neon(uint8_t top_neon) {
// get lights
auto &lights = get_lights();
// write value
auto value = top_neon > 0 ? 1.f : 0.f;
GameAPI::Lights::writeLight(RI_MGR, lights.at(Lights::NeonLamp), value);
}
unsigned char get_tt(int player, bool slow) {
// check change value for high/low precision
uint16_t change =
slow ? (uint16_t) (DIGITAL_TT_SENS / 4) : (uint16_t) DIGITAL_TT_SENS;
// check player number
if (player > 1)
return 0;
// get buttons
auto &buttons = get_buttons();
bool ttp = GameAPI::Buttons::getState(RI_MGR, buttons.at(
player != 0 ? Buttons::P2_TTPlus : Buttons::P1_TTPlus));
bool ttm = GameAPI::Buttons::getState(RI_MGR, buttons.at(
player != 0 ? Buttons::P2_TTMinus : Buttons::P1_TTMinus));
bool ttpm = GameAPI::Buttons::getState(RI_MGR, buttons.at(
player != 0 ? Buttons::P2_TTPlusMinus : Buttons::P1_TTPlusMinus));
bool ttpm_alt = GameAPI::Buttons::getState(RI_MGR, buttons.at(
player != 0 ? Buttons::P2_TTPlusMinusAlt : Buttons::P1_TTPlusMinusAlt));
// TT+
if (ttp)
IIDXIO_TT_STATE[player] += change;
// TT-
if (ttm)
IIDXIO_TT_STATE[player] -= change;
// TT+/-
bool ttpm_rising_edge = !IIDXIO_TT_PRESSED[player] && ttpm;
bool ttpm_alt_rising_edge = !IIDXIO_TT_ALT_PRESSED[player] && ttpm_alt;
if (ttpm_rising_edge || ttpm_alt_rising_edge) {
IIDXIO_TT_DIRECTION[player] *= -1;
}
if (ttpm || ttpm_alt) {
IIDXIO_TT_STATE[player] += (change * IIDXIO_TT_DIRECTION[player]);
}
IIDXIO_TT_PRESSED[player] = ttpm;
IIDXIO_TT_ALT_PRESSED[player] = ttpm_alt;
// raw input
auto &analogs = get_analogs();
auto &analog = analogs[player != 0 ? Analogs::TT_P2 : Analogs::TT_P1];
auto ret_value = IIDXIO_TT_STATE[player];
if (analog.isSet()) {
ret_value = IIDXIO_TT_STATE[player];
ret_value += (uint16_t) (GameAPI::Analogs::getState(RI_MGR, analog) * 1023.999f);
}
// return higher 8 bit
return (uint8_t) (ret_value >> 2);
}
unsigned char get_slider(uint8_t slider) {
// check slide
if (slider > 4)
return 0;
// get analog
auto &analogs = get_analogs();
Analog *analog = nullptr;
switch (slider) {
case 0:
analog = &analogs.at(Analogs::VEFX);
break;
case 1:
analog = &analogs.at(Analogs::LowEQ);
break;
case 2:
analog = &analogs.at(Analogs::HiEQ);
break;
case 3:
analog = &analogs.at(Analogs::Filter);
break;
case 4:
analog = &analogs.at(Analogs::PlayVolume);
break;
default:
break;
}
// if not set return max value
if (!analog || !analog->isSet()) {
return 0xF;
}
// return slide
return (unsigned char) (GameAPI::Analogs::getState(RI_MGR, *analog) * 15.999f);
}
const char* get_16seg() {
return IIDXIO_LED_TICKER;
}
bool is_tdj_fhd() {
return TDJ_MODE && avs::game::is_ext(2022101900, MAXINT);
}
}

56
games/iidx/iidx.h Normal file
View File

@@ -0,0 +1,56 @@
#pragma once
#include <mutex>
#include <optional>
#include "games/game.h"
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];
extern bool IIDXIO_LED_TICKER_READONLY;
extern std::mutex IIDX_LED_TICKER_LOCK;
class IIDXGame : public games::Game {
public:
IIDXGame();
virtual ~IIDXGame() override;
virtual void attach() override;
virtual void pre_attach() override;
virtual void detach() override;
private:
void detect_sound_output_device();
};
// helper methods
uint32_t get_pad();
void write_lamp(uint16_t lamp);
void write_led(uint8_t led);
void write_top_lamp(uint8_t top_lamp);
void write_top_neon(uint8_t top_neon);
unsigned char get_tt(int player, bool slow);
unsigned char get_slider(uint8_t slider);
const char* get_16seg();
bool is_tdj_fhd();
}

175
games/iidx/io.cpp Normal file
View File

@@ -0,0 +1,175 @@
#include "io.h"
std::vector<Button> &games::iidx::get_buttons() {
static std::vector<Button> buttons;
if (buttons.empty()) {
buttons = GameAPI::Buttons::getButtons("Beatmania IIDX");
GameAPI::Buttons::sortButtons(
&buttons,
"Service",
"Test",
"Coin Mech",
"P1 1",
"P1 2",
"P1 3",
"P1 4",
"P1 5",
"P1 6",
"P1 7",
"P1 TT+",
"P1 TT-",
"P1 TT+/-",
"P1 TT+/- Alternate",
"P1 Start",
"P2 1",
"P2 2",
"P2 3",
"P2 4",
"P2 5",
"P2 6",
"P2 7",
"P2 TT+",
"P2 TT-",
"P2 TT+/-",
"P2 TT+/- Alternate",
"P2 Start",
"EFFECT",
"VEFX",
"P1 Headphone",
"P2 Headphone"
);
}
return buttons;
}
std::vector<Analog> &games::iidx::get_analogs() {
static std::vector<Analog> analogs;
if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Beatmania IIDX");
GameAPI::Analogs::sortAnalogs(
&analogs,
"Turntable P1",
"Turntable P2",
"VEFX",
"Low-EQ",
"Hi-EQ",
"Filter",
"Play Volume"
);
}
return analogs;
}
std::vector<Light> &games::iidx::get_lights() {
static std::vector<Light> lights;
if (lights.empty()) {
lights = GameAPI::Lights::getLights("Beatmania IIDX");
GameAPI::Lights::sortLights(
&lights,
"P1 1",
"P1 2",
"P1 3",
"P1 4",
"P1 5",
"P1 6",
"P1 7",
"P2 1",
"P2 2",
"P2 3",
"P2 4",
"P2 5",
"P2 6",
"P2 7",
"P1 Start",
"P2 Start",
"VEFX",
"Effect",
"Spot Light 1",
"Spot Light 2",
"Spot Light 3",
"Spot Light 4",
"Spot Light 5",
"Spot Light 6",
"Spot Light 7",
"Spot Light 8",
"Neon Lamp",
"Woofer R",
"Woofer G",
"Woofer B",
"IC Card Reader P1 R",
"IC Card Reader P1 G",
"IC Card Reader P1 B",
"IC Card Reader P2 R",
"IC Card Reader P2 G",
"IC Card Reader P2 B",
"Turntable P1 R",
"Turntable P1 G",
"Turntable P1 B",
"Turntable P2 R",
"Turntable P2 G",
"Turntable P2 B",
"Turntable P1 Resistance",
"Turntable P2 Resistance",
"Stage Left Avg R",
"Stage Left Avg G",
"Stage Left Avg B",
"Stage Right Avg R",
"Stage Right Avg G",
"Stage Right Avg B",
"Cabinet Left Avg R",
"Cabinet Left Avg G",
"Cabinet Left Avg B",
"Cabinet Right Avg R",
"Cabinet Right Avg G",
"Cabinet Right Avg B",
"Control Panel Under Avg R",
"Control Panel Under Avg G",
"Control Panel Under Avg B",
"Ceiling Left Avg R",
"Ceiling Left Avg G",
"Ceiling Left Avg B",
"Title Left Avg R",
"Title Left Avg G",
"Title Left Avg B",
"Title Right Avg R",
"Title Right Avg G",
"Title Right Avg B",
"Ceiling Right Avg R",
"Ceiling Right Avg G",
"Ceiling Right Avg B",
"Touch Panel Left Avg R",
"Touch Panel Left Avg G",
"Touch Panel Left Avg B",
"Touch Panel Right Avg R",
"Touch Panel Right Avg G",
"Touch Panel Right Avg B",
"Side Panel Left Inner Avg R",
"Side Panel Left Inner Avg G",
"Side Panel Left Inner Avg B",
"Side Panel Left Outer Avg R",
"Side Panel Left Outer Avg G",
"Side Panel Left Outer Avg B",
"Side Panel Left Avg R",
"Side Panel Left Avg G",
"Side Panel Left Avg B",
"Side Panel Right Outer Avg R",
"Side Panel Right Outer Avg G",
"Side Panel Right Outer Avg B",
"Side Panel Right Inner Avg R",
"Side Panel Right Inner Avg G",
"Side Panel Right Inner Avg B",
"Side Panel Right Avg R",
"Side Panel Right Avg G",
"Side Panel Right Avg B"
);
}
return lights;
}

163
games/iidx/io.h Normal file
View File

@@ -0,0 +1,163 @@
#pragma once
#include <vector>
#include "cfg/api.h"
namespace games::iidx {
// all buttons in correct order
namespace Buttons {
enum {
Service,
Test,
CoinMech,
P1_1,
P1_2,
P1_3,
P1_4,
P1_5,
P1_6,
P1_7,
P1_TTPlus,
P1_TTMinus,
P1_TTPlusMinus,
P1_TTPlusMinusAlt,
P1_Start,
P2_1,
P2_2,
P2_3,
P2_4,
P2_5,
P2_6,
P2_7,
P2_TTPlus,
P2_TTMinus,
P2_TTPlusMinus,
P2_TTPlusMinusAlt,
P2_Start,
Effect,
VEFX,
P1_Headphone,
P2_Headphone,
};
}
// all analogs in correct order
namespace Analogs {
enum {
TT_P1,
TT_P2,
VEFX,
LowEQ,
HiEQ,
Filter,
PlayVolume
};
}
// all lights in correct order
namespace Lights {
enum {
P1_1,
P1_2,
P1_3,
P1_4,
P1_5,
P1_6,
P1_7,
P2_1,
P2_2,
P2_3,
P2_4,
P2_5,
P2_6,
P2_7,
P1_Start,
P2_Start,
VEFX,
Effect,
SpotLight1,
SpotLight2,
SpotLight3,
SpotLight4,
SpotLight5,
SpotLight6,
SpotLight7,
SpotLight8,
NeonLamp,
WooferR,
WooferG,
WooferB,
ICCR_P1_R,
ICCR_P1_G,
ICCR_P1_B,
ICCR_P2_R,
ICCR_P2_G,
ICCR_P2_B,
TT_P1_R,
TT_P1_G,
TT_P1_B,
TT_P2_R,
TT_P2_G,
TT_P2_B,
TT_P1_Resistance,
TT_P2_Resistance,
StageLeftAvgR,
StageLeftAvgG,
StageLeftAvgB,
StageRightAvgR,
StageRightAvgG,
StageRightAvgB,
CabinetLeftAvgR,
CabinetLeftAvgG,
CabinetLeftAvgB,
CabinetRightAvgR,
CabinetRightAvgG,
CabinetRightAvgB,
ControlPanelUnderAvgR,
ControlPanelUnderAvgG,
ControlPanelUnderAvgB,
CeilingLeftAvgR,
CeilingLeftAvgG,
CeilingLeftAvgB,
TitleLeftAvgR,
TitleLeftAvgG,
TitleLeftAvgB,
TitleRightAvgR,
TitleRightAvgG,
TitleRightAvgB,
CeilingRightAvgR,
CeilingRightAvgG,
CeilingRightAvgB,
TouchPanelLeftAvgR,
TouchPanelLeftAvgG,
TouchPanelLeftAvgB,
TouchPanelRightAvgR,
TouchPanelRightAvgG,
TouchPanelRightAvgB,
SidePanelLeftInnerAvgR,
SidePanelLeftInnerAvgG,
SidePanelLeftInnerAvgB,
SidePanelLeftOuterAvgR,
SidePanelLeftOuterAvgG,
SidePanelLeftOuterAvgB,
SidePanelLeftAvgR,
SidePanelLeftAvgG,
SidePanelLeftAvgB,
SidePanelRightOuterAvgR,
SidePanelRightOuterAvgG,
SidePanelRightOuterAvgB,
SidePanelRightInnerAvgR,
SidePanelRightInnerAvgG,
SidePanelRightInnerAvgB,
SidePanelRightAvgR,
SidePanelRightAvgG,
SidePanelRightAvgB,
};
}
// getters
std::vector<Button> &get_buttons();
std::vector<Analog> &get_analogs();
std::vector<Light> &get_lights();
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,5 @@
#pragma once
namespace games::iidx {
void init_legacy_camera_hook(bool);
}

765
games/iidx/local_camera.cpp Normal file
View File

@@ -0,0 +1,765 @@
#include "games/iidx/local_camera.h"
#include "util/logging.h"
#include "util/utils.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::tdjcam", "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::tdjcam", "[{}] Creating camera", m_name);
// 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::tdjcam", "[{}] 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::tdjcam", "[{}] Activated", m_name);
// Create an attribute store to hold initialization settings.
hr = MFCreateAttributes(&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 = MFCreateSourceReaderFromMediaSource(
m_pSource,
pAttributes,
&m_pSourceReader
);
if (FAILED(hr)) { goto done; }
log_misc("iidx::tdjcam", "[{}] 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::tdjcam", "[{}] Initialized", m_name);
} else {
log_warning("iidx::tdjcam", "[{}] 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::tdjcam", "[{}] Camera not initialized", m_name);
return E_FAIL;
}
// Try to find a suitable output type.
log_misc("iidx::tdjcam", "[{}] 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::tdjcam", "[{}] 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::tdjcam", "[{}] 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::tdjcam", "[{}] Creating thread", m_name);
CreateThread();
}
return hr;
}
HRESULT IIDXLocalCamera::ChangeMediaType(IMFMediaType *pType) {
HRESULT hr = S_OK;
MediaTypeInfo info = GetMediaTypeInfo(pType);
log_info("iidx::tdjcam", "[{}] 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::tdjcam", "[{}] 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]() {
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::tdjcam", "[{}] 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::tdjcam", "[{}] 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::tdjcam", "[{}] >> {} 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::tdjcam", "[{}] 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::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::tdjcam", "[{}] Failed to get subtype: {}", m_name, hr);
return hr;
}
hr = MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, &width, &height);
if (FAILED(hr)) {
log_warning("iidx::tdjcam", "[{}] 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::tdjcam", "[{}] 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::tdjcam", "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;
// printTextureLevelDesc(m_texture);
// printTextureLevelDesc(m_texture_original);
done:
if (SUCCEEDED(hr)) {
log_misc("iidx::tdjcam", "[{}] Created texture", m_name);
} else {
log_warning("iidx::tdjcam", "[{}] Failed to create texture: {}", m_name, hr);
}
return hr;
}
HRESULT IIDXLocalCamera::DrawSample(IMFMediaBuffer *pSrcBuffer) {
if (!m_active) {
return E_FAIL;
}
EnterCriticalSection(&m_critsec);
HRESULT hr = S_OK;
IDirect3DSurface9 *pCameraSurf = NULL;
IDirect3DQuery9* pEventQuery = nullptr;
hr = MFGetService(pSrcBuffer, MR_BUFFER_SERVICE, IID_PPV_ARGS(&pCameraSurf));
// Stretch camera texture to destination
hr = m_device->StretchRect(pCameraSurf, &m_rcSource, m_pDestSurf, &m_rcDest, D3DTEXF_LINEAR);
if (FAILED(hr)) { goto done; }
// 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
}
done:
if (FAILED(hr)) {
log_warning("iidx::tdjcam", "Error in DrawSample {}", GetLastError());
}
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::tdjcam", "[{}] Release camera", m_name);
m_active = false;
ULONG uCount = InterlockedDecrement(&m_nRefCount);
for (size_t i = 0; i < m_mediaTypeInfos.size(); i++) {
SafeRelease(&(m_mediaTypeInfos.at(i).p_mediaType));
}
SafeRelease(&m_pDestSurf);
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;
}
}

166
games/iidx/local_camera.h Normal file
View File

@@ -0,0 +1,166 @@
#pragma once
#include <d3d9.h>
#include <dxva2api.h>
#include <mutex>
#include <mfapi.h>
#include <mfidl.h>
#include <mferror.h>
#include <mfreadwrite.h>
#include <string>
#include <thread>
#include <strmif.h>
#include <vector>
#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;
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
LPDIRECT3DTEXTURE9 m_texture = nullptr;
IDirect3DSurface9 *m_pDestSurf = 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;
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();
std::string GetName();
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();
};
}

345
games/iidx/poke.cpp Normal file
View File

@@ -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 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");
}
}

6
games/iidx/poke.h Normal file
View File

@@ -0,0 +1,6 @@
#pragma once
namespace poke {
void enable();
void disable();
}

515
games/io.cpp Normal file
View File

@@ -0,0 +1,515 @@
#include "io.h"
#include <external/robin_hood.h>
#include "bbc/io.h"
#include "bs/io.h"
#include "ddr/io.h"
#include "dea/io.h"
#include "drs/io.h"
#include "ftt/io.h"
#include "gitadora/io.h"
#include "hpm/io.h"
#include "iidx/io.h"
#include "jb/io.h"
#include "launcher/launcher.h"
#include "launcher/options.h"
#include "loveplus/io.h"
#include "mfc/io.h"
#include "mga/io.h"
#include "museca/io.h"
#include "nost/io.h"
#include "otoca/io.h"
#include "popn/io.h"
#include "qma/io.h"
#include "rb/io.h"
#include "rf3d/io.h"
#include "sc/io.h"
#include "scotto/io.h"
#include "sdvx/io.h"
#include "shogikai/io.h"
#include "silentscope/io.h"
#include "we/io.h"
#include "pcm/io.h"
#include "onpara/io.h"
#include "bc/io.h"
#include "ccj/io.h"
#include "qks/io.h"
namespace games {
// state
static bool IO_INITIALIZED = false;
static std::vector<std::string> 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::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;
static robin_hood::unordered_map<std::string, std::vector<std::string>> file_hints;
static void initialize() {
// check if already done
if (IO_INITIALIZED) {
return;
}
IO_INITIALIZED = true;
// bbc
const std::string bbc("Bishi Bashi Channel");
games.push_back(bbc);
buttons.insert({ bbc, bbc::get_buttons() });
analogs.insert({ bbc, bbc::get_analogs() });
lights.insert({ bbc, bbc::get_lights() });
file_hints[bbc].emplace_back("bsch.dll");
// hpm
const std::string hpm("HELLO! Pop'n Music");
games.push_back(hpm);
buttons.insert({ hpm, hpm::get_buttons() });
lights.insert({ hpm, hpm::get_lights() });
file_hints[hpm].emplace_back("popn.dll");
// bs
const std::string bs("Beatstream");
games.push_back(bs);
buttons.insert({ bs, bs::get_buttons() });
lights.insert({ bs, bs::get_lights() });
file_hints[bs].emplace_back("beatstream.dll");
file_hints[bs].emplace_back("beatstream1.dll");
file_hints[bs].emplace_back("beatstream2.dll");
// ddr
const std::string ddr("Dance Dance Revolution");
games.push_back(ddr);
buttons.insert({ ddr, ddr::get_buttons() });
lights.insert({ ddr, ddr::get_lights() });
file_hints[ddr].emplace_back("ddr.dll");
file_hints[ddr].emplace_back("mdxja_945.dll");
file_hints[ddr].emplace_back("arkmdxp3.dll");
// dea
const std::string dea("Dance Evolution");
games.push_back(dea);
buttons.insert({ dea, dea::get_buttons() });
lights.insert({ dea, dea::get_lights() });
file_hints[dea].emplace_back("arkkdm.dll");
// gitadora
const std::string gitadora("GitaDora");
games.push_back(gitadora);
buttons.insert({ gitadora, gitadora::get_buttons() });
analogs.insert({ gitadora, gitadora::get_analogs() });
lights.insert({ gitadora, gitadora::get_lights() });
file_hints[gitadora].emplace_back("gdxg.dll");
// iidx
const std::string iidx("Beatmania IIDX");
games.push_back(iidx);
buttons.insert({ iidx, iidx::get_buttons() });
analogs.insert({ iidx, iidx::get_analogs() });
lights.insert({ iidx, iidx::get_lights() });
file_hints[iidx].emplace_back("bm2dx.dll");
// jb
const std::string jb("Jubeat");
games.push_back(jb);
buttons.insert({ jb, jb::get_buttons() });
lights.insert({ jb, jb::get_lights() });
file_hints[jb].emplace_back("jubeat.dll");
// mga
const std::string mga("Metal Gear");
games.push_back(mga);
buttons.insert({ mga, mga::get_buttons() });
analogs.insert({ mga, mga::get_analogs() });
lights.insert({ mga, mga::get_lights() });
file_hints[mga].emplace_back("launch.dll");
// museca
const std::string museca("Museca");
games.push_back(museca);
buttons.insert({ museca, museca::get_buttons() });
analogs.insert({ museca, museca::get_analogs() });
lights.insert({ museca, museca::get_lights() });
file_hints[museca].emplace_back("museca.dll");
// nost
const std::string nost("Nostalgia");
games.push_back(nost);
buttons.insert({ nost, nost::get_buttons() });
analogs.insert({ nost, nost::get_analogs() });
lights.insert({ nost, nost::get_lights() });
file_hints[nost].emplace_back("nostalgia.dll");
// popn
const std::string popn("Pop'n Music");
games.push_back(popn);
buttons.insert({ popn, popn::get_buttons() });
lights.insert({ popn, popn::get_lights() });
file_hints[popn].emplace_back("popn19.dll");
file_hints[popn].emplace_back("popn20.dll");
file_hints[popn].emplace_back("popn21.dll");
file_hints[popn].emplace_back("popn22.dll");
file_hints[popn].emplace_back("popn23.dll");
file_hints[popn].emplace_back("popn24.dll");
file_hints[popn].emplace_back("popn25.dll");
// qma
const std::string qma("Quiz Magic Academy");
games.push_back(qma);
buttons.insert({ qma, qma::get_buttons() });
lights.insert({ qma, qma::get_lights() });
file_hints[qma].emplace_back("client.dll");
// rb
const std::string rb("Reflec Beat");
games.push_back(rb);
buttons.insert({ rb, rb::get_buttons() });
lights.insert({ rb, rb::get_lights() });
file_hints[rb].emplace_back("reflecbeat.dll");
// shogikai
std::string shogikai("Tenkaichi Shogikai");
games.push_back(shogikai);
buttons.insert({ shogikai, shogikai::get_buttons() });
lights.insert({ shogikai, shogikai::get_lights() });
file_hints[shogikai].emplace_back("shogi_engine.dll");
// rf3d
const std::string rf3d("Road Fighters 3D");
games.push_back(rf3d);
buttons.insert({ rf3d, rf3d::get_buttons() });
analogs.insert({ rf3d, rf3d::get_analogs() });
file_hints[rf3d].emplace_back("jgt.dll");
// sc
const std::string sc("Steel Chronicle");
games.push_back(sc);
buttons.insert({ sc, sc::get_buttons() });
analogs.insert({ sc, sc::get_analogs() });
lights.insert({ sc, sc::get_lights() });
file_hints[sc].emplace_back("gamekgg.dll");
// sdvx
const std::string sdvx("Sound Voltex");
games.push_back(sdvx);
buttons.insert({ sdvx, sdvx::get_buttons() });
analogs.insert({ sdvx, sdvx::get_analogs() });
lights.insert({ sdvx, sdvx::get_lights() });
file_hints[sdvx].emplace_back("soundvoltex.dll");
// mfc
const std::string mfc("Mahjong Fight Club");
games.push_back(mfc);
buttons.insert({ mfc, mfc::get_buttons() });
file_hints[mfc].emplace_back("allinone.dll");
// ftt
const std::string ftt("FutureTomTom");
games.push_back(ftt);
buttons.insert({ ftt, ftt::get_buttons() });
analogs.insert({ ftt, ftt::get_analogs() });
lights.insert({ ftt, ftt::get_lights() });
file_hints[ftt].emplace_back("arkmmd.dll");
// loveplus
const std::string loveplus("LovePlus");
games.push_back(loveplus);
buttons.insert({ loveplus, loveplus::get_buttons() });
lights.insert({ loveplus, loveplus::get_lights() });
file_hints[loveplus].emplace_back("arkklp.dll");
// scotto
const std::string scotto("Scotto");
games.push_back(scotto);
buttons.insert({ scotto, scotto::get_buttons() });
lights.insert({ scotto, scotto::get_lights() });
file_hints[scotto].emplace_back("scotto.dll");
// drs
const std::string drs("DANCERUSH");
games.push_back(drs);
buttons.insert({ drs, drs::get_buttons() });
lights.insert({ drs, drs::get_lights() });
file_hints[drs].emplace_back("superstep.dll");
// otoca
const std::string otoca("Otoca D'or");
games.push_back(otoca);
buttons.insert({ otoca, otoca::get_buttons() });
file_hints[otoca].emplace_back("arkkep.dll");
// winning eleven
const std::string we("Winning Eleven");
games.push_back(we);
buttons.insert({ we, we::get_buttons() });
analogs.insert({ we, we::get_analogs() });
lights.insert({ we, we::get_lights() });
file_hints[we].emplace_back("weac12_bootstrap_release.dll");
file_hints[we].emplace_back("arknck.dll");
// silent scope: bone eater
const std::string silentscope("Silent Scope: Bone Eater");
games.push_back(silentscope);
buttons.insert({ silentscope, silentscope::get_buttons() });
analogs.insert({ silentscope, silentscope::get_analogs() });
file_hints[silentscope].emplace_back("arkndd.dll");
// charge machine
const std::string pcm("Charge Machine");
games.push_back(pcm);
buttons.insert({ pcm, pcm::get_buttons() });
file_hints[pcm].emplace_back("launch.dll");
// ongaku paradise
const std::string op("Ongaku Paradise");
games.push_back(op);
buttons.insert({ op, onpara::get_buttons() });
file_hints[op].emplace_back("arkjc9.dll");
// bc
const std::string bc("Busou Shinki: Armored Princess Battle Conductor");
games.push_back(bc);
buttons.insert({ bc, bc::get_buttons() });
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() });
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() });
file_hints[qks].emplace_back("game/uks.exe");
}
const std::vector<std::string> &get_games() {
initialize();
return games;
}
std::vector<Button> *get_buttons(const std::string &game) {
initialize();
auto it = buttons.find(game);
if (it == buttons.end()) {
return nullptr;
}
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;
std::vector<unsigned short> vkey_defaults;
// loop for 2 keypad units, only setting defaults for keypad 1
for (size_t unit = 0; unit < 2; unit++) {
std::string prefix = unit == 0 ? "P1 Keypad " : "P2 Keypad ";
names.emplace_back(prefix + "0");
vkey_defaults.push_back(unit == 0 ? VK_NUMPAD0 : 0xFF);
names.emplace_back(prefix + "1");
vkey_defaults.push_back(unit == 0 ? VK_NUMPAD1 : 0xFF);
names.emplace_back(prefix + "2");
vkey_defaults.push_back(unit == 0 ? VK_NUMPAD2 : 0xFF);
names.emplace_back(prefix + "3");
vkey_defaults.push_back(unit == 0 ? VK_NUMPAD3 : 0xFF);
names.emplace_back(prefix + "4");
vkey_defaults.push_back(unit == 0 ? VK_NUMPAD4 : 0xFF);
names.emplace_back(prefix + "5");
vkey_defaults.push_back(unit == 0 ? VK_NUMPAD5 : 0xFF);
names.emplace_back(prefix + "6");
vkey_defaults.push_back(unit == 0 ? VK_NUMPAD6 : 0xFF);
names.emplace_back(prefix + "7");
vkey_defaults.push_back(unit == 0 ? VK_NUMPAD7 : 0xFF);
names.emplace_back(prefix + "8");
vkey_defaults.push_back(unit == 0 ? VK_NUMPAD8 : 0xFF);
names.emplace_back(prefix + "9");
vkey_defaults.push_back(unit == 0 ? VK_NUMPAD9 : 0xFF);
names.emplace_back(prefix + "00");
vkey_defaults.push_back(unit == 0 ? VK_RETURN : 0xFF);
names.emplace_back(prefix + "Decimal");
vkey_defaults.push_back(unit == 0 ? VK_DECIMAL : 0xFF);
names.emplace_back(prefix + "Insert Card");
vkey_defaults.push_back(unit == 0 ? VK_ADD : 0xFF);
}
// return sorted buttons
buttons = GameAPI::Buttons::sortButtons(buttons, names, &vkey_defaults);
return buttons;
}
std::vector<Button> *get_buttons_keypads(const std::string &game) {
initialize();
auto it = buttons_keypads.find(game);
if (it == buttons_keypads.end()) {
if (game.empty()) {
return nullptr;
} else {
buttons_keypads[game] = gen_buttons_keypads(game);
return &buttons_keypads[game];
}
}
return &it->second;
}
static std::vector<Button> gen_buttons_overlay(const std::string &game) {
// get buttons
auto buttons = GameAPI::Buttons::getButtons(game);
std::vector<std::string> names;
std::vector<unsigned short> vkey_defaults;
// overlay button definitions
names.emplace_back("Screenshot");
vkey_defaults.push_back(VK_SNAPSHOT);
names.emplace_back("Toggle Sub Screen");
vkey_defaults.push_back(VK_PRIOR);
names.emplace_back("Insert Coin");
vkey_defaults.push_back(VK_F1);
names.emplace_back("Toggle IO Panel");
vkey_defaults.push_back(VK_F2);
names.emplace_back("Toggle Config");
vkey_defaults.push_back(VK_F4);
names.emplace_back("Toggle Virtual Keypad P1");
vkey_defaults.push_back(VK_F5);
names.emplace_back("Toggle Virtual Keypad P2");
vkey_defaults.push_back(VK_F6);
names.emplace_back("Toggle Card Manager");
vkey_defaults.push_back(VK_F7);
names.emplace_back("Toggle Log");
vkey_defaults.push_back(VK_F8);
names.emplace_back("Toggle Control");
vkey_defaults.push_back(VK_F9);
names.emplace_back("Toggle Patch Manager");
vkey_defaults.push_back(VK_F10);
names.emplace_back("Toggle Screen Resize");
vkey_defaults.push_back(VK_F11);
names.emplace_back("Toggle Overlay");
vkey_defaults.push_back(VK_F12);
names.emplace_back("Toggle VR Control");
vkey_defaults.push_back(0xFF);
names.emplace_back("Toggle Camera Control");
vkey_defaults.push_back(0xFF);
names.emplace_back("Screen Resize");
vkey_defaults.push_back(0xFF);
names.emplace_back("Force Exit Game");
vkey_defaults.push_back(0xFF);
names.emplace_back("Navigator Activate");
vkey_defaults.push_back(0xFF);
names.emplace_back("Navigator Cancel");
vkey_defaults.push_back(0xFF);
names.emplace_back("Navigator Up");
vkey_defaults.push_back(0xFF);
names.emplace_back("Navigator Down");
vkey_defaults.push_back(0xFF);
names.emplace_back("Navigator Left");
vkey_defaults.push_back(0xFF);
names.emplace_back("Navigator Right");
vkey_defaults.push_back(0xFF);
names.emplace_back("Hotkey Enable 1");
vkey_defaults.push_back(0xFF);
names.emplace_back("Hotkey Enable 2");
vkey_defaults.push_back(0xFF);
names.emplace_back("Hotkey Toggle");
vkey_defaults.push_back(0xFF);
// return sorted buttons
buttons = GameAPI::Buttons::sortButtons(buttons, names, &vkey_defaults);
return buttons;
}
std::vector<Button> *get_buttons_overlay(const std::string &game) {
initialize();
auto it = buttons_overlay.find(game);
if (it == buttons_overlay.end()) {
if (game.empty()) {
return nullptr;
} else {
buttons_overlay[game] = gen_buttons_overlay(game);
return &buttons_overlay[game];
}
}
return &it->second;
}
std::vector<Analog> *get_analogs(const std::string &game) {
initialize();
auto it = analogs.find(game);
if (it == analogs.end()) {
return nullptr;
}
return &it->second;
}
std::vector<Light> *get_lights(const std::string &game) {
initialize();
auto it = lights.find(game);
if (it == lights.end()) {
return nullptr;
}
return &it->second;
}
static std::vector<Option> gen_options(const std::string &game) {
// get options
auto options = GameAPI::Options::getOptions(game);
// sort options
GameAPI::Options::sortOptions(options, launcher::get_option_definitions());
// merge options
auto merged = launcher::merge_options(options, *LAUNCHER_OPTIONS);
// return result
return merged;
}
std::vector<Option> *get_options(const std::string &game) {
initialize();
if (game.empty()) {
return LAUNCHER_OPTIONS.get();
}
auto it = options.find(game);
if (it == options.end()) {
options[game] = gen_options(game);
return &options[game];
}
return &it->second;
}
std::vector<std::string> *get_game_file_hints(const std::string &game) {
initialize();
auto it = file_hints.find(game);
if (it == file_hints.end()) {
return nullptr;
}
return &it->second;
}
}

66
games/io.h Normal file
View File

@@ -0,0 +1,66 @@
#pragma once
#include <vector>
#include "cfg/api.h"
namespace games {
namespace OverlayButtons {
enum {
Screenshot,
ToggleSubScreen,
InsertCoin,
ToggleIOPanel,
ToggleConfig,
ToggleVirtualKeypadP1,
ToggleVirtualKeypadP2,
ToggleCardManager,
ToggleLog,
ToggleControl,
TogglePatchManager,
ToggleScreenResize,
ToggleOverlay,
ToggleVRControl,
ToggleCameraControl,
ScreenResize,
SuperExit,
NavigatorActivate,
NavigatorCancel,
NavigatorUp,
NavigatorDown,
NavigatorLeft,
NavigatorRight,
HotkeyEnable1,
HotkeyEnable2,
HotkeyToggle,
};
}
namespace KeypadButtons {
enum {
Keypad0,
Keypad1,
Keypad2,
Keypad3,
Keypad4,
Keypad5,
Keypad6,
Keypad7,
Keypad8,
Keypad9,
Keypad00,
KeypadDecimal,
InsertCard,
Size,
};
}
const std::vector<std::string> &get_games();
std::vector<Button> *get_buttons(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);
std::vector<Light> *get_lights(const std::string &game);
std::vector<Option> *get_options(const std::string &game);
std::vector<std::string> *get_game_file_hints(const std::string &game);
}

66
games/jb/io.cpp Normal file
View File

@@ -0,0 +1,66 @@
#include "io.h"
std::vector<Button> &games::jb::get_buttons() {
static std::vector<Button> buttons;
if (buttons.empty()) {
buttons = GameAPI::Buttons::getButtons("Jubeat");
GameAPI::Buttons::sortButtons(
&buttons,
"Service",
"Test",
"Coin Mech",
"Button 1",
"Button 2",
"Button 3",
"Button 4",
"Button 5",
"Button 6",
"Button 7",
"Button 8",
"Button 9",
"Button 10",
"Button 11",
"Button 12",
"Button 13",
"Button 14",
"Button 15",
"Button 16"
);
}
return buttons;
}
std::vector<Light> &games::jb::get_lights() {
static std::vector<Light> lights;
if (lights.empty()) {
lights = GameAPI::Lights::getLights("Jubeat");
GameAPI::Lights::sortLights(
&lights,
"Panel Front R",
"Panel Front G",
"Panel Front B",
"Panel Title R",
"Panel Title G",
"Panel Title B",
"Panel Top R",
"Panel Top G",
"Panel Top B",
"Panel Left R",
"Panel Left G",
"Panel Left B",
"Panel Right R",
"Panel Right G",
"Panel Right B",
"Panel Woofer R",
"Panel Woofer G",
"Panel Woofer B"
);
}
return lights;
}

60
games/jb/io.h Normal file
View File

@@ -0,0 +1,60 @@
#pragma once
#include <vector>
#include "cfg/api.h"
namespace games::jb {
// all buttons in correct order
namespace Buttons {
enum {
Service,
Test,
CoinMech,
Button1,
Button2,
Button3,
Button4,
Button5,
Button6,
Button7,
Button8,
Button9,
Button10,
Button11,
Button12,
Button13,
Button14,
Button15,
Button16,
};
}
// all lights in correct order
namespace Lights {
enum {
PANEL_FRONT_R,
PANEL_FRONT_G,
PANEL_FRONT_B,
PANEL_TITLE_R,
PANEL_TITLE_G,
PANEL_TITLE_B,
PANEL_TOP_R,
PANEL_TOP_G,
PANEL_TOP_B,
PANEL_LEFT_R,
PANEL_LEFT_G,
PANEL_LEFT_B,
PANEL_RIGHT_R,
PANEL_RIGHT_G,
PANEL_RIGHT_B,
PANEL_WOOFER_R,
PANEL_WOOFER_G,
PANEL_WOOFER_B,
};
}
// getters
std::vector<Button> &get_buttons();
std::vector<Light> &get_lights();
}

192
games/jb/jb.cpp Normal file
View File

@@ -0,0 +1,192 @@
#include "jb.h"
#include <windows.h>
#include "avs/game.h"
#include "hooks/graphics/graphics.h"
#include "touch/touch.h"
#include "util/logging.h"
#include "util/utils.h"
#include "util/detour.h"
#include "util/libutils.h"
#define JB_BUTTON_SIZE 160
#define JB_BUTTON_GAP 37
#define JB_BUTTON_HITBOX (JB_BUTTON_SIZE + JB_BUTTON_GAP)
namespace games::jb {
// touch stuff
bool TOUCH_LEGACY_BOX = false;
static bool TOUCH_ENABLE = false;
static bool TOUCH_ATTACHED = false;
static bool IS_PORTRAIT = true;
static std::vector<TouchPoint> TOUCH_POINTS;
bool TOUCH_STATE[16];
void touch_update() {
// check if touch enabled
if (!TOUCH_ENABLE) {
return;
}
// attach touch module
if (!TOUCH_ATTACHED) {
/*
* Find the game window.
* We check the foreground window first, then fall back to searching for the window title
* All game versions seem to have their model first in the window title
*/
HWND wnd = GetForegroundWindow();
if (!string_begins_with(GetActiveWindowTitle(), avs::game::MODEL)) {
wnd = FindWindowBeginsWith(avs::game::MODEL);
}
// check if we have a window handle
if (!wnd) {
log_warning("jubeat", "could not find window handle for touch");
TOUCH_ENABLE = false;
return;
}
// attach touch hook
log_info("jubeat", "using window handle for touch: {}", fmt::ptr(wnd));
touch_create_wnd(wnd, true);
// show cursor
if (GRAPHICS_SHOW_CURSOR) {
ShowCursor(TRUE);
}
// earlier games use a different screen orientation
if (!avs::game::is_model("L44")) {
IS_PORTRAIT = false;
}
// set attached
TOUCH_ATTACHED = true;
}
// reset touch state
memset(TOUCH_STATE, 0, sizeof(TOUCH_STATE));
// check touch points
// note that the IO code in device.cpp will correctly compensate for orientation, depending on the model.
TOUCH_POINTS.clear();
touch_get_points(TOUCH_POINTS);
if (TOUCH_LEGACY_BOX) {
auto offset = IS_PORTRAIT ? 580 : 0;
for (auto &tp : TOUCH_POINTS) {
// get grid coordinates
int x = tp.x * 4 / 768;
int y = (tp.y - offset) * 4 / (1360 - 580);
// set the corresponding state
int index = y * 4 + x;
if (index >= 0 && index < 16) {
TOUCH_STATE[index] = true;
}
}
} else {
for (auto &tp : TOUCH_POINTS) {
int x_relative = tp.x;
int y_relative = tp.y;
// x_relative and y_relative are relative to the top-left pixel of the first button
if (IS_PORTRAIT) {
// which is at (8, 602) in portrait:
// X: [8...759] (752 pixels wide)
// Y: [602...1353] (752 pixels high)
x_relative -= 8;
y_relative -= 602;
} else {
// and at (8, 8) in landscape
x_relative -= 8;
y_relative -= 8;
}
if (x_relative < 0 || y_relative < 0) {
continue;
}
// x_hitbox and y_hitbox is relative to top-left pixel of each button
int x_index = x_relative / JB_BUTTON_HITBOX;
int x_hitbox = x_relative % JB_BUTTON_HITBOX;
int y_index = y_relative / JB_BUTTON_HITBOX;
int y_hitbox = y_relative % JB_BUTTON_HITBOX;
// check if the gap was touched
if (x_hitbox > JB_BUTTON_SIZE || y_hitbox > JB_BUTTON_SIZE) {
continue;
}
// set the corresponding state
int index = y_index * 4 + x_index;
if (0 <= index && index < 16) {
TOUCH_STATE[index] = true;
}
}
}
}
/*
* to fix "IP ADDR CHANGE" errors on boot and in-game when using weird network setups such as a VPN
*/
static BOOL __stdcall network_addr_is_changed() {
return 0;
}
/*
* to fix lag spikes when game tries to ping "eamuse.konami.fun" every few minutes
*/
static BOOL __stdcall network_get_network_check_info() {
return 0;
}
/*
* to fix network error on non DHCP interfaces
*/
static BOOL __cdecl network_get_dhcp_result() {
return 1;
}
static int __cdecl GFDbgSetReportFunc(void *func) {
log_misc("jubeat", "GFDbgSetReportFunc hook hit");
return 0;
}
JBGame::JBGame() : Game("Jubeat") {
}
void JBGame::attach() {
Game::attach();
// enable touch
TOUCH_ENABLE = true;
// enable debug logging of gftools
HMODULE gftools = libutils::try_module("gftools.dll");
detour::inline_hook((void *) GFDbgSetReportFunc, libutils::try_proc(
gftools, "GFDbgSetReportFunc"));
// apply patches
HMODULE network = libutils::try_module("network.dll");
detour::inline_hook((void *) network_addr_is_changed, libutils::try_proc(
network, "network_addr_is_changed"));
detour::inline_hook((void *) network_get_network_check_info, libutils::try_proc(
network, "network_get_network_check_info"));
detour::inline_hook((void *) network_get_dhcp_result, libutils::try_proc(
network, "network_get_dhcp_result"));
}
void JBGame::detach() {
Game::detach();
// disable touch
TOUCH_ENABLE = false;
}
}

18
games/jb/jb.h Normal file
View File

@@ -0,0 +1,18 @@
#pragma once
#include "games/game.h"
namespace games::jb {
// touch stuff
extern bool TOUCH_LEGACY_BOX;
extern bool TOUCH_STATE[16];
void touch_update();
class JBGame : public games::Game {
public:
JBGame();
virtual void attach() override;
virtual void detach() override;
};
}

38
games/loveplus/io.cpp Normal file
View File

@@ -0,0 +1,38 @@
#include "io.h"
std::vector<Button> &games::loveplus::get_buttons() {
static std::vector<Button> buttons;
if (buttons.empty()) {
buttons = GameAPI::Buttons::getButtons("LovePlus");
GameAPI::Buttons::sortButtons(
&buttons,
"Service",
"Test",
"Left",
"Right"
);
}
return buttons;
}
std::vector<Light> &games::loveplus::get_lights() {
static std::vector<Light> lights;
if (lights.empty()) {
lights = GameAPI::Lights::getLights("LovePlus");
GameAPI::Lights::sortLights(
&lights,
"Red",
"Green",
"Blue",
"Left",
"Right"
);
}
return lights;
}

32
games/loveplus/io.h Normal file
View File

@@ -0,0 +1,32 @@
#pragma once
#include <vector>
#include "cfg/api.h"
namespace games::loveplus {
// all buttons in correct order
namespace Buttons {
enum {
Service,
Test,
Left,
Right,
};
}
// all lights in correct order
namespace Lights {
enum {
Red,
Green,
Blue,
Left,
Right,
};
}
// getters
std::vector<Button> &get_buttons();
std::vector<Light> &get_lights();
}

171
games/loveplus/loveplus.cpp Normal file
View File

@@ -0,0 +1,171 @@
#include "loveplus.h"
#include <windows.h>
#include "avs/game.h"
#include "hooks/graphics/graphics.h"
#include "hooks/devicehook.h"
#include "touch/touch.h"
#include "util/detour.h"
#include "util/libutils.h"
#include "util/logging.h"
#include "util/utils.h"
namespace games::loveplus {
// touch stuff
static bool TOUCH_ENABLE = false;
static bool TOUCH_ATTACHED = false;
void touch_update() {
// check if touch enabled
if (!TOUCH_ENABLE) {
return;
}
// attach touch module
if (!TOUCH_ATTACHED) {
// Find the game window.
// We check the foreground window first, then fall back to searching for the window title
// All game versions seem to have their model first in the window title
HWND wnd = GetForegroundWindow();
if (!string_begins_with(GetActiveWindowTitle(), "LovePlus")) {
wnd = FindWindowBeginsWith(avs::game::MODEL);
}
// attach touch hook
if (wnd) {
log_info("loveplus", "using window handle for touch: {}", fmt::ptr(wnd));
touch_create_wnd(wnd);
} else {
log_info("loveplus", "falling back to the DirectX window handle for touch");
touch_attach_dx_hook();
}
// show cursor
if (GRAPHICS_SHOW_CURSOR) {
ShowCursor(1);
}
// set attached
TOUCH_ATTACHED = true;
}
}
static int __cdecl lib_touchpanel_init() {
touch_update();
return 0;
}
static int __cdecl lib_touchpanel_calibrate(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8) {
return 0;
}
// FIXME: test this calling convention change!
static int __cdecl lib_touchpanel_diagnostics(void *a1) {
return 0;
}
static int __cdecl lib_touchpanel_get_touch(DWORD *a1) {
touch_update();
// get touch points
std::vector<TouchPoint> touch_points;
touch_get_points(touch_points);
// check first touch point
if (!touch_points.empty()) {
auto &tp = touch_points[0];
a1[0] = tp.y;
a1[1] = tp.x;
a1[2] = 0;
a1[3] = 1;
a1[4] = 0;
} else {
a1[0] = 0;
a1[1] = 0;
a1[2] = 0;
a1[3] = 0;
a1[4] = 0;
}
return 0;
}
static int __cdecl lib_touchpanel_get_touch_direct(DWORD *a1) {
return lib_touchpanel_get_touch(a1);
}
static int __cdecl lib_touchpanel_update() {
return 1;
}
static bool __cdecl lib_touchpanel_term() {
return 0;
}
static bool __cdecl lib_touchpanel_instance_term() {
return 0;
}
static BOOL __cdecl SetEqualizer(char *a1) {
return FALSE;
}
static LPSTR __stdcall GetCommandLineA_hook() {
static std::string lp_args = "-win -noCamera -noWatchDog -noIOError -noIrda -notarget";
return lp_args.data();
}
LovePlusGame::LovePlusGame() : Game("LovePlus") {
}
void LovePlusGame::attach() {
Game::attach();
// init stuff
devicehook_init();
// enable touch
TOUCH_ENABLE = true;
// touchpanel emulation
HMODULE lib_touchpanel = libutils::try_library("lib_touchpanel.dll");
detour::inline_hook((void *) lib_touchpanel_init, libutils::try_proc(
lib_touchpanel, "lib_touchpanel_init"));
detour::inline_hook((void *) lib_touchpanel_calibrate, libutils::try_proc(
lib_touchpanel, "lib_touchpanel_calibrate"));
detour::inline_hook((void *) lib_touchpanel_diagnostics, libutils::try_proc(
lib_touchpanel, "lib_touchpanel_diagnostics"));
detour::inline_hook((void *) lib_touchpanel_get_touch, libutils::try_proc(
lib_touchpanel, "lib_touchpanel_get_touch_direct"));
detour::inline_hook((void *) lib_touchpanel_get_touch_direct, libutils::try_proc(
lib_touchpanel, "lib_touchpanel_get_touch"));
detour::inline_hook((void *) lib_touchpanel_update, libutils::try_proc(
lib_touchpanel, "lib_touchpanel_update"));
detour::inline_hook((void *) lib_touchpanel_term, libutils::try_proc(
lib_touchpanel, "lib_touchpanel_term"));
detour::inline_hook((void *) lib_touchpanel_instance_term, libutils::try_proc(
lib_touchpanel, "lib_touchpanel_instance_term"));
// equalizer crash fix
HMODULE seteq = libutils::try_library("ad_hd_seteq_dll.dll");
detour::inline_hook((void *) SetEqualizer, libutils::try_proc(seteq, "SetEqualizer"));
// get command line hook
HMODULE lpac = libutils::try_library("lpac.dll");
detour::iat("GetCommandLineA", GetCommandLineA_hook, lpac);
}
void LovePlusGame::detach() {
Game::detach();
// disable touch
TOUCH_ENABLE = false;
}
}

16
games/loveplus/loveplus.h Normal file
View File

@@ -0,0 +1,16 @@
#pragma once
#include "games/game.h"
namespace games::loveplus {
// touch stuff
void touch_update();
class LovePlusGame : public games::Game {
public:
LovePlusGame();
virtual void attach() override;
virtual void detach() override;
};
}

22
games/mfc/io.cpp Normal file
View File

@@ -0,0 +1,22 @@
#include "io.h"
std::vector<Button> &games::mfc::get_buttons() {
static std::vector<Button> buttons;
if (buttons.empty()) {
buttons = GameAPI::Buttons::getButtons("Mahjong Fight Club");
GameAPI::Buttons::sortButtons(
&buttons,
"Select",
"Service",
"Test",
"Coin",
"Joystick Up",
"Joystick Down",
"Joystick Enter"
);
}
return buttons;
}

21
games/mfc/io.h Normal file
View File

@@ -0,0 +1,21 @@
#pragma once
#include <vector>
#include "cfg/api.h"
namespace games::mfc {
namespace Buttons {
enum {
Select,
Service,
Test,
Coin,
JoystickUp,
JoystickDown,
JoystickStart,
};
}
// getters
std::vector<Button> &get_buttons();
}

382
games/mfc/mfc.cpp Normal file
View File

@@ -0,0 +1,382 @@
#include "mfc.h"
#include "acio/icca/icca.h"
#include "avs/game.h"
#include "hooks/graphics/graphics.h"
#include "misc/eamuse.h"
#include "touch/touch.h"
#include "util/detour.h"
#include "util/libutils.h"
#include "util/logging.h"
#include "util/utils.h"
#include "io.h"
namespace games::mfc {
// touch stuff
static int TOUCH_MAX_X = 1360;
static int TOUCH_MAX_Y = 768;
static bool TOUCH_ATTACHED = false;
static bool TOUCH_PRESENT = false;
static bool TOUCH_LAST_PRESENT = false;
static TouchPoint TOUCH_CURRENT {};
static TouchPoint TOUCH_LAST {};
// general i/o stuff
static struct joystick_state JOY_STATE {};
static struct joystick_state JOY_PREVIOUS_STATE {};
// ic card stuff
static bool CARD_IN = false;
static uint8_t CARD_TYPE = 0;
static uint8_t CARD_UID[8];
typedef int (__cdecl *inifile_param_num_t)(const char *, int *);
static inifile_param_num_t inifile_param_num_real;
static int __cdecl inifile_param_num_hook(const char *a1, int *a2) {
if (strcmp(a1, "WINDOWED") == 0) {
*a2 = GRAPHICS_WINDOWED ? 1 : 0;
return 1;
}
return inifile_param_num_real(a1, a2);
}
static bool __cdecl nic_dhcp_maybe_exist() {
return true;
}
static void __cdecl touch_init(int width, int height) {
log_info("mfc", "call touch_init(width: {}, height: {})", width, height);
// attach touch module
if (!TOUCH_ATTACHED) {
// store touch size specification
TOUCH_MAX_X = width;
TOUCH_MAX_Y = height;
// attach touch hook
touch_attach_dx_hook();
// show cursor
if (GRAPHICS_SHOW_CURSOR) {
ShowCursor(TRUE);
}
// set attached
TOUCH_ATTACHED = true;
}
}
static void __cdecl touch_step() {
if (TOUCH_ATTACHED) {
// get touch points
std::vector<TouchPoint> touch_points;
touch_get_points(touch_points);
/*
log_info("MFC", "touch points: " + to_string(touch_points.size()));
for (TouchPoint tp : touch_points) {
log_info("MFC", "touch point (id: {}, x: {}, y: {})", tp.id, tp.x, tp.y);
}
*/
TOUCH_LAST = TOUCH_CURRENT;
TOUCH_LAST_PRESENT = TOUCH_PRESENT;
if (!touch_points.empty()) {
auto &tp = touch_points[0];
TOUCH_CURRENT = tp;
TOUCH_PRESENT = true;
} else {
TOUCH_PRESENT = false;
}
}
}
static int __cdecl h8io_touch_getpos(int *pX, int *pY) {
if (!TOUCH_PRESENT) {
// false value means no touch present
return 0;
}
*pX = TOUCH_CURRENT.x > TOUCH_MAX_X ? TOUCH_MAX_X : TOUCH_CURRENT.x;
*pY = TOUCH_CURRENT.y > TOUCH_MAX_Y ? TOUCH_MAX_Y : TOUCH_CURRENT.y;
// true value means touch present
return 1;
}
static int __cdecl h8io_touch_getpos_trig(int *pX, int *pY) {
// following the game logic, a trigger check bails early if there was a touch detected
// during the last frame
if (TOUCH_LAST_PRESENT) {
return 0;
}
return h8io_touch_getpos(pX, pY);
}
static void __cdecl touch_get_raw_data(int *pX, int *pY) {
h8io_touch_getpos(pX, pY);
}
static int __cdecl touch_get_raw_data_trig(int *pX, int *pY) {
return h8io_touch_getpos_trig(pX, pY);
}
static char __cdecl mfc5_begin_io_mng(int a1, int a2) {
log_info("mfc", "call mfc5_begin_io_mng");
return 1;
}
static int __cdecl mfc5_is_io_mng_ready() {
log_info("mfc", "call mfc5_is_io_mng_ready");
return 1;
}
// the joystick state functions return the current state that is updated
// in `joystick_step` while ensuring to track the previous state to detect
// held buttons and newly pressed buttons
static void __cdecl joystick_step() {
// set last state
JOY_PREVIOUS_STATE = JOY_STATE;
// get buttons
auto &buttons = get_buttons();
// set new state
JOY_STATE.up = GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::JoystickUp));
JOY_STATE.down = GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::JoystickDown));
JOY_STATE.start = GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::JoystickStart));
JOY_STATE.service = GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::Service));
JOY_STATE.test = GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::Test));
}
static bool __cdecl joy_up(int a1) {
return JOY_STATE.up;
}
static bool __cdecl joy_up_on(int a1) {
return JOY_STATE.up && !JOY_PREVIOUS_STATE.up;
}
static bool __cdecl joy_up_rep(int a1) {
return JOY_STATE.up && JOY_PREVIOUS_STATE.up;
}
static bool __cdecl joy_down(int a1) {
return JOY_STATE.down;
}
static bool __cdecl joy_down_on(int a1) {
return JOY_STATE.down && !JOY_PREVIOUS_STATE.down;
}
static bool __cdecl joy_down_rep(int a1) {
return JOY_STATE.down && JOY_PREVIOUS_STATE.down;
}
static bool __cdecl joy_start(int a1) {
static bool FIRST_CHECK = true;
// following the game logic, the first poll of the start button must
// be true for center mode
if (FIRST_CHECK) {
FIRST_CHECK = false;
return true;
}
return JOY_STATE.start;
}
static bool __cdecl joy_start_on(int a1) {
return JOY_STATE.start && !JOY_PREVIOUS_STATE.start;
}
static bool __cdecl joy_start_rep(int a1) {
return JOY_STATE.start && JOY_PREVIOUS_STATE.start;
}
static bool __cdecl joy_service() {
return JOY_STATE.service;
}
static bool __cdecl joy_service_on() {
return JOY_STATE.service && !JOY_PREVIOUS_STATE.service;
}
static bool __cdecl joy_test() {
return JOY_STATE.test;
}
static bool __cdecl joy_test_on() {
return JOY_STATE.test && !JOY_PREVIOUS_STATE.test;
}
static void update_card() {
if (eamuse_card_insert_consume(1, 0)) {
if (!CARD_IN) {
CARD_IN = true;
eamuse_get_card(1, 0, CARD_UID);
CARD_TYPE = is_card_uid_felica(CARD_UID) ? 1 : 0;
}
} else {
CARD_IN = false;
}
}
static int __cdecl mfc5_ic_card_read_init() {
//log_misc("mfc", "mfc5_ic_card_read_init");
CARD_IN = false;
CARD_TYPE = 0;
memset(CARD_UID, 0, sizeof(CARD_UID));
return 0;
}
static int __cdecl mfc5_ic_card_read_step() {
//log_misc("mfc", "mfc5_ic_card_read_step");
update_card();
return CARD_IN ? 0 : 1;
}
static int __cdecl mfc5_ic_card_status() {
//log_misc("mfc", "mfc5_ic_card_status");
return CARD_IN ? 2 : 0;
}
static void __cdecl mfc5_get_ic_card_id_type(uint8_t *a1) {
//log_misc("mfc", "mfc5_get_ic_card_id_type(a1: {})", fmt::ptr(a1));
*a1 = CARD_TYPE;
}
static void __cdecl mfc5_get_ic_card_id(uint8_t *a1) {
//log_misc("mfc", "mfc5_get_ic_card_id(a1: {})", fmt::ptr(a1));
memcpy(a1, CARD_UID, 8);
}
static void __cdecl mouse_utl_step() {
return;
}
static HCURSOR WINAPI SetCursor_hook(HCURSOR hCursor) {
log_misc("mfc", "SetCursor hook hit");
return nullptr;
}
MFCGame::MFCGame() : Game("Mahjong Fight Club") {
}
void MFCGame::attach() {
Game::attach();
// NOTE:
//
// develop key binds
// F2 SERVICE
// S SERVICE
// ENTER SELECT
// ENTER ENTER (center)
// ESC TEST
// UP SELECT1 (center)
// DOWN SELECT2 (center)
inifile_param_num_real = (inifile_param_num_t) detour::iat_try(
"?inifile_param_num@@YAHPBDPAH@Z", (void *) &inifile_param_num_hook, avs::game::DLL_INSTANCE);
auto allinone_module = libutils::try_module("allinone.dll");
auto system_module = libutils::try_module("system.dll");
// network fix
detour::iat_try("?nic_dhcp_maybe_exist@@YAEXZ", nic_dhcp_maybe_exist, system_module);
// touch i/o
detour::inline_hook((void *) h8io_touch_getpos, libutils::try_proc(
allinone_module, "?h8io_touch_getpos@@YAHPAH0@Z"));
detour::inline_hook((void *) h8io_touch_getpos_trig, libutils::try_proc(
allinone_module, "?h8io_touch_getpos_trig@@YAHPAH0@Z"));
detour::inline_hook((void *) touch_get_raw_data, libutils::try_proc(
allinone_module, "?touch_get_raw_data@@YAXPAH0@Z"));
detour::inline_hook((void *) touch_get_raw_data_trig, libutils::try_proc(
allinone_module, "?touch_get_raw_data_trig@@YAHPAH0@Z"));
detour::inline_hook((void *) touch_init, libutils::try_proc(
allinone_module, "?touch_init@@YAXHH@Z"));
detour::inline_hook((void *) touch_step, libutils::try_proc(
allinone_module, "?touch_step@@YAXXZ"));
// general i/o
detour::inline_hook((void *) mfc5_begin_io_mng, libutils::try_proc(
allinone_module, "?mfc5_begin_io_mng@@YAEHH@Z"));
detour::inline_hook((void *) mfc5_is_io_mng_ready, libutils::try_proc(
allinone_module, "?mfc5_is_io_mng_ready@@YAHXZ"));
detour::inline_hook((void *) joystick_step, libutils::try_proc(
allinone_module, "?joystick_step@@YAXXZ"));
detour::inline_hook((void *) joy_up, libutils::try_proc(
allinone_module, "?joy_up@@YAHH@Z"));
detour::inline_hook((void *) joy_up_on, libutils::try_proc(
allinone_module, "?joy_up_on@@YAHH@Z"));
detour::inline_hook((void *) joy_up_rep, libutils::try_proc(
allinone_module, "?joy_up_rep@@YAHH@Z"));
detour::inline_hook((void *) joy_down, libutils::try_proc(
allinone_module, "?joy_down@@YAHH@Z"));
detour::inline_hook((void *) joy_down_on, libutils::try_proc(
allinone_module, "?joy_down_on@@YAHH@Z"));
detour::inline_hook((void *) joy_down_rep, libutils::try_proc(
allinone_module, "?joy_down_rep@@YAHH@Z"));
detour::inline_hook((void *) joy_start, libutils::try_proc(
allinone_module, "?joy_start@@YAHH@Z"));
detour::inline_hook((void *) joy_start_on, libutils::try_proc(
allinone_module, "?joy_start_on@@YAHH@Z"));
detour::inline_hook((void *) joy_start_rep, libutils::try_proc(
allinone_module, "?joy_start_rep@@YAHH@Z"));
detour::inline_hook((void *) joy_service, libutils::try_proc(
allinone_module, "?joy_service@@YAHXZ"));
detour::inline_hook((void *) joy_service_on, libutils::try_proc(
allinone_module, "?joy_service_on@@YAHXZ"));
detour::inline_hook((void *) joy_test, libutils::try_proc(
allinone_module, "?joy_test@@YAHXZ"));
detour::inline_hook((void *) joy_test_on, libutils::try_proc(
allinone_module, "?joy_test_on@@YAHXZ"));
// ic card
detour::inline_hook((void *) mfc5_ic_card_read_init, libutils::try_proc(
allinone_module, "?mfc5_ic_card_read_init@@YAHXZ"));
detour::inline_hook((void *) mfc5_ic_card_read_step, libutils::try_proc(
allinone_module, "?mfc5_ic_card_read_step@@YAHXZ"));
detour::inline_hook((void *) mfc5_ic_card_status, libutils::try_proc(
allinone_module, "?mfc5_ic_card_status@@YAHXZ"));
detour::inline_hook((void *) mfc5_get_ic_card_id_type, libutils::try_proc(
allinone_module, "?mfc5_get_ic_card_id_type@@YAXPAE@Z"));
detour::inline_hook((void *) mfc5_get_ic_card_id, libutils::try_proc(
allinone_module, "?mfc5_get_ic_card_id@@YAXQAE@Z"));
if (GRAPHICS_SHOW_CURSOR) {
detour::iat_try("SetCursor", (void *) SetCursor_hook, avs::game::DLL_INSTANCE);
detour::iat_try("SetCursor", (void *) SetCursor_hook, allinone_module);
detour::inline_hook((void *) mouse_utl_step, libutils::try_proc(
allinone_module, "?mouse_utl_step@@YAXXZ"));
}
}
}

20
games/mfc/mfc.h Normal file
View File

@@ -0,0 +1,20 @@
#pragma once
#include "games/game.h"
namespace games::mfc {
struct joystick_state {
bool up = false;
bool down = false;
bool start = false;
bool service = false;
bool test = false;
};
class MFCGame : public games::Game {
public:
MFCGame();
virtual void attach() override;
};
}

158
games/mga/gunio.cpp Normal file
View File

@@ -0,0 +1,158 @@
#include "gunio.h"
#include "util/logging.h"
bool games::mga::SpiceGearGunHandle::open(LPCWSTR lpFileName) {
if (wcscmp(lpFileName, L"COM1") != 0) {
return false;
}
log_info("metalgear", "Opened gun device on COM1");
return true;
}
int games::mga::SpiceGearGunHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) {
// minimum buffer size
if ((command == 0u) || nNumberOfBytesToRead < 2)
return 0;
// execute command
int bytes_read = 0;
switch (command) {
case 0x04: // get version
// write version
*((unsigned char *) lpBuffer + bytes_read++) = version;
break;
case 0x08: // get input state
{
// check buffer size
if (nNumberOfBytesToRead < 12)
return 0;
// get cursor position
POINT cursor_pos{};
GetCursorPos(&cursor_pos);
// get screen size
RECT screen_size{};
GetWindowRect(GetDesktopWindow(), &screen_size);
auto screen_width = (unsigned short) (screen_size.right - screen_size.left);
auto screen_height = (unsigned short) (screen_size.bottom - screen_size.top);
// calculate cursor position
p1_x = (unsigned short) (((cursor_pos.x * 1024) / screen_width) % 1024);
p1_y = (unsigned short) (1024 - (((cursor_pos.y * 1024) / screen_height) % 1024));
// gun P1
*((unsigned char *) lpBuffer + bytes_read++) = 0x00;
*((unsigned char *) lpBuffer + bytes_read++) = HIBYTE(p1_x);
*((unsigned char *) lpBuffer + bytes_read++) = LOBYTE(p1_x);
*((unsigned char *) lpBuffer + bytes_read++) = HIBYTE(p1_y);
*((unsigned char *) lpBuffer + bytes_read++) = LOBYTE(p1_y);
bytes_read += 3;
// fill buffer
*((unsigned char *) lpBuffer + bytes_read++) = 0x75;
*((unsigned char *) lpBuffer + bytes_read++) = 0x75;
*((unsigned char *) lpBuffer + bytes_read++) = 0x75;
break;
}
case 0x0C: // not called in game - empty
// empty data
*((unsigned char *) lpBuffer + bytes_read++) = 0x00;
break;
case 0x10: // get DIP switches
// clear bits
*((unsigned char *) lpBuffer + bytes_read) = 0x00;
// set bits
if (frequency == 1)
*((unsigned char *) lpBuffer + bytes_read) |= 0x80;
if (frequency == 2)
*((unsigned char *) lpBuffer + bytes_read) |= 0x40;
if (frequency == 3)
*((unsigned char *) lpBuffer + bytes_read) |= 0x20;
if (frequency == 4)
*((unsigned char *) lpBuffer + bytes_read) |= 0x10;
break;
case 0x14: // frequency 1
// set frequency and return empty data
frequency = 1;
*((unsigned char *) lpBuffer + bytes_read++) = 0x00;
break;
case 0x18: // frequency 2
// set frequency and return empty data
frequency = 2;
*((unsigned char *) lpBuffer + bytes_read++) = 0x00;
break;
case 0x19: // frequency 3
// set frequency and return empty data
frequency = 3;
*((unsigned char *) lpBuffer + bytes_read++) = 0x00;
break;
case 0x1A: // frequency 4
// set frequency and return empty data
frequency = 4;
*((unsigned char *) lpBuffer + bytes_read++) = 0x00;
break;
case 0x1C: // led board check
// check buffer size
if (nNumberOfBytesToRead < 11)
return 0;
// set all bits
memset(lpBuffer, 0xFF, 10);
bytes_read += 10;
break;
default:
log_warning("metalgear", "unknown opcode: 0x{:02X}", command);
*((unsigned char *) lpBuffer + bytes_read++) = 0x01;
}
// reset command
command = 0;
// calculate checksum
unsigned char checksum = 0xFF;
for (int i = 0; i < bytes_read; i++)
checksum -= *((unsigned char *) lpBuffer + i);
*((unsigned char *) lpBuffer + bytes_read++) = checksum;
// return amount of bytes read
return bytes_read;
}
int games::mga::SpiceGearGunHandle::write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) {
// save command
if (nNumberOfBytesToWrite > 0)
command = *((unsigned char *) lpBuffer);
return (int) nNumberOfBytesToWrite;
}
int games::mga::SpiceGearGunHandle::device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize,
LPVOID lpOutBuffer, DWORD nOutBufferSize) {
return -1;
}
bool games::mga::SpiceGearGunHandle::close() {
log_info("metalgear", "Closed gun device on COM1");
return true;
}

29
games/mga/gunio.h Normal file
View File

@@ -0,0 +1,29 @@
#pragma once
#include "hooks/devicehook.h"
namespace games::mga {
// Team FrontLine GUN IO
class SpiceGearGunHandle : public CustomHandle {
private:
unsigned char version = 62;
unsigned char command = 0x00;
unsigned short p1_x = 0;
unsigned short p1_y = 0;
int frequency = 1;
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;
};
}

73
games/mga/io.cpp Normal file
View File

@@ -0,0 +1,73 @@
#include "io.h"
std::vector<Button> &games::mga::get_buttons() {
static std::vector<Button> buttons;
if (buttons.empty()) {
buttons = GameAPI::Buttons::getButtons("Metal Gear");
GameAPI::Buttons::sortButtons(
&buttons,
"Service",
"Test",
"Coin Mech",
"Start",
"Top",
"Front Top",
"Front Bottom",
"Side Left",
"Side Right",
"Side Lever",
"Trigger Button",
"Switch Button",
"Joy Forwards",
"Joy Backwards",
"Joy Left",
"Joy Right"
);
}
return buttons;
}
std::vector<Analog> &games::mga::get_analogs() {
static std::vector<Analog> analogs;
if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Metal Gear");
GameAPI::Analogs::sortAnalogs(
&analogs,
"Joy X",
"Joy Y"
);
}
return analogs;
}
std::vector<Light> &games::mga::get_lights() {
static std::vector<Light> lights;
if (lights.empty()) {
lights = GameAPI::Lights::getLights("Metal Gear");
GameAPI::Lights::sortLights(
&lights,
"Start",
"Left R",
"Left G",
"Left B",
"Right R",
"Right G",
"Right B",
"Gun R",
"Gun G",
"Gun B",
"Gun Vibration"
);
}
return lights;
}

59
games/mga/io.h Normal file
View File

@@ -0,0 +1,59 @@
#pragma once
#include <vector>
#include "cfg/api.h"
namespace games::mga {
// all buttons in correct order
namespace Buttons {
enum {
Service,
Test,
CoinMech,
Start,
Top,
FrontTop,
FrontBottom,
SideLeft,
SideRight,
SideLever,
TriggerButton,
SwitchButton,
JoyForwards,
JoyBackwards,
JoyLeft,
JoyRight,
};
}
// all analogs in correct order
namespace Analogs {
enum {
JoyX,
JoyY,
};
}
// all lights in correct order
namespace Lights {
enum {
Start,
LeftR,
LeftG,
LeftB,
RightR,
RightG,
RightB,
GunR,
GunG,
GunB,
GunVibration,
};
}
// getters
std::vector<Button> &get_buttons();
std::vector<Analog> &get_analogs();
std::vector<Light> &get_lights();
}

82
games/mga/mga.cpp Normal file
View File

@@ -0,0 +1,82 @@
#include "mga.h"
#include "acio/icca/icca.h"
#include "hooks/devicehook.h"
#include "util/detour.h"
#include "util/libutils.h"
#include "util/logging.h"
#include "util/detour.h"
#include "util/sigscan.h"
#include "gunio.h"
namespace games::mga {
void (__cdecl *ess_eventlog_request_error_orig)(const char *, unsigned int, int, int);
static int __cdecl log_change_level_hook(int level) {
log_misc("mga", "ignoring log_change_level({})", level);
return 1;
}
static uintptr_t __cdecl log_change_output_hook(uintptr_t target, uintptr_t a2) {
log_misc("mga", "ignoring log_change_output(0x{:x}, 0x{:x})", target, a2);
return 0;
}
static bool __cdecl setvolume_hook(uint8_t volume) {
log_misc("mga", "ignoring setvolume {}", static_cast<int>(volume));
return false;
}
static void __cdecl ess_eventlog_request_error_hook(const char *a1, unsigned int a2, int a3, int a4) {
log_info("mga", "ess_eventlog_request_error({}, {}, {}, {})", a1, a2, a3, a4);
//ess_eventlog_request_error_orig(a1, a2, a3, a4);
}
MGAGame::MGAGame() : Game("Metal Gear Arcade") {
}
void MGAGame::attach() {
Game::attach();
// load system.dll (along with the serial functions)
auto system = libutils::load_library("system.dll");
// add the gun
devicehook_init();
devicehook_add(new SpiceGearGunHandle());
// fix ICCA
acio::ICCA_COMPAT_ACTIVE = true;
// ignore sound engine failure
if (!replace_pattern(
system,
"E8????????84C0750A68F4010000E8????????E8????????E8????????3BF3750433C0EB0C",
"??????????????????90909090909090909090????????????????????????????????????",
0, 0))
{
log_warning("mga", "failed to patch sound engine");
}
// hook setvolume
if (!detour::iat_try("?setvolume@@YAHPAD@Z", setvolume_hook)) {
log_warning("mga", "setvolume hook failed");
}
// stop the game from redirecting AVS log calls
detour::iat_try("log_change_level", log_change_level_hook);
detour::iat_try("log_change_output", log_change_output_hook);
ess_eventlog_request_error_orig = detour::iat_try("ess_eventlog_request_error", ess_eventlog_request_error_hook);
}
void MGAGame::detach() {
Game::detach();
devicehook_dispose();
}
}

13
games/mga/mga.h Normal file
View File

@@ -0,0 +1,13 @@
#pragma once
#include "games/game.h"
namespace games::mga {
class MGAGame : public games::Game {
public:
MGAGame();
virtual void attach() override;
virtual void detach() override;
};
}

98
games/museca/io.cpp Normal file
View File

@@ -0,0 +1,98 @@
#include "io.h"
std::vector<Button> &games::museca::get_buttons() {
static std::vector<Button> buttons;
if (buttons.empty()) {
buttons = GameAPI::Buttons::getButtons("Museca");
GameAPI::Buttons::sortButtons(
&buttons,
"Service",
"Test",
"Start",
"Disk1-",
"Disk1+",
"Disk1 Press",
"Disk2-",
"Disk2+",
"Disk2 Press",
"Disk3-",
"Disk3+",
"Disk3 Press",
"Disk4-",
"Disk4+",
"Disk4 Press",
"Disk5-",
"Disk5+",
"Disk5 Press",
"Foot Pedal",
"Analog Slowdown"
);
}
return buttons;
}
std::vector<Analog> &games::museca::get_analogs() {
static std::vector<Analog> analogs;
if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Museca");
GameAPI::Analogs::sortAnalogs(
&analogs,
"Disk1",
"Disk2",
"Disk3",
"Disk4",
"Disk5"
);
}
return analogs;
}
std::vector<Light> &games::museca::get_lights() {
static std::vector<Light> lights;
if (lights.empty()) {
lights = GameAPI::Lights::getLights("Museca");
GameAPI::Lights::sortLights(
&lights,
"Title R",
"Title G",
"Title B",
"Side R",
"Side G",
"Side B",
"Spinner1 R",
"Spinner1 G",
"Spinner1 B",
"Spinner2 R",
"Spinner2 G",
"Spinner2 B",
"Spinner3 R",
"Spinner3 G",
"Spinner3 B",
"Spinner4 R",
"Spinner4 G",
"Spinner4 B",
"Spinner5 R",
"Spinner5 G",
"Spinner5 B",
"Under-LED1 R",
"Under-LED1 G",
"Under-LED1 B",
"Under-LED2 R",
"Under-LED2 G",
"Under-LED2 B",
"Under-LED3 R",
"Under-LED3 G",
"Under-LED3 B"
);
}
return lights;
}

86
games/museca/io.h Normal file
View File

@@ -0,0 +1,86 @@
#pragma once
#include <vector>
#include "cfg/api.h"
namespace games::museca {
// all buttons in correct order
namespace Buttons {
enum {
Service,
Test,
Start,
Disk1Minus,
Disk1Plus,
Disk1Press,
Disk2Minus,
Disk2Plus,
Disk2Press,
Disk3Minus,
Disk3Plus,
Disk3Press,
Disk4Minus,
Disk4Plus,
Disk4Press,
Disk5Minus,
Disk5Plus,
Disk5Press,
FootPedal,
AnalogSlowdown
};
}
// all analogs in correct order
namespace Analogs {
enum {
Disk1,
Disk2,
Disk3,
Disk4,
Disk5
};
}
// all lights in correct order
namespace Lights {
enum {
TitleR,
TitleG,
TitleB,
SideR,
SideG,
SideB,
Spinner1R,
Spinner1G,
Spinner1B,
Spinner2R,
Spinner2G,
Spinner2B,
Spinner3R,
Spinner3G,
Spinner3B,
Spinner4R,
Spinner4G,
Spinner4B,
Spinner5R,
Spinner5G,
Spinner5B,
UnderLED1R,
UnderLED1G,
UnderLED1B,
UnderLED2R,
UnderLED2G,
UnderLED2B,
UnderLED3R,
UnderLED3G,
UnderLED3B
};
}
// getters
std::vector<Button> &get_buttons();
std::vector<Analog> &get_analogs();
std::vector<Light> &get_lights();
}

Some files were not shown because too many files have changed in this diff Show More