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