Let us start here, from square one. No, from Zero!

This commit is contained in:
smpn2
2022-09-30 22:45:51 +09:00
parent 14215af0d1
commit b7a60638a4
1005 changed files with 303069 additions and 0 deletions
+37
View File
@@ -0,0 +1,37 @@
#include "bbc.h"
#include "avs/game.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();
// 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
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
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
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
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"));
setupapihook_add({
{ 0, 0, 0, 0, },
"",
"USB\\VID_1CCF&PID_8050\\0000",
{},
"COM3",
});
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
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;
};
}
+28
View File
@@ -0,0 +1,28 @@
#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;
}
+27
View File
@@ -0,0 +1,27 @@
#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,
};
}
std::vector<Button> &get_buttons();
}
+104
View File
@@ -0,0 +1,104 @@
#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();
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;
}
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;
}
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
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
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
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
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();
}
+117
View File
@@ -0,0 +1,117 @@
#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 "io.h"
#include "p3io/foot.h"
#include "p3io/p3io.h"
#include "p3io/sate.h"
#include "p3io/usbmem.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::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 {
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));
// 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);
// 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();
}
}
+16
View File
@@ -0,0 +1,16 @@
#pragma once
#include "games/game.h"
namespace games::ddr {
// settings
extern bool SDMODE;
class DDRGame : public games::Game {
public:
DDRGame();
virtual void attach() override;
virtual void detach() override;
};
}
+87
View File
@@ -0,0 +1,87 @@
#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"
);
}
return lights;
}
+82
View File
@@ -0,0 +1,82 @@
#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,
};
}
// getters
std::vector<Button> &get_buttons();
std::vector<Light> &get_lights();
}
+92
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
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
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
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
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
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
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
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;
};
}
+26
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
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
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
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();
}
+409
View File
@@ -0,0 +1,409 @@
#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;
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},
};
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();
}
}
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);
// VR
if (vrutil::STATUS == vrutil::VRStatus::Running) {
start_vr();
}
}
void DRSGame::detach() {
Game::detach();
}
}
+51
View File
@@ -0,0 +1,51 @@
#pragma once
#include "games/game.h"
#include "external/linalg.h"
namespace games::drs {
enum DRS_TOUCH_TYPE {
DRS_DOWN = 0,
DRS_UP = 1,
DRS_MOVE = 2,
};
typedef struct {
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];
void fire_touches(drs_touch_t *events, size_t event_count);
void start_vr();
class DRSGame : public games::Game {
public:
DRSGame();
virtual void attach() override;
virtual void detach() override;
};
}
+41
View File
@@ -0,0 +1,41 @@
#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,
//);
//}
return lights;
}
+36
View File
@@ -0,0 +1,36 @@
#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 {
};
}
// getters
std::vector<Button> &get_buttons();
std::vector<Light> &get_lights();
}
+152
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
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
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
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
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
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
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();
};
}
+206
View File
@@ -0,0 +1,206 @@
#include "gitadora.h"
#include <unordered_map>
#include "hooks/graphics/graphics.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;
}
/*
* 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);
}
/*
* 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::attach() {
Game::attach();
// modules
HMODULE sharepj_module = libutils::try_module("libshare-pj.dll");
HMODULE bmsd_engine_module = libutils::try_module("libbmsd-engine.dll");
HMODULE bmsd_module = libutils::try_module("libbmsd.dll");
HMODULE bmsd2_module = libutils::try_module("libbmsd2.dll");
HMODULE gdme_module = libutils::try_module("libgdme.dll");
HMODULE system_module = libutils::try_module("libsystem.dll");
// patches
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 *) 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"));
// window patch
if (GRAPHICS_WINDOWED && !replace_pattern(
gdme_module,
"754185ED753D8B4118BF0000CB02",
"9090????9090??????????????12", 0, 0))
{
log_warning("gitadora", "windowed mode failed");
}
// 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");
}
}
}
}
+18
View File
@@ -0,0 +1,18 @@
#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 attach();
};
}
+118
View File
@@ -0,0 +1,118 @@
#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"
);
}
return lights;
}
+108
View File
@@ -0,0 +1,108 @@
#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 {
enum {
GuitarP1Motor,
GuitarP2Motor,
};
}
// getters
std::vector<Button> &get_buttons();
std::vector<Analog> &get_analogs();
std::vector<Light> &get_lights();
}
+38
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
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
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
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();
}
+241
View File
@@ -0,0 +1,241 @@
#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 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
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;
};
}
+248
View File
@@ -0,0 +1,248 @@
#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 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
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;
};
}
+561
View File
@@ -0,0 +1,561 @@
#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"
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;
float avg_mult;
TapeLedMapping(size_t data_size, int index_r, int index_g, int index_b)
: data_size(data_size), index_r(index_r), index_g(index_g), index_b(index_b) {
avg_mult = 1.f / (data_size * 255);
}
} 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 (index < std::size(mapping)) {
auto &map = mapping[index];
// calculate average color
size_t avg_ri = 0;
size_t avg_gi = 0;
size_t avg_bi = 0;
for (size_t i = 0; i < map.data_size; i++) {
auto color = &data[i * 3];
avg_ri += color[0];
avg_gi += color[1];
avg_bi += color[2];
}
float avg_r = avg_ri * map.avg_mult;
float avg_g = avg_gi * map.avg_mult;
float avg_b = avg_bi * map.avg_mult;
// set average color
auto &lights = get_lights();
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_r], avg_r);
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_g], avg_g);
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_b], avg_b);
}
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
View File
@@ -0,0 +1,6 @@
#pragma once
namespace games::iidx {
void bi2x_hook_init();
}
+277
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
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;
};
}
+796
View File
@@ -0,0 +1,796 @@
// set version to Windows 7 to enable Media Foundation functions
#ifdef _WIN32_WINNT
#undef _WIN32_WINNT
#endif
#define _WIN32_WINNT 0x0601
// turn on C-style COM objects
#define CINTERFACE
#include "iidx.h"
#include <mfobjects.h>
#include <mfidl.h>
#include "acioemu/handle.h"
#include "avs/core.h"
#include "avs/game.h"
#include "games/io.h"
#include "hooks/avshook.h"
#include "hooks/cfgmgr32hook.h"
#include "hooks/devicehook.h"
#include "hooks/graphics/graphics.h"
#include "hooks/setupapihook.h"
#include "hooks/sleephook.h"
#include "launcher/options.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/utils.h"
#include "bi2a.h"
#include "bi2x_hook.h"
#include "ezusb.h"
#include "io.h"
static VTBL_TYPE(IMFActivate, GetAllocatedString) GetAllocatedString_orig = nullptr;
static decltype(MFEnumDeviceSources) *MFEnumDeviceSources_orig = nullptr;
static decltype(RegCloseKey) *RegCloseKey_orig = nullptr;
static decltype(RegEnumKeyA) *RegEnumKeyA_orig = nullptr;
static decltype(RegOpenKeyA) *RegOpenKeyA_orig = nullptr;
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_MODE = false;
std::optional<std::string> SOUND_OUTPUT_DEVICE = std::nullopt;
std::optional<std::string> ASIO_DRIVER = 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 uint16_t IIDXIO_TT_DIRECTION[2]{};
static bool IIDXIO_TT_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);
}
/*
* Dirty Workaround for IO Device
* Game gets registry object via SetupDiOpenDevRegKey, then looks up "PortName" via RegQueryValueExA
* We ignore the first and just cheat on the second.
*/
static LONG WINAPI RegQueryValueExA_hook(HKEY hKey, LPCTSTR lpValueName, LPDWORD lpReserved, LPDWORD lpType,
LPBYTE lpData, LPDWORD lpcbData)
{
if (lpValueName != nullptr && lpData != nullptr && lpcbData != nullptr) {
if (hKey == DEVICE_ASIO_REG_HANDLE && ASIO_DRIVER.has_value()) {
log_info("iidx::asio", "RegQueryValueExA({}, \"{}\")", fmt::ptr((void *) hKey), lpValueName);
if (_stricmp(lpValueName, "Description") == 0) {
memcpy(lpData, ASIO_DRIVER.value().c_str(), ASIO_DRIVER.value().size() + 1);
return ERROR_SUCCESS;
} else {
hKey = real_asio_device_reg_handle;
}
}
// 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);
}
/*
* Camera related stuff
*/
static std::wstring CAMERA0_ID;
static std::wstring CAMERA1_ID;
static HRESULT WINAPI GetAllocatedString_hook(IMFActivate* This, REFGUID guidKey, LPWSTR *ppwszValue,
UINT32 *pcchLength)
{
// call the original
HRESULT result = GetAllocatedString_orig(This, guidKey, ppwszValue, pcchLength);
// log the cam name
log_misc("iidx::cam", "obtained {}", ws2s(*ppwszValue));
// try first camera
wchar_t *pwc = nullptr;
if (CAMERA0_ID.length() == 23) {
pwc = wcsstr(*ppwszValue, CAMERA0_ID.c_str());
}
// try second camera if first wasn't found
if (!pwc && CAMERA1_ID.length() == 23) {
pwc = wcsstr(*ppwszValue, CAMERA1_ID.c_str());
}
// check if camera could be identified
if (pwc) {
// fake the USB IDs
pwc[4] = L'2';
pwc[5] = L'8';
pwc[6] = L'8';
pwc[7] = L'c';
pwc[13] = L'0';
pwc[14] = L'0';
pwc[15] = L'0';
pwc[16] = L'2';
pwc[21] = L'0';
pwc[22] = L'0';
log_misc("iidx::cam", "replaced {}", ws2s(*ppwszValue));
}
// return original result
return result;
}
static void hook_camera_vtable(IMFActivate *camera) {
// hook allocated string method for camera identification
memutils::VProtectGuard camera_guard(camera->lpVtbl);
camera->lpVtbl->GetAllocatedString = GetAllocatedString_hook;
}
static void hook_camera(size_t no, std::wstring camera_id, std::string camera_instance) {
// logic based on camera no
if (no == 0) {
// don't hook if camera 0 is already hooked
if (CAMERA0_ID.length() > 0) {
return;
}
// save the camera ID
CAMERA0_ID = camera_id;
// cfgmgr hook
CFGMGR32_HOOK_SETTING camera_setting;
camera_setting.device_instance = 0xDEADBEEF;
camera_setting.parent_instance = ~camera_setting.device_instance;
camera_setting.device_id = "USB\\VEN_1022&DEV_7908";
camera_setting.device_node_id = "USB\\VID_288C&PID_0002&MI_00\\?&????????&?&????";
if (camera_instance.length() == 17) {
for (int i = 0; i < 17; i++) {
camera_setting.device_node_id[28 + i] = camera_instance[i];
}
}
cfgmgr32hook_add(camera_setting);
log_info("iidx::cam", "hooked camera 1 @ {}", ws2s(camera_id));
}
else if (no == 1) {
// don't hook if camera 1 is already hooked
if (CAMERA1_ID.length() > 0) {
return;
}
// save the camera ID
CAMERA1_ID = camera_id;
// cfgmgr hook
CFGMGR32_HOOK_SETTING camera_setting;
camera_setting.device_instance = 0xBEEFDEAD;
camera_setting.parent_instance = ~camera_setting.device_instance;
camera_setting.device_id = "USB\\VEN_1022&DEV_7914";
camera_setting.device_node_id = "USB\\VID_288C&PID_0002&MI_00\\?&????????&?&????";
if (camera_instance.length() == 17) {
for (int i = 0; i < 17; i++) {
camera_setting.device_node_id[28 + i] = camera_instance[i];
}
}
cfgmgr32hook_add(camera_setting);
log_info("iidx::cam", "hooked camera 2 @ {}", ws2s(camera_id));
}
}
static HRESULT WINAPI MFEnumDeviceSources_hook(IMFAttributes *pAttributes, IMFActivate ***pppSourceActivate,
UINT32 *pcSourceActivate) {
// call original function
HRESULT result_orig = MFEnumDeviceSources_orig(pAttributes, pppSourceActivate, pcSourceActivate);
// check for capture devices
if (FAILED(result_orig) || !*pcSourceActivate) {
return result_orig;
}
// iterate cameras
size_t cam_hook_num = 0;
for (size_t cam_num = 0; cam_num < *pcSourceActivate && cam_hook_num < 2; cam_num++) {
// flip
size_t cam_num_flipped = cam_num;
if (FLIP_CAMS) {
cam_num_flipped = *pcSourceActivate - cam_num - 1;
}
// get camera
IMFActivate *camera = (*pppSourceActivate)[cam_num_flipped];
// save original method for later use
if (GetAllocatedString_orig == nullptr) {
GetAllocatedString_orig = camera->lpVtbl->GetAllocatedString;
}
// hook allocated string method for camera identification
hook_camera_vtable(camera);
// get camera link
LPWSTR camera_link_lpwstr;
UINT32 camera_link_length;
if (SUCCEEDED(GetAllocatedString_orig(
camera,
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
&camera_link_lpwstr,
&camera_link_length))) {
// cut name to make ID
std::wstring camera_link_ws = std::wstring(camera_link_lpwstr);
std::wstring camera_id = camera_link_ws.substr(8, 23);
log_info("iidx::cam", "found video capture device: {}", ws2s(camera_link_ws));
// only support cameras that start with \\?\usb
if (!string_begins_with(camera_link_ws, L"\\\\?\\usb")) {
continue;
}
// get camera instance
std::string camera_link = ws2s(camera_link_ws);
std::string camera_instance = camera_link.substr(32, 17);
// hook the camera
hook_camera(cam_hook_num, camera_id, camera_instance);
// increase camera hook number
cam_hook_num++;
} else {
log_warning("iidx::cam", "failed to open camera {}", cam_num_flipped);
}
}
// return result
return result_orig;
}
static bool log_hook(void *user, const std::string &data, logger::Style style, std::string &out) {
// get rid of the log spam
if (data.find(" I:graphic: adapter mode ") != 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();
// 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("D:\\001rom.txt") == "TDJ";
// force TDJ mode
if (TDJ_MODE) {
// ensure game starts in the desired mode
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);
// hook touch window
wintouchemu::FORCE = 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) {
// camera media framework hook
MFEnumDeviceSources_orig = detour::iat_try(
"MFEnumDeviceSources", MFEnumDeviceSources_hook, avs::game::DLL_INSTANCE);
// camera settings
SETUPAPI_SETTINGS settings3 {};
settings3.class_guid[0] = 0x00000000;
settings3.class_guid[1] = 0x00000000;
settings3.class_guid[2] = 0x00000000;
settings3.class_guid[3] = 0x00000000;
const char property3[] = "USB Composite Device";
memcpy(settings3.property_devicedesc, property3, sizeof(property3));
settings3.property_address[0] = 1;
settings3.property_address[1] = 7;
setupapihook_add(settings3);
}
// init 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 (SOUND_OUTPUT_DEVICE.has_value()) {
SetEnvironmentVariable("SOUND_OUTPUT_DEVICE", SOUND_OUTPUT_DEVICE.value().c_str());
}
}
void IIDXGame::detach() {
Game::detach();
devicehook_dispose();
}
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) 1 : (uint16_t) 4;
// 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));
// TT+
if (ttp)
IIDXIO_TT_STATE[player] += change;
// TT-
if (ttm)
IIDXIO_TT_STATE[player] -= change;
// TT+/-
if (ttpm) {
if (IIDXIO_TT_DIRECTION[player] != 0u) {
if (!IIDXIO_TT_PRESSED[player])
IIDXIO_TT_DIRECTION[player] = 0;
IIDXIO_TT_STATE[player] += change;
} else {
if (!IIDXIO_TT_PRESSED[player])
IIDXIO_TT_DIRECTION[player] = 1;
IIDXIO_TT_STATE[player] -= change;
}
IIDXIO_TT_PRESSED[player] = true;
} else
IIDXIO_TT_PRESSED[player] = false;
// 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;
}
}
+41
View File
@@ -0,0 +1,41 @@
#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_MODE;
extern std::optional<std::string> SOUND_OUTPUT_DEVICE;
extern std::optional<std::string> ASIO_DRIVER;
// 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;
};
// 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();
}
+173
View File
@@ -0,0 +1,173 @@
#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 Start",
"P2 1",
"P2 2",
"P2 3",
"P2 4",
"P2 5",
"P2 6",
"P2 7",
"P2 TT+",
"P2 TT-",
"P2 TT+/-",
"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;
}
+161
View File
@@ -0,0 +1,161 @@
#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_Start,
P2_1,
P2_2,
P2_3,
P2_4,
P2_5,
P2_6,
P2_7,
P2_TTPlus,
P2_TTMinus,
P2_TTPlusMinus,
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();
}
+492
View File
@@ -0,0 +1,492 @@
#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"
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() });
file_hints[bc].emplace_back("kamunity.dll");
}
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("Insert Coin");
vkey_defaults.push_back(VK_F1);
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 Sub Screen");
vkey_defaults.push_back(VK_PRIOR);
names.emplace_back("Toggle VR Control");
vkey_defaults.push_back(0xFF);
names.emplace_back("Toggle Overlay");
vkey_defaults.push_back(VK_F12);
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("Screenshot");
vkey_defaults.push_back(VK_SNAPSHOT);
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);
names.emplace_back("Screen Resize");
vkey_defaults.push_back(0xFF);
names.emplace_back("Toggle Screen Resize");
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;
}
}
+63
View File
@@ -0,0 +1,63 @@
#pragma once
#include <vector>
#include "cfg/api.h"
namespace games {
namespace OverlayButtons {
enum {
InsertCoin,
ToggleConfig,
ToggleVirtualKeypadP1,
ToggleVirtualKeypadP2,
ToggleCardManager,
ToggleLog,
ToggleControl,
TogglePatchManager,
ToggleSubScreen,
ToggleVRControl,
ToggleOverlay,
NavigatorActivate,
NavigatorCancel,
NavigatorUp,
NavigatorDown,
NavigatorLeft,
NavigatorRight,
Screenshot,
HotkeyEnable1,
HotkeyEnable2,
HotkeyToggle,
ScreenResize,
ToggleScreenResize,
};
}
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
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
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();
}
+145
View File
@@ -0,0 +1,145 @@
#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"
namespace games::jb {
// touch stuff
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
TOUCH_POINTS.clear();
touch_get_points(TOUCH_POINTS);
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;
}
}
}
/*
* 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;
}
}
+17
View File
@@ -0,0 +1,17 @@
#pragma once
#include "games/game.h"
namespace games::jb {
// touch stuff
extern bool TOUCH_STATE[16];
void touch_update();
class JBGame : public games::Game {
public:
JBGame();
virtual void attach() override;
virtual void detach() override;
};
}
+38
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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();
}
+191
View File
@@ -0,0 +1,191 @@
#include "io.h"
std::vector<Button> &games::nost::get_buttons() {
static std::vector<Button> buttons;
if (buttons.empty()) {
buttons = GameAPI::Buttons::getButtons("Nostalgia");
GameAPI::Buttons::sortButtons(
&buttons,
"Service",
"Test",
"Coin Mech",
"Key 1",
"Key 2",
"Key 3",
"Key 4",
"Key 5",
"Key 6",
"Key 7",
"Key 8",
"Key 9",
"Key 10",
"Key 11",
"Key 12",
"Key 13",
"Key 14",
"Key 15",
"Key 16",
"Key 17",
"Key 18",
"Key 19",
"Key 20",
"Key 21",
"Key 22",
"Key 23",
"Key 24",
"Key 25",
"Key 26",
"Key 27",
"Key 28"
);
}
return buttons;
}
std::vector<Analog> &games::nost::get_analogs() {
static std::vector<Analog> analogs;
if (analogs.empty()) {
analogs = GameAPI::Analogs::getAnalogs("Nostalgia");
GameAPI::Analogs::sortAnalogs(
&analogs,
"Key 1",
"Key 2",
"Key 3",
"Key 4",
"Key 5",
"Key 6",
"Key 7",
"Key 8",
"Key 9",
"Key 10",
"Key 11",
"Key 12",
"Key 13",
"Key 14",
"Key 15",
"Key 16",
"Key 17",
"Key 18",
"Key 19",
"Key 20",
"Key 21",
"Key 22",
"Key 23",
"Key 24",
"Key 25",
"Key 26",
"Key 27",
"Key 28"
);
}
return analogs;
}
std::vector<Light> &games::nost::get_lights() {
static std::vector<Light> lights;
if (lights.empty()) {
lights = GameAPI::Lights::getLights("Nostalgia");
GameAPI::Lights::sortLights(
&lights,
"Title R",
"Title G",
"Title B",
"Bottom R",
"Bottom G",
"Bottom B",
"Key 1 R",
"Key 1 G",
"Key 1 B",
"Key 2 R",
"Key 2 G",
"Key 2 B",
"Key 3 R",
"Key 3 G",
"Key 3 B",
"Key 4 R",
"Key 4 G",
"Key 4 B",
"Key 5 R",
"Key 5 G",
"Key 5 B",
"Key 6 R",
"Key 6 G",
"Key 6 B",
"Key 7 R",
"Key 7 G",
"Key 7 B",
"Key 8 R",
"Key 8 G",
"Key 8 B",
"Key 9 R",
"Key 9 G",
"Key 9 B",
"Key 10 R",
"Key 10 G",
"Key 10 B",
"Key 11 R",
"Key 11 G",
"Key 11 B",
"Key 12 R",
"Key 12 G",
"Key 12 B",
"Key 13 R",
"Key 13 G",
"Key 13 B",
"Key 14 R",
"Key 14 G",
"Key 14 B",
"Key 15 R",
"Key 15 G",
"Key 15 B",
"Key 16 R",
"Key 16 G",
"Key 16 B",
"Key 17 R",
"Key 17 G",
"Key 17 B",
"Key 18 R",
"Key 18 G",
"Key 18 B",
"Key 19 R",
"Key 19 G",
"Key 19 B",
"Key 20 R",
"Key 20 G",
"Key 20 B",
"Key 21 R",
"Key 21 G",
"Key 21 B",
"Key 22 R",
"Key 22 G",
"Key 22 B",
"Key 23 R",
"Key 23 G",
"Key 23 B",
"Key 24 R",
"Key 24 G",
"Key 24 B",
"Key 25 R",
"Key 25 G",
"Key 25 B",
"Key 26 R",
"Key 26 G",
"Key 26 B",
"Key 27 R",
"Key 27 G",
"Key 27 B",
"Key 28 R",
"Key 28 G",
"Key 28 B"
);
}
return lights;
}
+179
View File
@@ -0,0 +1,179 @@
#pragma once
#include <vector>
#include "cfg/api.h"
namespace games::nost {
// all buttons in correct order
namespace Buttons {
enum {
Service,
Test,
CoinMech,
Key1,
Key2,
Key3,
Key4,
Key5,
Key6,
Key7,
Key8,
Key9,
Key10,
Key11,
Key12,
Key13,
Key14,
Key15,
Key16,
Key17,
Key18,
Key19,
Key20,
Key21,
Key22,
Key23,
Key24,
Key25,
Key26,
Key27,
Key28,
};
}
// all analogs in correct order
namespace Analogs {
enum {
Key1,
Key2,
Key3,
Key4,
Key5,
Key6,
Key7,
Key8,
Key9,
Key10,
Key11,
Key12,
Key13,
Key14,
Key15,
Key16,
Key17,
Key18,
Key19,
Key20,
Key21,
Key22,
Key23,
Key24,
Key25,
Key26,
Key27,
Key28,
};
}
// all lights in correct order
namespace Lights {
enum {
TitleR,
TitleG,
TitleB,
BottomR,
BottomG,
BottomB,
Key1R,
Key1G,
Key1B,
Key2R,
Key2G,
Key2B,
Key3R,
Key3G,
Key3B,
Key4R,
Key4G,
Key4B,
Key5R,
Key5G,
Key5B,
Key6R,
Key6G,
Key6B,
Key7R,
Key7G,
Key7B,
Key8R,
Key8G,
Key8B,
Key9R,
Key9G,
Key9B,
Key10R,
Key10G,
Key10B,
Key11R,
Key11G,
Key11B,
Key12R,
Key12G,
Key12B,
Key13R,
Key13G,
Key13B,
Key14R,
Key14G,
Key14B,
Key15R,
Key15G,
Key15B,
Key16R,
Key16G,
Key16B,
Key17R,
Key17G,
Key17B,
Key18R,
Key18G,
Key18B,
Key19R,
Key19G,
Key19B,
Key20R,
Key20G,
Key20B,
Key21R,
Key21G,
Key21B,
Key22R,
Key22G,
Key22B,
Key23R,
Key23G,
Key23B,
Key24R,
Key24G,
Key24B,
Key25R,
Key25G,
Key25B,
Key26R,
Key26G,
Key26B,
Key27R,
Key27G,
Key27B,
Key28R,
Key28G,
Key28B,
};
}
// getters
std::vector<Button> &get_buttons();
std::vector<Analog> &get_analogs();
std::vector<Light> &get_lights();
}
+46
View File
@@ -0,0 +1,46 @@
// enable touch functions - set version to windows 7
// mingw otherwise doesn't load touch stuff
#define _WIN32_WINNT 0x0601
#include "nost.h"
#include "hooks/setupapihook.h"
#include "misc/wintouchemu.h"
#include "avs/game.h"
namespace games::nost {
NostGame::NostGame() : Game("Nostalgia") {
}
void NostGame::attach() {
Game::attach();
// fake touchscreen
///////////////////
SETUPAPI_SETTINGS touch_settings{};
// GUID must be set to 0 to make SETUPAPI hook working
touch_settings.class_guid[0] = 0x00000000;
touch_settings.class_guid[1] = 0x00000000;
touch_settings.class_guid[2] = 0x00000000;
touch_settings.class_guid[3] = 0x00000000;
/*
* set hardware ID containing VID and PID
* there is 3 known VID/PID combinations known to the game:
* VID_04DD&PID_97CB
* VID_0EEF&PID_C000
* VID_29BD&PID_4101
*/
const char touch_hardwareid[] = "VID_29BD&PID_4101";
memcpy(touch_settings.property_hardwareid, touch_hardwareid, sizeof(touch_hardwareid));
// apply settings
setupapihook_init(avs::game::DLL_INSTANCE);
setupapihook_add(touch_settings);
// custom touch
wintouchemu::hook("nostalgia", avs::game::DLL_INSTANCE);
}
}
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include "games/game.h"
namespace games::nost {
class NostGame : public games::Game {
public:
NostGame();
virtual void attach() override;
};
}
+19
View File
@@ -0,0 +1,19 @@
#include "io.h"
std::vector<Button> &games::onpara::get_buttons() {
static std::vector<Button> buttons;
if (buttons.empty()) {
buttons = GameAPI::Buttons::getButtons("Ongaku Paradise");
GameAPI::Buttons::sortButtons(
&buttons,
"Service",
"Test",
"Start",
"Headphone"
);
}
return buttons;
}
+17
View File
@@ -0,0 +1,17 @@
#pragma once
#include <vector>
#include "cfg/api.h"
namespace games::onpara {
namespace Buttons {
enum {
Service,
Test,
Start,
Headphone,
};
}
std::vector<Button> &get_buttons();
}
+74
View File
@@ -0,0 +1,74 @@
#include "onpara.h"
#include "avs/game.h"
#include "util/detour.h"
#include "util/utils.h"
#include "util/libutils.h"
#include "util/sigscan.h"
#include "hooks/devicehook.h"
#include "hooks/sleephook.h"
#include "hooks/graphics/graphics.h"
#include "touch/touch.h"
#include "touchpanel.h"
#include "westboard.h"
static decltype(BuildCommDCBA)* orig_BuildCommDCBA = BuildCommDCBA;
games::onpara::OnparaGame::OnparaGame() : games::Game("Ongaku Paradise") { }
static BOOL WINAPI my_BuildCommDCBA(LPCSTR lpDef, LPDCB lpDCB) {
if (lpDef == nullptr || lpDCB == nullptr) {
return orig_BuildCommDCBA(lpDef, lpDCB);
}
log_misc("onpara", "BuildCommDCBA({})", lpDef);
// remove the device name if present
if (string_begins_with(lpDef, "COM") && std::strlen(lpDef) >= 5) {
lpDef += 6;
}
return orig_BuildCommDCBA(lpDef, lpDCB);
}
static int __cdecl setvolume_and_setequalizer_stub(const char*) {
// log_misc("onpara", "setvolume_and_setequalizer_stub()");
return 0;
}
void games::onpara::OnparaGame::attach() {
Game::attach();
// make the game start faster
auto gamejc9_module = libutils::try_library("gamejc9.dll");
if (gamejc9_module != nullptr) {
hooks::sleep::init(29999, 1, gamejc9_module);
}
// attach touch handler
log_info("onpara", "attach touch handler");
touch_attach_dx_hook();
if (GRAPHICS_SHOW_CURSOR) {
ShowCursor(1);
}
// patch the CRC check
// TODO: I don't like patching, make this not a patch
replace_pattern(avs::game::DLL_INSTANCE, "8D4C240851E8????????83C404", "8D4C240851B80000000083C404", 0, 0);
detour::iat_try("BuildCommDCBA", my_BuildCommDCBA);
// load this now so that we can hook its dependencies
libutils::try_library("gamejc9.dll");
// stub annoying functions
detour::iat_try("?setvolume@@YAHPAD@Z", setvolume_and_setequalizer_stub);
detour::iat_try("SetEqualizer", setvolume_and_setequalizer_stub);
devicehook_init(avs::game::DLL_INSTANCE);
devicehook_add(new TouchPanelHandle());
devicehook_add(new WestBoardHandle());
}
void games::onpara::OnparaGame::detach() {
Game::detach();
}
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include "games/game.h"
namespace games::onpara {
class OnparaGame : public games::Game {
public:
OnparaGame();
virtual void attach() override;
virtual void detach() override;
};
}
+136
View File
@@ -0,0 +1,136 @@
#include <thread>
#include <chrono>
#include "touchpanel.h"
#include "util/logging.h"
#include "touch/touch.h"
using namespace std::chrono_literals;
void games::onpara::TouchPanelHandle::set_state(touch_panel_state state) {
state_ = state;
// reset the output queue as well for good measure
output_queue_ = {};
}
void games::onpara::TouchPanelHandle::enqueue_packet(touch_panel_message const &message) {
// push magic
output_queue_.push('U');
// push message
for (auto b : message.raw) {
output_queue_.push(b);
}
// push checksum
output_queue_.push(message.raw[0] +
message.raw[1] +
message.raw[2] +
message.raw[3] +
message.raw[4] +
message.raw[5] +
message.raw[6] +
message.raw[7] - 1);
}
bool games::onpara::TouchPanelHandle::open(LPCWSTR lpFileName) {
if (wcscmp(lpFileName, L"COM1: baud=9600 parity=N data=8 stop=1") != 0) {
return false;
}
log_info("touchpanel", "Opened COM1 (Touch Panel)");
return true;
}
int games::onpara::TouchPanelHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) {
DWORD i;
auto buffer = reinterpret_cast<uint8_t *>(lpBuffer);
if (state_ == TOUCH_PANEL_STATE_REPORT && output_queue_.empty()) {
// create a new status report message
touch_panel_message report = {
'T', 'K', 0, 0, 0
};
std::vector<TouchPoint> touch_points;
touch_get_points(touch_points);
if (!touch_points.empty()) {
auto &touch_point = touch_points[0];
report.x = static_cast<uint16_t>(touch_point.x);
report.y = 768 - static_cast<uint16_t>(touch_point.y);
report.z = ~0;
}
enqueue_packet(report);
// prevent cpu bullying
std::this_thread::sleep_for(1ms);
}
// copy from output queue
for (i = 0; i < nNumberOfBytesToRead && !output_queue_.empty(); i++) {
buffer[i] = output_queue_.front();
output_queue_.pop();
}
return i;
}
int games::onpara::TouchPanelHandle::write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) {
// static messages
static const touch_panel_message acknowledge_message = {
'A', 0, 0, 0, 0
};
static const touch_panel_message diagnostics_message = {
'D', 0, 0, 0, 0
};
// make sure that we received the correct amount of data
if (nNumberOfBytesToWrite != 10) {
log_warning("touchscreen", "invalid packet size");
return 0;
}
auto op = reinterpret_cast<const char *>(lpBuffer)[1];
switch (op) {
case 'R': // reset
set_state(TOUCH_PANEL_STATE_INACTIVE);
break;
case 'a': // acknowledge
if (state_ == TOUCH_PANEL_STATE_INACTIVE) {
set_state(TOUCH_PANEL_STATE_ACKNOWLEDGE);
enqueue_packet(acknowledge_message);
}
else if (state_ == TOUCH_PANEL_STATE_DIAGNOSTICS) {
// start reporting
set_state(TOUCH_PANEL_STATE_REPORT);
}
break;
case 'D': // diagnositcs
set_state(TOUCH_PANEL_STATE_DIAGNOSTICS);
enqueue_packet(diagnostics_message);
break;
default:
log_warning("touchpanel", "invalid operation: {:c}", op);
break;
}
return nNumberOfBytesToWrite;
}
int games::onpara::TouchPanelHandle::device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize,
LPVOID lpOutBuffer, DWORD nOutBufferSize) {
return -1;
}
bool games::onpara::TouchPanelHandle::close() {
log_info("touchpanel", "Closed COM1 (Touch Panel)");
return true;
}
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#include <cstdint>
#include <queue>
#include "hooks/devicehook.h"
namespace games::onpara {
class TouchPanelHandle : public CustomHandle {
private:
union touch_panel_message {
struct {
char op;
char something;
uint16_t x;
uint16_t y;
uint16_t z;
};
char raw[8];
};
enum touch_panel_state {
TOUCH_PANEL_STATE_INACTIVE,
TOUCH_PANEL_STATE_ACKNOWLEDGE,
TOUCH_PANEL_STATE_DIAGNOSTICS,
TOUCH_PANEL_STATE_REPORT
};
touch_panel_state state_ = TOUCH_PANEL_STATE_INACTIVE;
std::queue<uint8_t> output_queue_;
void set_state(touch_panel_state state);
void enqueue_packet(touch_panel_message const &message);
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;
};
}
+166
View File
@@ -0,0 +1,166 @@
#include "westboard.h"
#include "io.h"
#include "util/logging.h"
#include "util/secplug.h"
#include "misc/eamuse.h"
using namespace acioemu;
games::onpara::WestBoardHandle::WestBoardDevice::WestBoardDevice() {
/*
* Node 1 -> Buttons
* Node 2 -> Card Reader
* Node 3-5 -> Lights
*/
this->node_count = 5;
}
games::onpara::WestBoardHandle::WestBoardHandle() {
acio_emu_.add_device(new games::onpara::WestBoardHandle::WestBoardDevice);
}
bool games::onpara::WestBoardHandle::WestBoardDevice::parse_msg(MessageData *msg_in, circular_buffer<uint8_t> *response_buffer) {
response_ = {};
switch (msg_in->cmd.code) {
case ACIO_CMD_GET_VERSION:
set_version(&response_, ':', '^', ')', ':', '(', ":^)");
response_.cmd.data_size = MSG_VERSION_SIZE;
break;
case ACIO_WESTBOARD_CMD_LOAD_SECPLUG_MODEL:
if (msg_in->cmd.raw[0] == SECURITY_PLUG_LICENSE) {
secplug_model_ = secplug::encode_secplug_model("GQJC9JAA");
}
else if (msg_in->cmd.raw[0] == SECURITY_PLUG_ACCOUNT) {
secplug_model_ = secplug::encode_secplug_model("@@@@@@@@");
}
else {
log_warning("westboard", "invalid security plug type: {}", msg_in->cmd.raw[0]);
}
// empty response body
response_.cmd.data_size = 0;
break;
case ACIO_WESTBOARD_CMD_QUERY_SECPLUG:
// write security plug id
response_.cmd.raw[3] = 4;
response_.cmd.raw[4] = 3;
response_.cmd.raw[5] = 4;
response_.cmd.raw[6] = 3;
// write security plug model
std::copy(secplug_model_.begin(), secplug_model_.end(), response_.cmd.raw + 7);
response_.cmd.data_size = 33;
break;
case ACIO_WESTBOARD_CMD_POLL_BUTTONS_0:
case ACIO_WESTBOARD_CMD_POLL_BUTTONS_1: {
auto &buttons = games::onpara::get_buttons();
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::onpara::Buttons::Test])) {
response_.cmd.raw[1] |= 1 << 5;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::onpara::Buttons::Service])) {
response_.cmd.raw[1] |= 1 << 4;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::onpara::Buttons::Start])) {
response_.cmd.raw[8] |= 1;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::onpara::Buttons::Headphone])) {
response_.cmd.raw[8] |= 1 << 1;
}
response_.cmd.data_size = 15;
break;
}
case ACIO_WESTBOARD_CMD_ENABLE_CARD_READER:
// send status "1"
response_.cmd.status = 1;
response_.cmd.data_size = 1;
break;
case ACIO_WESTBOARD_CMD_POLL_CARD_READER:
response_.cmd.data_size = 24;
response_.cmd.raw[0] = 2;
if (eamuse_card_insert_consume(1, 0)) {
eamuse_get_card(1, 0, &response_.cmd.raw[2]);
}
break;
case ACIO_WESTBOARD_CMD_LOAD_SECPLUG_ID:
case ACIO_WESTBOARD_CMD_SET_LIGHTS:
// empty response body
response_.cmd.data_size = 0;
break;
case ACIO_CMD_STARTUP:
case ACIO_CMD_CLEAR:
case ACIO_WESTBOARD_CMD_EXECUTE:
// send status "0"
response_.cmd.status = 0;
response_.cmd.data_size = 1;
break;
default:
log_warning("westboard", "unknown cmd: {}", msg_in->cmd.code);
return false;
}
// set acio header
set_header(&response_, msg_in->addr, msg_in->cmd.code, msg_in->cmd.pid, response_.cmd.data_size);
// write message
write_msg(&response_, response_buffer);
return true;
}
bool games::onpara::WestBoardHandle::open(LPCWSTR lpFileName) {
if (wcscmp(lpFileName, L"COM2: baud=57600 parity=N data=8 stop=1") != 0){
return false;
}
log_info("westboard", "Opened COM2 (West Board)");
return true;
}
int games::onpara::WestBoardHandle::write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) {
auto buffer = reinterpret_cast<const uint8_t *>(lpBuffer);
for (DWORD i = 0; i < nNumberOfBytesToWrite; i++) {
acio_emu_.write(buffer[i]);
}
return nNumberOfBytesToWrite;
}
int games::onpara::WestBoardHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) {
auto buffer = reinterpret_cast<uint8_t *>(lpBuffer);
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 bytes_read;
}
int games::onpara::WestBoardHandle::device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize,
LPVOID lpOutBuffer, DWORD nOutBufferSize) {
return -1;
}
bool games::onpara::WestBoardHandle::close() {
log_info("westboard", "Closed COM2 (West Board)");
return true;
}
+49
View File
@@ -0,0 +1,49 @@
#pragma once
#include <array>
#include "hooks/devicehook.h"
#include "acioemu/acioemu.h"
namespace games::onpara {
class WestBoardHandle : public CustomHandle {
private:
acioemu::ACIOEmu acio_emu_;
class WestBoardDevice : public acioemu::ACIODeviceEmu {
private:
enum security_plug {
SECURITY_PLUG_LICENSE,
SECURITY_PLUG_ACCOUNT,
};
enum acio_westboard_cmd {
ACIO_WESTBOARD_CMD_SET_LIGHTS = 0x0112,
ACIO_WESTBOARD_CMD_EXECUTE = 0x0130,
ACIO_WESTBOARD_CMD_POLL_BUTTONS_0 = 0x0111,
ACIO_WESTBOARD_CMD_POLL_BUTTONS_1 = 0x0113,
ACIO_WESTBOARD_CMD_ENABLE_CARD_READER = 0x0131,
ACIO_WESTBOARD_CMD_POLL_CARD_READER = 0x0134,
ACIO_WESTBOARD_CMD_LOAD_SECPLUG_ID = 0x0140,
ACIO_WESTBOARD_CMD_LOAD_SECPLUG_MODEL = 0x0141,
ACIO_WESTBOARD_CMD_QUERY_SECPLUG = 0x0148
};
std::array<uint8_t, 6> secplug_model_;
acioemu::MessageData response_;
public:
WestBoardDevice();
bool parse_msg(acioemu::MessageData *msg_in, circular_buffer<uint8_t> *response_buffer) override;
};
public:
WestBoardHandle();
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;
};
}
+40
View File
@@ -0,0 +1,40 @@
#include "io.h"
std::vector<Button> &games::otoca::get_buttons() {
static std::vector<Button> buttons;
if (buttons.empty()) {
buttons = GameAPI::Buttons::getButtons("Otoca D'or");
GameAPI::Buttons::sortButtons(
&buttons,
"Service",
"Test",
"Coin Mech",
"Button Left",
"Button Right",
"Lever Up",
"Lever Down",
"Lever Left",
"Lever Right"
);
}
return buttons;
}
std::vector<Light> &games::otoca::get_lights() {
static std::vector<Light> lights;
if (lights.empty()) {
lights = GameAPI::Lights::getLights("Otoca D'or");
GameAPI::Lights::sortLights(
&lights,
"Left Button",
"Right Button"
);
}
return lights;
}
+34
View File
@@ -0,0 +1,34 @@
#pragma once
#include <vector>
#include "cfg/api.h"
namespace games::otoca {
// all buttons in correct order
namespace Buttons {
enum {
Service,
Test,
CoinMech,
ButtonLeft,
ButtonRight,
LeverUp,
LeverDown,
LeverLeft,
LeverRight,
};
}
// all lights in correct order
namespace Lights {
enum {
LeftButton,
RightButton,
};
}
// getters
std::vector<Button> &get_buttons();
std::vector<Light> &get_lights();
}
+27
View File
@@ -0,0 +1,27 @@
#include "otoca.h"
#include "p4io.h"
#include "games/shared/printer.h"
#include "util/libutils.h"
namespace games::otoca {
OtocaGame::OtocaGame() : Game("Otoca D'or") {
}
OtocaGame::~OtocaGame() {
}
void OtocaGame::attach() {
Game::attach();
/*
* arkkep.dll uses LoadLibrary to access game.dll
* we preload it so that hooks (e.g. for graphics) will work
*/
libutils::try_library("game.dll");
// initialize IO hooks
p4io_hook();
games::shared::printer_attach();
}
}
+14
View File
@@ -0,0 +1,14 @@
#pragma once
#include "games/game.h"
namespace games::otoca {
class OtocaGame : public games::Game {
public:
OtocaGame();
~OtocaGame() override;
virtual void attach() override;
};
}
+133
View File
@@ -0,0 +1,133 @@
#include "p4io.h"
#include "util/logging.h"
#include "util/detour.h"
#include "misc/eamuse.h"
#include "acio/icca/icca.h"
#include "rawinput/rawinput.h"
#include "io.h"
namespace games::otoca {
// button mapping
static size_t MAPPING[] {
~0u,
Buttons::Test,
Buttons::Service,
Buttons::CoinMech,
~0u,
Buttons::LeverUp,
Buttons::LeverDown,
Buttons::LeverLeft,
Buttons::LeverRight,
Buttons::ButtonLeft,
Buttons::ButtonRight,
~0u,
~0u,
};
static uint8_t INPUT_STATE[std::size(MAPPING)] {};
static uint8_t *CARD_DATA = nullptr;
static int __cdecl P4io_Boot_step() {
return 1;
}
static int __cdecl P4ioInputIsOn(int input_no) {
if (input_no < (int) std::size(MAPPING)) {
return INPUT_STATE[input_no] > 0 && INPUT_STATE[input_no] < 3;
}
return 0;
}
static bool __cdecl P4ioInputIsOnTrg(int input_no) {
if (input_no < (int) std::size(MAPPING)) {
return INPUT_STATE[input_no] == 1;
}
return 0;
}
static bool __cdecl P4ioInputIsOffTrg(int input_no) {
if (input_no < (int) std::size(MAPPING)) {
return INPUT_STATE[input_no] == 3;
}
return 0;
}
static void __cdecl P4ioInput_Polling() {
auto &buttons = get_buttons();
for (size_t i = 0; i < std::size(MAPPING); i++) {
if (MAPPING[i] != ~0u) {
// reset release event
if (INPUT_STATE[i] == 3) {
INPUT_STATE[i] = 0;
}
// apply new button state
auto state = GameAPI::Buttons::getState(RI_MGR, buttons.at(MAPPING[i]));
if (state && INPUT_STATE[i] < 2) {
INPUT_STATE[i]++;
} else if (!state && INPUT_STATE[i] > 0 && INPUT_STATE[i] < 3) {
INPUT_STATE[i] = 3;
}
}
}
}
static int __cdecl P4ioOutputPolling(uint32_t data) {
auto &lights = get_lights();
GameAPI::Lights::writeLight(RI_MGR, lights.at(Lights::LeftButton), (data & 1) ? 1.f : 0.f);
GameAPI::Lights::writeLight(RI_MGR, lights.at(Lights::RightButton), (data & 2) ? 1.f : 0.f);
RI_MGR->devices_flush_output();
return 0;
}
static int __cdecl P4ioCoinCount() {
return eamuse_coin_get_stock();
}
static bool __cdecl P4ioInputIsICCardHold() {
bool kb_insert_press = false;
// eamio keypress
kb_insert_press |= eamuse_get_keypad_state(0) & (1 << EAM_IO_INSERT);
// check for card
if (CARD_DATA == nullptr && (eamuse_card_insert_consume(1, 0) || kb_insert_press)) {
auto card = new uint8_t[8];
if (!eamuse_get_card(1, 0, card)) {
// invalid card found
delete[] card;
} else {
CARD_DATA = card;
}
}
// check if hold
return CARD_DATA != nullptr;
}
static void __cdecl P4ioInputIsICCardRead(uint8_t *data) {
if (CARD_DATA != nullptr) {
memcpy(data, CARD_DATA, 8);
}
}
void p4io_hook() {
detour::iat_try("?P4io_Boot_step@@YA?AW4P4IO_STATUS@@XZ", P4io_Boot_step);
detour::iat_try("?P4ioInputIsOn@@YAHH@Z", P4ioInputIsOn);
detour::iat_try("?P4ioInputIsOnTrg@@YAHH@Z", P4ioInputIsOnTrg);
detour::iat_try("?P4ioInputIsOffTrg@@YAHH@Z", P4ioInputIsOffTrg);
detour::iat_try("?P4ioInput_Polling@@YAXXZ", P4ioInput_Polling);
detour::iat_try("?P4ioOutputPolling@@YAHU_P4IO_POLLING_DATA_t@@@Z", P4ioOutputPolling);
detour::iat_try("?P4ioCoinCount@@YAHXZ", P4ioCoinCount);
detour::iat_try("?P4ioInputIsICCardHold@@YA_NXZ", (void*) P4ioInputIsICCardHold);
detour::iat_try("?P4ioInputIsICCardRead@@YAXPAU_P4IO_ICCARDDATA@@@Z", P4ioInputIsICCardRead);
}
}
+6
View File
@@ -0,0 +1,6 @@
#pragma once
namespace games::otoca {
void p4io_hook();
}

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