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

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

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

@@ -0,0 +1,171 @@
#include "ddr.h"
#include "acioemu/handle.h"
#include "avs/game.h"
#include "hooks/devicehook.h"
#include "hooks/setupapihook.h"
#include "hooks/sleephook.h"
#include "hooks/input/dinput8/hook.h"
#include "util/utils.h"
#include "util/libutils.h"
#include "util/fileutils.h"
#include "util/detour.h"
#include "cfg/configurator.h"
#include "io.h"
#include "p3io/foot.h"
#include "p3io/p3io.h"
#include "p3io/sate.h"
#include "p3io/usbmem.h"
#include "p4io/p4io.h"
using namespace acioemu;
namespace games::ddr {
// settings
bool SDMODE = false;
static decltype(SendMessage) *SendMessage_real = nullptr;
static SHORT WINAPI GetAsyncKeyState_hook(int vKey) {
// disable debug keys
return 0;
}
static LRESULT WINAPI SendMessage_hook(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
// ignore broadcasts
if (hWnd == HWND_BROADCAST) {
return 1;
}
// fallback
return SendMessage_real(hWnd, Msg, wParam, lParam);
}
DDRGame::DDRGame() : Game("Dance Dance Revolution") {
}
void DDRGame::pre_attach() {
if (!cfg::CONFIGURATOR_STANDALONE && avs::game::is_model("TDX")) {
log_fatal(
"ddr",
"BAD MODEL NAME ERROR\n\n\n"
"!!! model name set to TDX, this is WRONG and will break your game !!!\n"
"!!! !!!\n"
"!!! !!!\n"
"!!! spice2x does not yet support TDX, use MDX instead. !!!\n"
"!!! !!!\n"
"!!! !!!\n"
"!!! model name set to TDX, this is WRONG and will break your game !!!\n\n\n"
);
}
if (!cfg::CONFIGURATOR_STANDALONE && avs::game::DEST[0] == 'U') {
log_warning(
"ddr",
"U-REGION WARNING\n\n\n"
"!!! !!!\n"
"!!! !!!\n"
"!!! !!!\n"
"!!! !!!\n"
"!!! <dest> is set to U region !!!\n"
"!!! !!!\n"
"!!! While this is legal, unless you have compatible data, this !!!\n"
"!!! will most likely crash your game or fail to boot. !!!\n"
"!!! !!!\n"
"!!! It is recommended that you stick with J region. !!!\n"
"!!! !!!\n"
"!!! !!!\n"
"!!! !!!\n"
"!!! !!!\n\n\n"
);
}
}
void DDRGame::attach() {
Game::attach();
// dinput hook on this dll since the game dll doesn't load it
auto game_mdx = libutils::try_library(MODULE_PATH / "gamemdx.dll");
hooks::input::dinput8::init(game_mdx);
// init device hook
devicehook_init();
// add fake devices
if (avs::game::DLL_NAME == "arkmdxbio2.dll") {
devicehook_add(new acioemu::ACIOHandle(L"COM1"));
} else if(avs::game::DLL_NAME == "arkmdxp4.dll") {
devicehook_add(new DDRP4IOHandle());
} else {
devicehook_add(new DDRFOOTHandle());
devicehook_add(new DDRSATEHandle());
devicehook_add(new DDRUSBMEMHandle());
devicehook_add(new DDRP3IOHandle());
}
// has nothing to do with P3IO, but is enough to trick the game into SD/HD mode
const char *settings_property = ddr::SDMODE ? "Generic Television" : "Generic Monitor";
const char settings_detail[] = R"(\\.\P3IO)";
// settings 1
SETUPAPI_SETTINGS settings1 {};
settings1.class_guid[0] = 0x1FA4A480;
settings1.class_guid[1] = 0x40C7AC60;
settings1.class_guid[2] = 0x7952ACA7;
settings1.class_guid[3] = 0x5A57340F;
memcpy(settings1.property_devicedesc, settings_property, strlen(settings_property) + 1);
memcpy(settings1.interface_detail, settings_detail, sizeof(settings_detail));
// settings 2
SETUPAPI_SETTINGS settings2 {};
settings2.class_guid[0] = 0x4D36E96E;
settings2.class_guid[1] = 0x11CEE325;
settings2.class_guid[2] = 0x8C1BF;
settings2.class_guid[3] = 0x1803E12B;
memcpy(settings2.property_devicedesc, settings_property, strlen(settings_property) + 1);
memcpy(settings2.interface_detail, settings_detail, sizeof(settings_detail));
const char settings_detail_p4io[] = R"(\\.\P4IO)";
// settings p4io
SETUPAPI_SETTINGS settingsp4io {};
settingsp4io.class_guid[0] = 0x8B7250A5;
settingsp4io.class_guid[1] = 0x46C94F61;
settingsp4io.class_guid[2] = 0x68E63A84;
settingsp4io.class_guid[3] = 0x206A4706;
memcpy(settingsp4io.property_devicedesc, settings_property, strlen(settings_property) + 1);
memcpy(settingsp4io.interface_detail, settings_detail_p4io, sizeof(settings_detail_p4io));
// init SETUP API
setupapihook_init(avs::game::DLL_INSTANCE);
// DDR ACE actually uses another DLL for things
if (game_mdx != nullptr) {
setupapihook_init(game_mdx);
}
// add settings
setupapihook_add(settings1);
setupapihook_add(settings2);
setupapihook_add(settingsp4io);
// misc hooks
detour::iat_try("GetAsyncKeyState", GetAsyncKeyState_hook, avs::game::DLL_INSTANCE);
detour::iat_try("GetKeyState", GetAsyncKeyState_hook, avs::game::DLL_INSTANCE);
SendMessage_real = detour::iat_try("SendMessageW", SendMessage_hook, avs::game::DLL_INSTANCE);
detour::iat_try("SendMessageA", SendMessage_hook, avs::game::DLL_INSTANCE);
}
void DDRGame::detach() {
Game::detach();
// dispose device hook
devicehook_dispose();
}
}

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

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

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

@@ -0,0 +1,178 @@
#include "io.h"
std::vector<Button> &games::ddr::get_buttons() {
static std::vector<Button> analogs;
if (analogs.empty()) {
analogs = GameAPI::Buttons::getButtons("Dance Dance Revolution");
GameAPI::Buttons::sortButtons(
&analogs,
"Service",
"Test",
"Coin Mech",
"P1 Start",
"P1 Panel Up",
"P1 Panel Down",
"P1 Panel Left",
"P1 Panel Right",
"P1 Menu Up",
"P1 Menu Down",
"P1 Menu Left",
"P1 Menu Right",
"P2 Start",
"P2 Panel Up",
"P2 Panel Down",
"P2 Panel Left",
"P2 Panel Right",
"P2 Menu Up",
"P2 Menu Down",
"P2 Menu Left",
"P2 Menu Right"
);
}
return analogs;
}
std::vector<Light> &games::ddr::get_lights() {
static std::vector<Light> lights;
if (lights.empty()) {
lights = GameAPI::Lights::getLights("Dance Dance Revolution");
GameAPI::Lights::sortLights(
&lights,
"P1 Foot Left",
"P1 Foot Up",
"P1 Foot Right",
"P1 Foot Down",
"P2 Foot Left",
"P2 Foot Up",
"P2 Foot Right",
"P2 Foot Down",
"Spot Red",
"Spot Blue",
"Top Spot Red",
"Top Spot Blue",
"P1 Halogen Upper",
"P1 Halogen Lower",
"P2 Halogen Upper",
"P2 Halogen Lower",
"P1 Button",
"P2 Button",
"Neon",
"HD P1 Start",
"HD P1 Menu Left-Right",
"HD P1 Menu Up-Down",
"HD P2 Start",
"HD P2 Menu Left-Right",
"HD P2 Menu Up-Down",
"HD P1 Speaker F R",
"HD P1 Speaker F G",
"HD P1 Speaker F B",
"HD P1 Speaker W R",
"HD P1 Speaker W G",
"HD P1 Speaker W B",
"HD P2 Speaker F R",
"HD P2 Speaker F G",
"HD P2 Speaker F B",
"HD P2 Speaker W R",
"HD P2 Speaker W G",
"HD P2 Speaker W B",
"WHITE Speaker Top R",
"WHITE Speaker Top G",
"WHITE Speaker Top B",
"WHITE Speaker Bottom R",
"WHITE Speaker Bottom G",
"WHITE Speaker Bottom B",
"WHITE Woofer R",
"WHITE Woofer G",
"WHITE Woofer B",
"GOLD P1 Menu Start",
"GOLD P1 Menu Up",
"GOLD P1 Menu Down",
"GOLD P1 Menu Left",
"GOLD P1 Menu Right",
"GOLD P2 Menu Start",
"GOLD P2 Menu Up",
"GOLD P2 Menu Down",
"GOLD P2 Menu Left",
"GOLD P2 Menu Right",
"GOLD Title Panel Left",
"GOLD Title Panel Center",
"GOLD Title Panel Right",
"GOLD P1 Woofer Corner",
"GOLD P2 Woofer Corner",
"GOLD P1 Card Unit R",
"GOLD P1 Card Unit G",
"GOLD P1 Card Unit B",
"GOLD P2 Card Unit R",
"GOLD P2 Card Unit G",
"GOLD P2 Card Unit B",
"GOLD Top Panel Avg R",
"GOLD Top Panel Avg G",
"GOLD Top Panel Avg B",
"GOLD Monitor Side Left Avg R",
"GOLD Monitor Side Left Avg G",
"GOLD Monitor Side Left Avg B",
"GOLD Monitor Side Right Avg R",
"GOLD Monitor Side Right Avg G",
"GOLD Monitor Side Right Avg B",
"GOLD P1 Foot Up Avg R",
"GOLD P1 Foot Up Avg G",
"GOLD P1 Foot Up Avg B",
"GOLD P1 Foot Down Avg R",
"GOLD P1 Foot Down Avg G",
"GOLD P1 Foot Down Avg B",
"GOLD P1 Foot Left Avg R",
"GOLD P1 Foot Left Avg G",
"GOLD P1 Foot Left Avg B",
"GOLD P1 Foot Right Avg R",
"GOLD P1 Foot Right Avg G",
"GOLD P1 Foot Right Avg B",
"GOLD P2 Foot Up Avg R",
"GOLD P2 Foot Up Avg G",
"GOLD P2 Foot Up Avg B",
"GOLD P2 Foot Down Avg R",
"GOLD P2 Foot Down Avg G",
"GOLD P2 Foot Down Avg B",
"GOLD P2 Foot Left Avg R",
"GOLD P2 Foot Left Avg G",
"GOLD P2 Foot Left Avg B",
"GOLD P2 Foot Right Avg R",
"GOLD P2 Foot Right Avg G",
"GOLD P2 Foot Right Avg B",
"GOLD P1 Stage Corner Up-Left",
"GOLD P1 Stage Corner Up-Right",
"GOLD P1 Stage Corner Down-Left",
"GOLD P1 Stage Corner Down-Right",
"GOLD P2 Stage Corner Up-Left",
"GOLD P2 Stage Corner Up-Right",
"GOLD P2 Stage Corner Down-Left",
"GOLD P2 Stage Corner Down-Right"
);
}
return lights;
}

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

@@ -0,0 +1,181 @@
#pragma once
#include <vector>
#include "cfg/api.h"
namespace games::ddr {
// all buttons in correct order
namespace Buttons {
enum {
SERVICE,
TEST,
COIN_MECH,
P1_START,
P1_PANEL_UP,
P1_PANEL_DOWN,
P1_PANEL_LEFT,
P1_PANEL_RIGHT,
P1_MENU_UP,
P1_MENU_DOWN,
P1_MENU_LEFT,
P1_MENU_RIGHT,
P2_START,
P2_PANEL_UP,
P2_PANEL_DOWN,
P2_PANEL_LEFT,
P2_PANEL_RIGHT,
P2_MENU_UP,
P2_MENU_DOWN,
P2_MENU_LEFT,
P2_MENU_RIGHT,
};
}
// all lights in correct order
namespace Lights {
enum {
P1_FOOT_LEFT,
P1_FOOT_UP,
P1_FOOT_RIGHT,
P1_FOOT_DOWN,
P2_FOOT_LEFT,
P2_FOOT_UP,
P2_FOOT_RIGHT,
P2_FOOT_DOWN,
SPOT_RED,
SPOT_BLUE,
TOP_SPOT_RED,
TOP_SPOT_BLUE,
P1_HALOGEN_UPPER,
P1_HALOGEN_LOWER,
P2_HALOGEN_UPPER,
P2_HALOGEN_LOWER,
P1_BUTTON,
P2_BUTTON,
NEON,
HD_P1_START,
HD_P1_LEFT_RIGHT,
HD_P1_UP_DOWN,
HD_P2_START,
HD_P2_LEFT_RIGHT,
HD_P2_UP_DOWN,
HD_P1_SPEAKER_F_R,
HD_P1_SPEAKER_F_G,
HD_P1_SPEAKER_F_B,
HD_P1_SPEAKER_W_R,
HD_P1_SPEAKER_W_G,
HD_P1_SPEAKER_W_B,
HD_P2_SPEAKER_F_R,
HD_P2_SPEAKER_F_G,
HD_P2_SPEAKER_F_B,
HD_P2_SPEAKER_W_R,
HD_P2_SPEAKER_W_G,
HD_P2_SPEAKER_W_B,
WHITE_SPEAKER_TOP_R,
WHITE_SPEAKER_TOP_G,
WHITE_SPEAKER_TOP_B,
WHITE_SPEAKER_BOTTOM_R,
WHITE_SPEAKER_BOTTOM_G,
WHITE_SPEAKER_BOTTOM_B,
WHITE_WOOFER_R,
WHITE_WOOFER_G,
WHITE_WOOFER_B,
GOLD_P1_MENU_START,
GOLD_P1_MENU_UP,
GOLD_P1_MENU_DOWN,
GOLD_P1_MENU_LEFT,
GOLD_P1_MENU_RIGHT,
GOLD_P2_MENU_START,
GOLD_P2_MENU_UP,
GOLD_P2_MENU_DOWN,
GOLD_P2_MENU_LEFT,
GOLD_P2_MENU_RIGHT,
GOLD_TITLE_PANEL_LEFT,
GOLD_TITLE_PANEL_CENTER,
GOLD_TITLE_PANEL_RIGHT,
GOLD_P1_WOOFER_CORNER,
GOLD_P2_WOOFER_CORNER,
GOLD_P1_CARD_UNIT_R,
GOLD_P1_CARD_UNIT_G,
GOLD_P1_CARD_UNIT_B,
GOLD_P2_CARD_UNIT_R,
GOLD_P2_CARD_UNIT_G,
GOLD_P2_CARD_UNIT_B,
GOLD_TOP_PANEL_AVG_R,
GOLD_TOP_PANEL_AVG_G,
GOLD_TOP_PANEL_AVG_B,
GOLD_MONITOR_SIDE_LEFT_AVG_R,
GOLD_MONITOR_SIDE_LEFT_AVG_G,
GOLD_MONITOR_SIDE_LEFT_AVG_B,
GOLD_MONITOR_SIDE_RIGHT_AVG_R,
GOLD_MONITOR_SIDE_RIGHT_AVG_G,
GOLD_MONITOR_SIDE_RIGHT_AVG_B,
GOLD_P1_FOOT_UP_AVG_R,
GOLD_P1_FOOT_UP_AVG_G,
GOLD_P1_FOOT_UP_AVG_B,
GOLD_P1_FOOT_DOWN_AVG_R,
GOLD_P1_FOOT_DOWN_AVG_G,
GOLD_P1_FOOT_DOWN_AVG_B,
GOLD_P1_FOOT_LEFT_AVG_R,
GOLD_P1_FOOT_LEFT_AVG_G,
GOLD_P1_FOOT_LEFT_AVG_B,
GOLD_P1_FOOT_RIGHT_AVG_R,
GOLD_P1_FOOT_RIGHT_AVG_G,
GOLD_P1_FOOT_RIGHT_AVG_B,
GOLD_P2_FOOT_UP_AVG_R,
GOLD_P2_FOOT_UP_AVG_G,
GOLD_P2_FOOT_UP_AVG_B,
GOLD_P2_FOOT_DOWN_AVG_R,
GOLD_P2_FOOT_DOWN_AVG_G,
GOLD_P2_FOOT_DOWN_AVG_B,
GOLD_P2_FOOT_LEFT_AVG_R,
GOLD_P2_FOOT_LEFT_AVG_G,
GOLD_P2_FOOT_LEFT_AVG_B,
GOLD_P2_FOOT_RIGHT_AVG_R,
GOLD_P2_FOOT_RIGHT_AVG_G,
GOLD_P2_FOOT_RIGHT_AVG_B,
GOLD_P1_STAGE_UP_LEFT,
GOLD_P1_STAGE_UP_RIGHT,
GOLD_P1_STAGE_DOWN_LEFT,
GOLD_P1_STAGE_DOWN_RIGHT,
GOLD_P2_STAGE_UP_LEFT,
GOLD_P2_STAGE_UP_RIGHT,
GOLD_P2_STAGE_DOWN_LEFT,
GOLD_P2_STAGE_DOWN_RIGHT,
};
}
// getters
std::vector<Button> &get_buttons();
std::vector<Light> &get_lights();
}

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

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

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

@@ -0,0 +1,23 @@
#pragma once
#include "hooks/devicehook.h"
namespace games::ddr {
class DDRFOOTHandle : public CustomHandle {
private:
bool data_trigger = false;
public:
bool open(LPCWSTR lpFileName) override;
int read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) override;
int write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) override;
int device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer,
DWORD nOutBufferSize) override;
bool close() override;
};
}

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

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

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

@@ -0,0 +1,37 @@
#pragma once
#include "acioemu/acioemu.h"
#include "hooks/devicehook.h"
namespace games::ddr {
class DDRP3IOHandle : public CustomHandle {
private:
acioemu::ACIOEmu *acio_emu;
circular_buffer<uint8_t> read_buf = circular_buffer<uint8_t>(1024);
class HDXSDevice : public acioemu::ACIODeviceEmu {
public:
HDXSDevice();
bool parse_msg(
acioemu::MessageData* msg_in,
circular_buffer<uint8_t> *response_buffer
) override;
};
void write_msg(const uint8_t *data, size_t len);
public:
bool open(LPCWSTR lpFileName) override;
int read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) override;
int write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) override;
int device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer,
DWORD nOutBufferSize) override;
bool close() override;
};
}

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

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

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

@@ -0,0 +1,31 @@
#pragma once
#include "hooks/devicehook.h"
#include "acioemu/acioemu.h"
namespace games::ddr {
class DDRSATEHandle : public CustomHandle {
private:
acioemu::ACIOEmu acio_emu;
class SATEDevice : public acioemu::ACIODeviceEmu {
public:
SATEDevice();
bool parse_msg(acioemu::MessageData *msg_in, circular_buffer<uint8_t> *response_buffer) override;
};
public:
bool open(LPCWSTR lpFileName) override;
int read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) override;
int write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) override;
int device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer,
DWORD nOutBufferSize) override;
bool close() override;
};
}

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

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

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

@@ -0,0 +1,26 @@
#pragma once
#include "hooks/devicehook.h"
namespace games::ddr {
class DDRUSBMEMHandle : public CustomHandle {
private:
char response_data[256]{};
size_t response_data_size = 0;
void respond(const char *data);
public:
bool open(LPCWSTR lpFileName) override;
int read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) override;
int write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) override;
int device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer,
DWORD nOutBufferSize) override;
bool close() override;
};
}

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

@@ -0,0 +1,352 @@
#include "p4io.h"
#include "rawinput/rawinput.h"
#include "util/logging.h"
#include "util/utils.h"
#include "../io.h"
#include <algorithm>
#ifndef FILE_DEVICE_UNKNOWN
#define FILE_DEVICE_UNKNOWN 0x00000022
#endif
// Packet header seems to be
// AA cmdId Seq maybe_length?
namespace {
#pragma pack(push, 1)
enum P4IOIoCtl : uint16_t {
P4IO_IOCTL_GET_INPUTS = 0x801,
P4IO_IOCTL_GET_VERSION_FUNC = 0x803,
};
enum class P4IOCmd : uint8_t {
Nop = 0x00,
GetTypeVerProd = 0x01,
UpdateLights = 0x12,
CoinHandler = 0x18,
Unk_1C = 0x1C,
IoSciOpen = 0x20,
SciTransfer = 0x21,
IoSciClose = 0x24,
ReadSecPlug_01 = 0x40,
ReadSecPlug_02 = 0x41,
};
struct P4IOHeader {
uint8_t magic;
P4IOCmd cmd;
uint8_t seq;
uint8_t length;
};
#pragma pack(pop)
const uint8_t blackPlugData[0x20] = {
0xC9, 0xFE, 0xC1, 0x5B, 0x8D, 0x8A, 0xE7, 0xA8,
0x92, 0xB8, 0x1A, 0x86, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77
};
const uint8_t whitePlugData[0x20] = {
0x18, 0x98, 0x12, 0x71, 0xB3, 0xA2, 0x20, 0x08,
0x82, 0x20, 0x08, 0x82, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDA
};
}
namespace games::ddr {
bool DDRP4IOHandle::open(LPCWSTR lpFileName) {
if (wcscmp(lpFileName, L"\\\\.\\P4IO\\p4io") != 0) {
return false;
}
if (!m_acio_emu) {
m_acio_emu = new acioemu::ACIOEmu();
m_acio_emu->add_device(new acioemu::ICCADevice(false, true, 2));
m_acio_emu->add_device(new HDXSDevice());
}
log_info("ddr", "Opened P4IO");
return true;
}
int DDRP4IOHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) {
DWORD transferSize = std::min(nNumberOfBytesToRead, (DWORD)m_read_buf.size());
memcpy(lpBuffer, m_read_buf.data(), transferSize);
m_read_buf.erase(m_read_buf.begin(), m_read_buf.begin()+transferSize);
return transferSize;
}
int DDRP4IOHandle::write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) {
const P4IOHeader *header = reinterpret_cast<const P4IOHeader*>(lpBuffer);
const uint8_t *data = reinterpret_cast<const uint8_t*>(reinterpret_cast<const uint8_t*>(lpBuffer)+sizeof(P4IOHeader));
if(nNumberOfBytesToWrite < sizeof(P4IOHeader)
|| header->magic != 0xAA
|| nNumberOfBytesToWrite < sizeof(P4IOHeader)+header->length)
{
log_warning("ddr", "p4io has the wrong header: {}", bin2hex(reinterpret_cast<const uint8_t*>(lpBuffer),
(int)nNumberOfBytesToWrite));
return 0;
}
std::vector<uint8_t> responseBuffer(64);
P4IOHeader *respHeader = reinterpret_cast<P4IOHeader*>(responseBuffer.data());
respHeader->magic = 0xAA;
respHeader->cmd = header->cmd;
respHeader->seq = header->seq;
uint8_t *respData = responseBuffer.data()+sizeof(P4IOHeader);
switch(header->cmd)
{
case P4IOCmd::Nop:
case P4IOCmd::Unk_1C:
case P4IOCmd::IoSciOpen:
case P4IOCmd::IoSciClose:
{
m_read_buf.insert(m_read_buf.end(), responseBuffer.begin(), responseBuffer.end());
return nNumberOfBytesToWrite;
}
break;
case P4IOCmd::GetTypeVerProd: {
respHeader->length = 0x2C;
// type
memcpy(&respData[0], "JDX", 4);
// version
respData[5] = 69;
respData[6] = 69;
respData[7] = 69;
// prod
memcpy(&respData[8], "prod", 4);
// date
memcpy(&respData[0xC], "2024-03-12", 10);
// time
memcpy(&respData[0x1C], "06:39:21", 8);
m_read_buf_mutex.lock();
m_read_buf.insert(m_read_buf.end(), responseBuffer.begin(), responseBuffer.end());
m_read_buf_mutex.unlock();
return nNumberOfBytesToWrite;
}
break;
case P4IOCmd::UpdateLights: {
static const size_t lightMapping[] = {
Lights::WHITE_SPEAKER_TOP_G,
Lights::WHITE_SPEAKER_TOP_R,
Lights::WHITE_SPEAKER_TOP_B,
Lights::WHITE_SPEAKER_BOTTOM_G,
Lights::WHITE_SPEAKER_BOTTOM_R,
Lights::WHITE_SPEAKER_BOTTOM_B,
Lights::WHITE_WOOFER_G,
Lights::WHITE_WOOFER_R,
Lights::WHITE_WOOFER_B,
Lights::HD_P1_START,
Lights::HD_P1_UP_DOWN,
Lights::HD_P1_LEFT_RIGHT
};
auto &lights = get_lights();
for(size_t i = 0; i < std::size(lightMapping); ++i) {
GameAPI::Lights::writeLight(RI_MGR, lights[lightMapping[i]], data[i] == 0 ? 0.f : 1.f);
}
RI_MGR->devices_flush_output();
m_read_buf_mutex.lock();
m_read_buf.insert(m_read_buf.end(), responseBuffer.begin(), responseBuffer.end());
m_read_buf_mutex.unlock();
return nNumberOfBytesToWrite;
}
break;
case P4IOCmd::CoinHandler: {
respHeader->length = 0x04;
// TODO:
m_read_buf_mutex.lock();
m_read_buf.insert(m_read_buf.end(), responseBuffer.begin(), responseBuffer.end());
m_read_buf_mutex.unlock();
return nNumberOfBytesToWrite;
}
break;
case P4IOCmd::SciTransfer: {
// request is 1 byte port #, followed by data
if(header->length > 1 && data[0] == 0) {
for(size_t i = 0; i < header->length-1u; ++i) {
m_acio_emu->write(data[1+i]);
}
}
respHeader->length = 2;
//response is 1 byte port #, 1 byte error flag, followed by data
if(header->length >= 1 && data[0] == 0) {
uint8_t readBytes = 0;
while(readBytes < 58) {
const auto b = m_acio_emu->read();
if(!b.has_value()) {
break;
}
respData[2+readBytes] = b.value();
++readBytes;
}
respHeader->length = 2+readBytes;
}
m_read_buf_mutex.lock();
m_read_buf.insert(m_read_buf.end(), responseBuffer.begin(), responseBuffer.end());
m_read_buf_mutex.unlock();
return nNumberOfBytesToWrite;
}
break;
case P4IOCmd::ReadSecPlug_01: {
// maybe just a presense check
respHeader->length = 0x09;
memset(&respData[1], 0xAA, 8);
m_read_buf_mutex.lock();
m_read_buf.insert(m_read_buf.end(), responseBuffer.begin(), responseBuffer.end());
m_read_buf_mutex.unlock();
return nNumberOfBytesToWrite;
}
break;
case P4IOCmd::ReadSecPlug_02: {
respHeader->length = 0x21;
if(data[0] == 0) {
memcpy(&respData[1], blackPlugData, sizeof(blackPlugData));
} else {
memcpy(&respData[1], whitePlugData, sizeof(whitePlugData));
}
m_read_buf_mutex.lock();
m_read_buf.insert(m_read_buf.end(), responseBuffer.begin(), responseBuffer.end());
m_read_buf_mutex.unlock();
return nNumberOfBytesToWrite;
}
break;
}
return 0;
}
int DDRP4IOHandle::device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer,
DWORD nOutBufferSize) {
if(((dwIoControlCode>>16)&0xFFFF) == FILE_DEVICE_UNKNOWN) {
switch((dwIoControlCode>>2)&0x3FFF) {
case P4IO_IOCTL_GET_INPUTS: {
memset(lpOutBuffer, 0, 16);
auto controls = (uint32_t*) lpOutBuffer;
const std::pair<size_t, uint8_t> buttonMapping[] =
{
{Buttons::SERVICE, 25}, //Good
{Buttons::TEST, 28}, //Good
{Buttons::COIN_MECH, 24}, //Good
{Buttons::P1_START, 0}, //Good
{Buttons::P1_PANEL_UP, 5}, //Good
{Buttons::P1_PANEL_DOWN, 6}, //Good
{Buttons::P1_PANEL_LEFT, 7}, //Goood
{Buttons::P1_PANEL_RIGHT, 16}, //Good
{Buttons::P1_MENU_UP, 1}, //Good
{Buttons::P1_MENU_DOWN, 2}, //Good
{Buttons::P1_MENU_LEFT, 3}, //Good
{Buttons::P1_MENU_RIGHT, 4}, //Good
{Buttons::P2_START, 8}, //Good
{Buttons::P2_PANEL_UP, 13}, //Good
{Buttons::P2_PANEL_DOWN, 14}, //Good
{Buttons::P2_PANEL_LEFT, 15}, //Good
{Buttons::P2_PANEL_RIGHT, 20}, //Good
{Buttons::P2_MENU_UP, 9}, //Good
{Buttons::P2_MENU_DOWN, 10}, //Good
{Buttons::P2_MENU_LEFT, 11}, //Good
{Buttons::P2_MENU_RIGHT, 12}, //Good
};
auto &buttons = get_buttons();
for(const auto &mapping : buttonMapping)
{
if(GameAPI::Buttons::getState(RI_MGR, buttons[mapping.first]))
{
controls[mapping.second/32] |= 1<<(mapping.second%32);
}
}
return 16;
}
break;
case P4IO_IOCTL_GET_VERSION_FUNC: {
if(nOutBufferSize > strlen("spicy p4io") + 1) {
strcpy((char*)lpOutBuffer, "spicy p4io");
return 11;
}
}
break;
}
}
return 0;
}
bool DDRP4IOHandle::close() {
log_info("ddr", "Closed P4IO");
return true;
}
DDRP4IOHandle::HDXSDevice::HDXSDevice() {
this->node_count = 1;
}
bool DDRP4IOHandle::HDXSDevice::parse_msg(
acioemu::MessageData* msg_in,
circular_buffer<uint8_t> *response_buffer) {
switch(msg_in->cmd.code)
{
case acioemu::ACIO_CMD_GET_VERSION: {
auto msg = this->create_msg(msg_in, acioemu::MSG_VERSION_SIZE);
this->set_version(msg, 0x204, 0, 1, 6, 0, "HDXS");
write_msg(msg, response_buffer);
delete msg;
}
break;
case acioemu::ACIO_CMD_KEEPALIVE: {
auto msg = this->create_msg(msg_in, 0);
write_msg(msg, response_buffer);
delete msg;
break;
}
break;
case acioemu::ACIO_CMD_STARTUP:
case acioemu::ACIO_CMD_CLEAR:
case 0x110:
case 0x128:
{
auto msg = this->create_msg_status(msg_in, 0x00);
write_msg(msg, response_buffer);
delete msg;
}
break;
default:
log_info("ddr", "HDXS unhandled {:03X}", msg_in->cmd.code);
}
return true;
}
}

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

@@ -0,0 +1,36 @@
#pragma once
#include "acioemu/acioemu.h"
#include "hooks/devicehook.h"
#include <mutex>
namespace games::ddr {
class DDRP4IOHandle : public CustomHandle {
private:
acioemu::ACIOEmu *m_acio_emu;
std::vector<uint8_t> m_read_buf;
std::mutex m_read_buf_mutex;
class HDXSDevice : public acioemu::ACIODeviceEmu {
public:
HDXSDevice();
bool parse_msg(
acioemu::MessageData* msg_in,
circular_buffer<uint8_t> *response_buffer
) override;
};
public:
bool open(LPCWSTR lpFileName) override;
int read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) override;
int write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) override;
int device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer,
DWORD nOutBufferSize) override;
bool close() override;
};
}