mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-01 22:30:42 -07:00
71ba9b6b47
## Link to GitHub Issue or related Pull Request, if one exists Fixes #681 ## Description of change `Sleep` and `sleep_for()` can be very inaccurate and varies depending on what the OS gives us... ### `timeBeginPeriod(1)` On boot, we are now calling `timeBeginPeriod(1)`, which affects the whole process but makes `Sleep` more accurate. There is some risk here if any game was relying on doing things like `Sleep(1)` and expecting it to run for 15.6ms. Most games already call `timeBeginPeriod(1)` in the game engine, though not the whole time, so I'm hoping that this is not too impactful. ### Opt out of Win11 power throttling Ensure that timer resolution change above is respected even when the window is occluded / minimized by opting out of throttling via `PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION`. ### Use Win10 high resolution timer instead of Sleep On Win10 1803 and above, there is a new OS-level API for high resolution timers; if this is available, use it (`CREATE_WAITABLE_TIMER_HIGH_RESOLUTION`). Worth noting that WINE doesn't support this currently. If not, fall back to `Sleep`, which is significantly better than `sleep_for()` in my experiments. Callers of Sleep / sleep_for were replaced with this new timer. Most of them anyway; calls to Sleep() with more than 100ms+ was left alone. ### Add an option as a chicken bit To opt out I'm adding a new option called `Use Legacy Timers` which will revert to behavior before this PR. The code paths that switched from `sleep_for` to `Sleep` will remain in place though, not affected by the option. ## Expected changes In some I/O emulation modules, poll threads may run more frequently, resulting in lower latency. It also means that spice overall may use more CPU resources and power. If you don't like this, you can always enable the option to opt out; e.g., if you're on old arcade cab PC. ## Testing DDR p4io - ok drs touch hook - ok IIDX camera hook - ok CCJ trackball - ok
601 lines
18 KiB
C++
601 lines
18 KiB
C++
#include "p3io.h"
|
|
|
|
#include "cfg/api.h"
|
|
#include "rawinput/rawinput.h"
|
|
#include "util/logging.h"
|
|
#include "util/precise_timer.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] && light_bits & 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
|
|
static thread_local timeutils::PreciseSleepTimer timer;
|
|
timer.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, // service, test, coin
|
|
8, // p1 start
|
|
9, 10, 11, 12, // p1 panel
|
|
24, 25, 14, 15, // p1 menu
|
|
16, // p2 start
|
|
17, 18, 19, 20, // p2 panel
|
|
26, 27, 22, 23 // p2 menu
|
|
};
|
|
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,
|
|
};
|
|
|
|
// get analogs
|
|
struct {
|
|
bool up;
|
|
bool down;
|
|
bool left;
|
|
bool right;
|
|
} analog_values[2] = {};
|
|
games::ddr::get_analog_x_axis(1, analog_values[0].left, analog_values[0].right);
|
|
games::ddr::get_analog_y_axis(1, analog_values[0].up, analog_values[0].down);
|
|
games::ddr::get_analog_x_axis(2, analog_values[1].left, analog_values[1].right);
|
|
games::ddr::get_analog_y_axis(2, analog_values[1].up, analog_values[1].down);
|
|
if (analog_values[0].up) {
|
|
controls |= 1 << 9;
|
|
}
|
|
if (analog_values[0].down) {
|
|
controls |= 1 << 10;
|
|
}
|
|
if (analog_values[0].left) {
|
|
controls |= 1 << 11;
|
|
}
|
|
if (analog_values[0].right) {
|
|
controls |= 1 << 12;
|
|
}
|
|
if (analog_values[1].up) {
|
|
controls |= 1 << 17;
|
|
}
|
|
if (analog_values[1].down) {
|
|
controls |= 1 << 18;
|
|
}
|
|
if (analog_values[1].left) {
|
|
controls |= 1 << 19;
|
|
}
|
|
if (analog_values[1].right) {
|
|
controls |= 1 << 20;
|
|
}
|
|
|
|
// 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;
|
|
}
|