Initial re-upload of spice2x-24-08-24
This commit is contained in:
209
acioemu/acioemu.cpp
Normal file
209
acioemu/acioemu.cpp
Normal file
@@ -0,0 +1,209 @@
|
||||
#include "acioemu.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
using namespace acioemu;
|
||||
|
||||
ACIOEmu::ACIOEmu() {
|
||||
this->devices = new std::vector<ACIODeviceEmu *>();
|
||||
this->response_buffer = new circular_buffer<uint8_t>(4096);
|
||||
this->read_buffer = new circular_buffer<uint8_t>(1024);
|
||||
}
|
||||
|
||||
ACIOEmu::~ACIOEmu() {
|
||||
|
||||
// delete devices
|
||||
for (auto device : *this->devices) {
|
||||
delete device;
|
||||
}
|
||||
delete this->devices;
|
||||
|
||||
// delete buffers
|
||||
delete this->response_buffer;
|
||||
delete this->read_buffer;
|
||||
}
|
||||
|
||||
void ACIOEmu::add_device(ACIODeviceEmu *device) {
|
||||
this->devices->push_back(device);
|
||||
}
|
||||
|
||||
void ACIOEmu::write(uint8_t byte) {
|
||||
|
||||
// insert into buffer
|
||||
if (!invert) {
|
||||
if (byte == ACIO_ESCAPE) {
|
||||
invert = true;
|
||||
} else {
|
||||
this->read_buffer->put(byte);
|
||||
}
|
||||
} else {
|
||||
byte = ~byte;
|
||||
invert = false;
|
||||
this->read_buffer->put(byte);
|
||||
}
|
||||
|
||||
// clean garbage
|
||||
while (!this->read_buffer->empty() && this->read_buffer->peek() != 0xAA) {
|
||||
this->read_buffer->get();
|
||||
}
|
||||
while (this->read_buffer->size() > 1 && this->read_buffer->peek(1) == 0xAA) {
|
||||
this->read_buffer->get();
|
||||
}
|
||||
|
||||
// handshake counter
|
||||
static unsigned int handshake_counter = 0;
|
||||
if (byte == 0xAA) {
|
||||
handshake_counter++;
|
||||
} else {
|
||||
handshake_counter = 0;
|
||||
}
|
||||
|
||||
// check for handshake
|
||||
if (handshake_counter > 1) {
|
||||
|
||||
/*
|
||||
* small hack - BIO2 seems to expect more bytes here - sending two bytes each time fixes it
|
||||
* TODO replace this handshake code with something better
|
||||
*/
|
||||
this->response_buffer->put(ACIO_SOF);
|
||||
this->response_buffer->put(ACIO_SOF);
|
||||
handshake_counter--;
|
||||
return;
|
||||
}
|
||||
|
||||
// parse
|
||||
if (!this->read_buffer->empty() && this->read_buffer->size() >= 6) {
|
||||
bool is_complete = false;
|
||||
|
||||
// check if broadcast
|
||||
if (this->read_buffer->peek(1) == ACIO_BROADCAST) {
|
||||
|
||||
// check msg data size
|
||||
auto data_size = this->read_buffer->peek(2);
|
||||
|
||||
// check if msg is complete (SOF + checksum + broadcast header + data_size)
|
||||
is_complete = this->read_buffer->size() >= 2u + 2u + data_size;
|
||||
} else {
|
||||
|
||||
// check msg data size
|
||||
auto data_size = this->read_buffer->peek(5);
|
||||
|
||||
// check if msg is complete (SOF + checksum + command header + data_size)
|
||||
is_complete = this->read_buffer->size() >= 2u + MSG_HEADER_SIZE + data_size;
|
||||
}
|
||||
|
||||
// parse message if complete
|
||||
if (is_complete) {
|
||||
this->msg_parse();
|
||||
this->read_buffer->reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<uint8_t> ACIOEmu::read() {
|
||||
if (this->response_buffer->empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return this->response_buffer->get();
|
||||
}
|
||||
|
||||
size_t ACIOEmu::bytes_available() {
|
||||
return this->response_buffer->size();
|
||||
}
|
||||
|
||||
void ACIOEmu::msg_parse() {
|
||||
|
||||
#ifdef ACIOEMU_LOG
|
||||
log_info("acioemu", "MSG RECV: {}", bin2hex(*this->read_buffer));
|
||||
#endif
|
||||
|
||||
// calculate checksum
|
||||
uint8_t chk = 0;
|
||||
size_t max = this->read_buffer->size() - 1;
|
||||
for (size_t i = 1; i < max; i++) {
|
||||
chk += this->read_buffer->peek(i);
|
||||
}
|
||||
|
||||
// check checksum
|
||||
uint8_t chk_receive = this->read_buffer->peek(this->read_buffer->size() - 1);
|
||||
if (chk != chk_receive) {
|
||||
#ifdef ACIOEMU_LOG
|
||||
log_info("acioemu", "detected wrong checksum: {}/{}", chk, chk_receive);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
// get message data
|
||||
auto msg_data = this->read_buffer->peek_all();
|
||||
auto msg_in = (MessageData *) &msg_data[1];
|
||||
|
||||
// correct cmd code endianness if this is not a broadcast
|
||||
if (msg_in->addr != ACIO_BROADCAST) {
|
||||
msg_in->cmd.code = acio_u16(msg_in->cmd.code);
|
||||
}
|
||||
|
||||
// pass to applicable device
|
||||
uint8_t node_offset = 0;
|
||||
for (auto device : *this->devices) {
|
||||
if (device->is_applicable(node_offset, msg_in->addr)) {
|
||||
auto cur_offset = msg_in->addr - node_offset - 1;
|
||||
if (cur_offset < 0) {
|
||||
break;
|
||||
}
|
||||
if (device->parse_msg(msg_in, this->response_buffer)) {
|
||||
return;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
node_offset += device->node_count;
|
||||
}
|
||||
|
||||
// ignore broadcast messages by default
|
||||
if (msg_in->addr == ACIO_BROADCAST) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Default Behavior
|
||||
* If you want to do anything different, just handle the
|
||||
* commands in your own device implementation.
|
||||
*/
|
||||
switch (msg_in->cmd.code) {
|
||||
|
||||
// node count report
|
||||
case ACIO_CMD_ASSIGN_ADDRS: {
|
||||
if (msg_in->addr == 0x00 && node_offset > 0) {
|
||||
auto msg = ACIODeviceEmu::create_msg(msg_in, 1, &node_offset);
|
||||
ACIODeviceEmu::write_msg(msg, this->response_buffer);
|
||||
delete msg;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// status 0 defaults
|
||||
case ACIO_CMD_CLEAR:
|
||||
case ACIO_CMD_STARTUP:
|
||||
case 0x80: // KEEPALIVE
|
||||
case 0xFF: // BROADCAST
|
||||
{
|
||||
// send status 0
|
||||
auto msg = ACIODeviceEmu::create_msg_status(msg_in, 0);
|
||||
ACIODeviceEmu::write_msg(msg, response_buffer);
|
||||
delete msg;
|
||||
return;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef ACIOEMU_LOG
|
||||
log_info("acioemu", "UNHANDLED MSG FOR ADDR: {}, CMD: 0x{:x}), DATA: {}",
|
||||
msg_in->addr,
|
||||
msg_in->cmd.code,
|
||||
bin2hex(*this->read_buffer));
|
||||
#endif
|
||||
}
|
||||
33
acioemu/acioemu.h
Normal file
33
acioemu/acioemu.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "util/circular_buffer.h"
|
||||
|
||||
#include "device.h"
|
||||
#include "icca.h"
|
||||
|
||||
namespace acioemu {
|
||||
|
||||
class ACIOEmu {
|
||||
private:
|
||||
std::vector<ACIODeviceEmu *> *devices;
|
||||
circular_buffer<uint8_t> *response_buffer;
|
||||
circular_buffer<uint8_t> *read_buffer;
|
||||
bool invert = false;
|
||||
|
||||
void msg_parse();
|
||||
|
||||
public:
|
||||
|
||||
explicit ACIOEmu();
|
||||
~ACIOEmu();
|
||||
|
||||
void add_device(ACIODeviceEmu *device);
|
||||
|
||||
void write(uint8_t byte);
|
||||
std::optional<uint8_t> read();
|
||||
size_t bytes_available();
|
||||
};
|
||||
}
|
||||
80
acioemu/bi2a.h
Normal file
80
acioemu/bi2a.h
Normal file
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#include <ctime>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <cstring>
|
||||
#include "device.h"
|
||||
#include "hooks/sleephook.h"
|
||||
|
||||
namespace acioemu {
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct bio2_bi2a_state_in {
|
||||
uint8_t pad0[3];
|
||||
uint8_t panel[4];
|
||||
uint8_t deck_switch[14];
|
||||
uint8_t pad21[2];
|
||||
uint8_t led_ticker[9];
|
||||
uint8_t spot_light_1[4];
|
||||
uint8_t neon_light;
|
||||
uint8_t spot_light_2[4];
|
||||
uint8_t pad41[7];
|
||||
};
|
||||
|
||||
struct bio2_bi2a_status {
|
||||
uint8_t slider_1;
|
||||
uint8_t system;
|
||||
uint8_t slider_2;
|
||||
uint8_t pad3;
|
||||
uint8_t slider_3;
|
||||
uint8_t pad5;
|
||||
uint8_t slider_4;
|
||||
uint8_t slider_5;
|
||||
uint8_t pad8;
|
||||
uint8_t panel;
|
||||
uint8_t pad10[6];
|
||||
uint8_t tt_p1;
|
||||
uint8_t tt_p2;
|
||||
uint8_t p1_s1;
|
||||
uint8_t pad20;
|
||||
uint8_t p1_s2;
|
||||
uint8_t pad22;
|
||||
uint8_t p1_s3;
|
||||
uint8_t pad24;
|
||||
uint8_t p1_s4;
|
||||
uint8_t pad26;
|
||||
uint8_t p1_s5;
|
||||
uint8_t pad28;
|
||||
uint8_t p1_s6;
|
||||
uint8_t pad30;
|
||||
uint8_t p1_s7;
|
||||
uint8_t pad32;
|
||||
uint8_t p2_s1;
|
||||
uint8_t pad34;
|
||||
uint8_t p2_s2;
|
||||
uint8_t pad36;
|
||||
uint8_t p2_s3;
|
||||
uint8_t pad38;
|
||||
uint8_t p2_s4;
|
||||
uint8_t pad40;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
class BI2A : public ACIODeviceEmu {
|
||||
private:
|
||||
uint8_t coin_counter = 0;
|
||||
|
||||
public:
|
||||
explicit BI2A(bool type_new, bool flip_order, bool keypad_thread, uint8_t node_count);
|
||||
~BI2A() override;
|
||||
|
||||
bool parse_msg(unsigned int node_offset,
|
||||
MessageData *msg_in,
|
||||
circular_buffer<uint8_t> *response_buffer) override;
|
||||
|
||||
void update_card(int unit);
|
||||
void update_keypad(int unit, bool update_edge);
|
||||
void update_status(int unit);
|
||||
};
|
||||
}
|
||||
118
acioemu/device.cpp
Normal file
118
acioemu/device.cpp
Normal file
@@ -0,0 +1,118 @@
|
||||
#include "device.h"
|
||||
|
||||
#include "util/logging.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
using namespace acioemu;
|
||||
|
||||
void ACIODeviceEmu::set_header(MessageData* data, uint8_t addr, uint16_t code, uint8_t pid,
|
||||
uint8_t data_size)
|
||||
{
|
||||
// flag as response
|
||||
if (addr != 0) {
|
||||
addr |= ACIO_RESPONSE_FLAG;
|
||||
}
|
||||
|
||||
// set header data
|
||||
data->addr = addr;
|
||||
data->cmd.code = acio_u16(code);
|
||||
data->cmd.pid = pid;
|
||||
data->cmd.data_size = data_size;
|
||||
}
|
||||
|
||||
void ACIODeviceEmu::set_version(MessageData* data, uint32_t type, uint8_t flag,
|
||||
uint8_t ver_major, uint8_t ver_minor, uint8_t ver_rev, std::string code)
|
||||
{
|
||||
|
||||
// set version data
|
||||
auto data_version = &data->cmd.data_version;
|
||||
data_version->type = type;
|
||||
data_version->flag = flag;
|
||||
data_version->ver_major = ver_major;
|
||||
data_version->ver_minor = ver_minor;
|
||||
data_version->ver_rev = ver_rev;
|
||||
strncpy(data_version->code, code.c_str(), sizeof(data_version->code));
|
||||
strncpy(data_version->date, __DATE__, sizeof(data_version->date));
|
||||
strncpy(data_version->time, __TIME__, sizeof(data_version->time));
|
||||
}
|
||||
|
||||
MessageData *ACIODeviceEmu::create_msg(uint8_t addr, uint16_t code, uint8_t pid, size_t data_size,
|
||||
uint8_t *data)
|
||||
{
|
||||
// check data size
|
||||
if (data_size > 0xFF) {
|
||||
log_warning("acio", "data size > 255: {}", data_size);
|
||||
data_size = 0xFF;
|
||||
}
|
||||
|
||||
// allocate data
|
||||
auto data_raw = new uint8_t[MSG_HEADER_SIZE + data_size];
|
||||
|
||||
// set header
|
||||
auto msg = (MessageData *) &data_raw[0];
|
||||
set_header(msg, addr, code, pid, (uint8_t) data_size);
|
||||
|
||||
// set data
|
||||
if (data) {
|
||||
memcpy(data_raw + MSG_HEADER_SIZE, data, data_size);
|
||||
} else {
|
||||
memset(data_raw + MSG_HEADER_SIZE, 0, data_size);
|
||||
}
|
||||
|
||||
// return prepared message
|
||||
return msg;
|
||||
}
|
||||
|
||||
MessageData *ACIODeviceEmu::create_msg(MessageData *msg_in, size_t data_size, uint8_t *data) {
|
||||
return create_msg(msg_in->addr, msg_in->cmd.code, msg_in->cmd.pid, data_size, data);
|
||||
}
|
||||
|
||||
MessageData *ACIODeviceEmu::create_msg_status(uint8_t addr, uint16_t code, uint8_t pid, uint8_t status) {
|
||||
return create_msg(addr, code, pid, 1, &status);
|
||||
}
|
||||
|
||||
MessageData *ACIODeviceEmu::create_msg_status(MessageData *msg_in, uint8_t status) {
|
||||
return create_msg_status(msg_in->addr, msg_in->cmd.code, msg_in->cmd.pid, status);
|
||||
}
|
||||
|
||||
bool ACIODeviceEmu::is_applicable(uint8_t node_offset, uint8_t node) {
|
||||
return node > node_offset && node <= node_offset + this->node_count;
|
||||
}
|
||||
|
||||
void ACIODeviceEmu::write_msg(const uint8_t *data, size_t size, circular_buffer<uint8_t> *response_buffer) {
|
||||
|
||||
// header
|
||||
for (int i = 0; i < 2; i++) {
|
||||
response_buffer->put(ACIO_SOF);
|
||||
}
|
||||
|
||||
// msg data and checksum
|
||||
uint8_t b, chk = 0;
|
||||
for (size_t i = 0; i <= size; i++) {
|
||||
|
||||
// set byte to data or checksum
|
||||
if (i < size) {
|
||||
b = data[i];
|
||||
chk += b;
|
||||
} else {
|
||||
b = chk;
|
||||
}
|
||||
|
||||
// check for escape
|
||||
if (b == ACIO_SOF || b == ACIO_ESCAPE) {
|
||||
response_buffer->put(ACIO_ESCAPE);
|
||||
response_buffer->put(~b);
|
||||
} else {
|
||||
response_buffer->put(b);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ACIOEMU_LOG
|
||||
log_info("acioemu", "ACIO MSG OUT: AA{}{:02X}", bin2hex(data, size), chk);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ACIODeviceEmu::write_msg(MessageData *msg, circular_buffer<uint8_t> *response_buffer) {
|
||||
auto data = reinterpret_cast<const uint8_t *>(msg);
|
||||
write_msg(data, MSG_HEADER_SIZE + msg->cmd.data_size, response_buffer);
|
||||
}
|
||||
103
acioemu/device.h
Normal file
103
acioemu/device.h
Normal file
@@ -0,0 +1,103 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "util/circular_buffer.h"
|
||||
|
||||
// convert big-endian to little-endian
|
||||
#define acio_u16 _byteswap_ushort
|
||||
#define acio_u32 _byteswap_ulong
|
||||
|
||||
namespace acioemu {
|
||||
|
||||
constexpr uint8_t ACIO_SOF = 0xAA;
|
||||
constexpr uint8_t ACIO_ESCAPE = 0xFF;
|
||||
constexpr uint8_t ACIO_BROADCAST = 0x70;
|
||||
constexpr uint8_t ACIO_RESPONSE_FLAG = 0x80;
|
||||
|
||||
// general command codes
|
||||
enum acio_cmd_codes {
|
||||
ACIO_CMD_ASSIGN_ADDRS = 0x0001,
|
||||
ACIO_CMD_GET_VERSION = 0x0002,
|
||||
ACIO_CMD_STARTUP = 0x0003,
|
||||
ACIO_CMD_KEEPALIVE = 0x0080,
|
||||
ACIO_CMD_CLEAR = 0x0100,
|
||||
};
|
||||
|
||||
// message structs
|
||||
#pragma pack(push, 1)
|
||||
struct VersionData {
|
||||
uint32_t type;
|
||||
uint8_t flag;
|
||||
uint8_t ver_major;
|
||||
uint8_t ver_minor;
|
||||
uint8_t ver_rev;
|
||||
char code[4];
|
||||
char date[16];
|
||||
char time[16];
|
||||
};
|
||||
|
||||
struct MessageData {
|
||||
uint8_t addr;
|
||||
|
||||
union {
|
||||
struct {
|
||||
uint16_t code;
|
||||
uint8_t pid;
|
||||
uint8_t data_size;
|
||||
|
||||
union {
|
||||
uint8_t raw[0xFF];
|
||||
uint8_t status;
|
||||
VersionData data_version;
|
||||
};
|
||||
} cmd;
|
||||
|
||||
struct {
|
||||
uint8_t data_size;
|
||||
uint8_t raw[0xFF];
|
||||
} broadcast;
|
||||
};
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
// message sizes
|
||||
constexpr size_t MSG_HEADER_SIZE = 5;
|
||||
constexpr size_t MSG_VERSION_SIZE = sizeof(VersionData);
|
||||
|
||||
class ACIODeviceEmu {
|
||||
public:
|
||||
|
||||
// attributes
|
||||
uint8_t node_count = 0;
|
||||
|
||||
/*
|
||||
* Helper functions for getting/setting the message contents
|
||||
*/
|
||||
static void set_header(MessageData* data, uint8_t addr, uint16_t code, uint8_t pid, uint8_t data_size);
|
||||
static void set_version(MessageData* data, uint32_t type, uint8_t flag,
|
||||
uint8_t ver_major, uint8_t ver_minor, uint8_t ver_rev,
|
||||
std::string code);
|
||||
|
||||
/*
|
||||
* This function creates a basic message with optional parameter data.
|
||||
* If data is set to null, the parameter data will be initialized with 0x00
|
||||
*/
|
||||
static MessageData* create_msg(uint8_t addr, uint16_t cmd, uint8_t pid,
|
||||
size_t data_size, uint8_t *data = nullptr);
|
||||
static MessageData* create_msg(MessageData* msg_in, size_t data_size, uint8_t *data = nullptr);
|
||||
|
||||
/*
|
||||
* Helper functions for generating messages
|
||||
*/
|
||||
static MessageData* create_msg_status(uint8_t addr, uint16_t code, uint8_t pid, uint8_t status);
|
||||
static MessageData* create_msg_status(MessageData* msg_in, uint8_t status);
|
||||
|
||||
virtual ~ACIODeviceEmu() = default;
|
||||
|
||||
virtual bool is_applicable(uint8_t node_offset, uint8_t node);
|
||||
virtual bool parse_msg(MessageData *msg_in, circular_buffer<uint8_t> *response_buffer) = 0;
|
||||
static void write_msg(const uint8_t *data, size_t size, circular_buffer<uint8_t> *response_buffer);
|
||||
static void write_msg(MessageData *msg, circular_buffer<uint8_t> *response_buffer);
|
||||
};
|
||||
}
|
||||
73
acioemu/handle.cpp
Normal file
73
acioemu/handle.cpp
Normal file
@@ -0,0 +1,73 @@
|
||||
#include "handle.h"
|
||||
|
||||
#include "misc/eamuse.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
acioemu::ACIOHandle::ACIOHandle(LPCWSTR lpCOMPort) {
|
||||
this->com_port = lpCOMPort;
|
||||
}
|
||||
|
||||
bool acioemu::ACIOHandle::open(LPCWSTR lpFileName) {
|
||||
if (wcscmp(lpFileName, com_port) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
log_info("acioemu", "Opened {} (ACIO)", ws2s(com_port));
|
||||
|
||||
// ACIO device
|
||||
acio_emu.add_device(new acioemu::ICCADevice(false, true, 2));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int acioemu::ACIOHandle::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 acioemu::ACIOHandle::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 acioemu::ACIOHandle::device_io(
|
||||
DWORD dwIoControlCode,
|
||||
LPVOID lpInBuffer,
|
||||
DWORD nInBufferSize,
|
||||
LPVOID lpOutBuffer,
|
||||
DWORD nOutBufferSize
|
||||
) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t acioemu::ACIOHandle::bytes_available() {
|
||||
return acio_emu.bytes_available();
|
||||
}
|
||||
|
||||
bool acioemu::ACIOHandle::close() {
|
||||
log_info("acioemu", "Closed {} (ACIO)", ws2s(com_port));
|
||||
|
||||
return true;
|
||||
}
|
||||
30
acioemu/handle.h
Normal file
30
acioemu/handle.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include "acioemu/acioemu.h"
|
||||
#include "hooks/devicehook.h"
|
||||
|
||||
namespace acioemu {
|
||||
|
||||
class ACIOHandle : public CustomHandle {
|
||||
private:
|
||||
LPCWSTR com_port;
|
||||
|
||||
acioemu::ACIOEmu acio_emu;
|
||||
|
||||
public:
|
||||
ACIOHandle(LPCWSTR lpCOMPort);
|
||||
|
||||
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;
|
||||
|
||||
size_t bytes_available() override;
|
||||
|
||||
bool close() override;
|
||||
};
|
||||
}
|
||||
581
acioemu/icca.cpp
Normal file
581
acioemu/icca.cpp
Normal file
@@ -0,0 +1,581 @@
|
||||
#include "icca.h"
|
||||
|
||||
#include "acio/icca/icca.h"
|
||||
#include "avs/game.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
using namespace acioemu;
|
||||
|
||||
namespace acioemu {
|
||||
bool ICCA_DEVICE_HACK = false;
|
||||
}
|
||||
|
||||
ICCADevice::ICCADevice(bool flip_order, bool keypad_thread, uint8_t node_count) {
|
||||
|
||||
// init defaults
|
||||
this->type_new = false;
|
||||
this->flip_order = flip_order;
|
||||
this->node_count = node_count;
|
||||
this->cards = new uint8_t *[node_count] {};
|
||||
this->cards_time = new time_t[node_count] {};
|
||||
this->status = new uint8_t[node_count * 16] {};
|
||||
this->accept = new bool[node_count] {};
|
||||
for (int i = 0; i < node_count; i++) {
|
||||
this->accept[i] = true;
|
||||
}
|
||||
this->hold = new bool[node_count] {};
|
||||
this->keydown = new uint8_t[node_count] {};
|
||||
this->keypad = new uint16_t[node_count] {};
|
||||
this->keypad_last = new bool*[node_count] {};
|
||||
for (int i = 0; i < node_count; i++) {
|
||||
this->keypad_last[i] = new bool[12] {};
|
||||
}
|
||||
this->keypad_capture = new uint8_t[node_count] {};
|
||||
for (int i = 0; i < node_count; i++) {
|
||||
this->keypad_capture[i] = 0x08;
|
||||
}
|
||||
this->crypt = new std::optional<Crypt>[node_count] {};
|
||||
this->counter = new uint8_t[node_count] {};
|
||||
for (int i = 0; i < node_count; i++) {
|
||||
this->counter[i] = 2;
|
||||
}
|
||||
|
||||
// keypad thread for faster polling
|
||||
if (keypad_thread) {
|
||||
this->keypad_thread = new std::thread([this]() {
|
||||
while (this->cards) {
|
||||
for (int unit = 0; unit < this->node_count; unit++) {
|
||||
this->update_keypad(unit, false);
|
||||
}
|
||||
Sleep(7);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ICCADevice::~ICCADevice() {
|
||||
|
||||
// stop thread
|
||||
delete keypad_thread;
|
||||
|
||||
// delete cards in array
|
||||
for (int i = 0; i < node_count; i++) {
|
||||
delete cards[i];
|
||||
}
|
||||
|
||||
// delete the rest
|
||||
delete[] cards;
|
||||
delete[] cards_time;
|
||||
delete[] status;
|
||||
delete[] accept;
|
||||
delete[] hold;
|
||||
delete[] keydown;
|
||||
delete[] keypad;
|
||||
delete[] keypad_last;
|
||||
delete[] keypad_capture;
|
||||
delete[] crypt;
|
||||
delete[] counter;
|
||||
}
|
||||
|
||||
bool ICCADevice::parse_msg(MessageData *msg_in,
|
||||
circular_buffer<uint8_t> *response_buffer) {
|
||||
|
||||
// get unit
|
||||
int unit = msg_in->addr - 1;
|
||||
if (this->flip_order) {
|
||||
unit = this->node_count - unit - 1;
|
||||
}
|
||||
if (unit != 0 && unit != 1) {
|
||||
log_fatal("icca", "invalid unit: {}", unit);
|
||||
}
|
||||
|
||||
#ifdef ACIOEMU_LOG
|
||||
log_info("acioemu", "ICCA ADDR: {}, CMD: 0x{:04x}", unit, 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);
|
||||
if (
|
||||
avs::game::is_model({"LDJ", "TBS", "UJK"}) ||
|
||||
// SDVX Valkyrie cabinet mode
|
||||
(avs::game::is_model("KFC") && (avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H'))
|
||||
) {
|
||||
this->set_version(msg, 0x3, 0, 1, 7, 0, "ICCA");
|
||||
} else {
|
||||
this->set_version(msg, 0x3, 0, 1, 6, 0, "ICCA");
|
||||
}
|
||||
write_msg(msg, response_buffer);
|
||||
delete msg;
|
||||
break;
|
||||
}
|
||||
case 0x0130: { // REINITIALIZE
|
||||
|
||||
// send status 0
|
||||
auto msg = this->create_msg_status(msg_in, 0x00);
|
||||
write_msg(msg, response_buffer);
|
||||
delete msg;
|
||||
break;
|
||||
}
|
||||
case 0x0131: { // READ CARD UID
|
||||
|
||||
// build data array
|
||||
auto msg = this->create_msg(msg_in, 16);
|
||||
|
||||
// update things
|
||||
update_card(unit);
|
||||
update_keypad(unit, true);
|
||||
update_status(unit);
|
||||
|
||||
// copy status
|
||||
memcpy(msg->cmd.raw, &status[unit * 16], 16);
|
||||
|
||||
// explicitly set no card since this is just read
|
||||
msg->cmd.raw[0] = 0x01;
|
||||
|
||||
// write message
|
||||
write_msg(msg, response_buffer);
|
||||
delete msg;
|
||||
break;
|
||||
}
|
||||
case 0x0135: { // SET ACTION
|
||||
|
||||
// check for data
|
||||
if (msg_in->cmd.data_size >= 2) {
|
||||
|
||||
// subcommand
|
||||
switch (msg_in->cmd.raw[1]) {
|
||||
case 0x00: // ACCEPT DISABLE
|
||||
this->accept[unit] = false;
|
||||
break;
|
||||
case 0x11: // ACCEPT ENABLE
|
||||
this->accept[unit] = true;
|
||||
break;
|
||||
case 0x12: // EJECT
|
||||
if (this->cards[unit] != nullptr) {
|
||||
delete this->cards[unit];
|
||||
}
|
||||
this->cards[unit] = nullptr;
|
||||
this->hold[unit] = false;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// no break, return status
|
||||
[[fallthrough]];
|
||||
}
|
||||
case 0x0134: { // GET STATUS
|
||||
|
||||
// build data array
|
||||
auto msg = this->create_msg(msg_in, 16);
|
||||
|
||||
// update things
|
||||
update_card(unit);
|
||||
update_keypad(unit, true);
|
||||
update_status(unit);
|
||||
|
||||
// copy status
|
||||
memcpy(msg->cmd.raw, &status[unit * 16], 16);
|
||||
|
||||
// write message
|
||||
write_msg(msg, response_buffer);
|
||||
delete msg;
|
||||
break;
|
||||
}
|
||||
case 0x0160: { // KEY EXCHANGE
|
||||
|
||||
// if this cmd is called, the reader type must be new
|
||||
this->type_new = true;
|
||||
|
||||
// build data array
|
||||
auto msg = this->create_msg(msg_in, 4);
|
||||
|
||||
// set key
|
||||
msg->cmd.raw[0] = 0xBE;
|
||||
msg->cmd.raw[1] = 0xEF;
|
||||
msg->cmd.raw[2] = 0xCA;
|
||||
msg->cmd.raw[3] = 0xFE;
|
||||
|
||||
// convert keys
|
||||
uint32_t game_key =
|
||||
msg_in->cmd.raw[0] << 24 |
|
||||
msg_in->cmd.raw[1] << 16 |
|
||||
msg_in->cmd.raw[2] << 8 |
|
||||
msg_in->cmd.raw[3];
|
||||
uint32_t reader_key =
|
||||
msg->cmd.raw[0] << 24 |
|
||||
msg->cmd.raw[1] << 16 |
|
||||
msg->cmd.raw[2] << 8 |
|
||||
msg->cmd.raw[3];
|
||||
|
||||
log_info("icca", "client key: {:08x}", game_key);
|
||||
log_info("icca", "reader key: {:08x}", reader_key);
|
||||
|
||||
this->crypt[unit].emplace();
|
||||
this->crypt[unit]->set_keys(reader_key, game_key);
|
||||
|
||||
// write message
|
||||
write_msg(msg, response_buffer);
|
||||
delete msg;
|
||||
break;
|
||||
}
|
||||
case 0x0161: { // READ CARD UID NEW
|
||||
|
||||
// if this cmd is called, the reader type must be new
|
||||
this->type_new = true;
|
||||
|
||||
// decide on answer
|
||||
int answer_type = 0;
|
||||
//if (avs::game::is_model("LDJ"))
|
||||
//answer_type = 1;
|
||||
// SDVX Old cabinet mode
|
||||
if (avs::game::is_model("KFC") && avs::game::SPEC[0] != 'G' && avs::game::SPEC[0] != 'H')
|
||||
answer_type = 1;
|
||||
if (avs::game::is_model("L44"))
|
||||
answer_type = 2;
|
||||
|
||||
// check answer type
|
||||
switch (answer_type) {
|
||||
case 1: {
|
||||
|
||||
// send status 1
|
||||
auto msg = this->create_msg_status(msg_in, 1);
|
||||
write_msg(msg, response_buffer);
|
||||
delete msg;
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
|
||||
// build data array
|
||||
auto msg = this->create_msg(msg_in, 16);
|
||||
|
||||
// update card
|
||||
update_card(unit);
|
||||
|
||||
// check for card
|
||||
if (this->cards[unit] != nullptr) {
|
||||
|
||||
// copy into data buffer
|
||||
memcpy(msg->cmd.raw, this->cards[unit], 8);
|
||||
|
||||
// delete card
|
||||
delete this->cards[unit];
|
||||
this->cards[unit] = nullptr;
|
||||
this->hold[unit] = false;
|
||||
}
|
||||
|
||||
// write message
|
||||
write_msg(msg, response_buffer);
|
||||
delete msg;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
|
||||
// send response with no data
|
||||
auto msg = this->create_msg(msg_in, 0);
|
||||
write_msg(msg, response_buffer);
|
||||
delete msg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x0164: { // GET STATUS ENC
|
||||
|
||||
// build data array
|
||||
auto msg = this->create_msg(msg_in, 18);
|
||||
|
||||
// update things
|
||||
update_card(unit);
|
||||
update_keypad(unit, true);
|
||||
update_status(unit);
|
||||
|
||||
// copy status
|
||||
memcpy(msg->cmd.raw, &status[unit * 16], 16);
|
||||
|
||||
if (this->crypt[unit].has_value()) {
|
||||
auto &crypt = this->crypt[unit];
|
||||
uint16_t crc = crypt->crc(msg->cmd.raw, 16);
|
||||
|
||||
msg->cmd.raw[16] = (uint8_t) (crc >> 8);
|
||||
msg->cmd.raw[17] = (uint8_t) crc;
|
||||
|
||||
crypt->crypt(msg->cmd.raw, 18);
|
||||
} else {
|
||||
log_warning("icca", "'GET STATUS ENC' message received with no crypt keys initialized");
|
||||
}
|
||||
|
||||
// write message
|
||||
write_msg(msg, response_buffer);
|
||||
delete msg;
|
||||
break;
|
||||
}
|
||||
case 0x013A: { // POWER CONTROL (tentative name, used in 1.7 firmware)
|
||||
// TODO(felix): isolate this logic to LDJ and/or firmware 1.7 emulation
|
||||
|
||||
if (this->counter[unit] > 0) {
|
||||
this->counter[unit]--;
|
||||
}
|
||||
//log_info("icca", "counter[{}] = {}", unit, this->counter[unit]);
|
||||
|
||||
auto msg = this->create_msg_status(msg_in, this->counter[unit]);
|
||||
write_msg(msg, response_buffer);
|
||||
delete msg;
|
||||
break;
|
||||
}
|
||||
case ACIO_CMD_STARTUP:
|
||||
case ACIO_CMD_CLEAR:
|
||||
case 0x30: // GetBoardProductNumber
|
||||
case 0x31: // GetMicomInfo
|
||||
case 0x0116: // ???
|
||||
case 0x0120: // ???
|
||||
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 ICCADevice::update_card(int unit) {
|
||||
|
||||
// wavepass timeout after 10s
|
||||
if (this->cards[unit] != nullptr) {
|
||||
time_t t_now;
|
||||
time(&t_now);
|
||||
|
||||
if (difftime(t_now, this->cards_time[unit]) >= 10.f) {
|
||||
if (this->cards[unit] != nullptr) {
|
||||
delete this->cards[unit];
|
||||
}
|
||||
this->cards[unit] = nullptr;
|
||||
this->hold[unit] = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool kb_insert_press = false;
|
||||
|
||||
// eamio keypress
|
||||
kb_insert_press |= eamuse_get_keypad_state((size_t) unit) & (1 << EAM_IO_INSERT);
|
||||
|
||||
// check for card
|
||||
if (this->cards[unit] == nullptr && (eamuse_card_insert_consume(this->node_count, unit) || kb_insert_press)) {
|
||||
auto card = new uint8_t[8];
|
||||
|
||||
if (!eamuse_get_card(this->node_count, unit, card)) {
|
||||
|
||||
// invalid card found
|
||||
delete[] card;
|
||||
|
||||
} else {
|
||||
this->cards[unit] = card;
|
||||
time(&this->cards_time[unit]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int KEYPAD_EAMUSE_MAPPING[] = {
|
||||
0, 1, 5, 9, 2, 6, 10, 3, 7, 11, 8, 4
|
||||
};
|
||||
|
||||
// map for KEYPAD_KEY_CODES:
|
||||
// 7 8 9 | 800 8000 8
|
||||
// 4 5 6 | 400 4000 4
|
||||
// 1 2 3 | 200 2000 2
|
||||
// 0 00 . | 100 1000 1
|
||||
static int KEYPAD_KEY_CODES[]{
|
||||
0x100, // 0
|
||||
0x200, // 1
|
||||
0x2000, // 2
|
||||
2, // 3
|
||||
0x400, // 4
|
||||
0x4000, // 5
|
||||
4, // 6
|
||||
0x800, // 7
|
||||
0x8000, // 8
|
||||
8, // 9
|
||||
1, // .
|
||||
0x1000 // 00
|
||||
};
|
||||
|
||||
// map for KEYPAD_KEY_CODES_ALT:
|
||||
// 7 8 9 | 8 80 800
|
||||
// 4 5 6 | 4 40 400
|
||||
// 1 2 3 | 2 20 200
|
||||
// 0 00 . | 1 10 100
|
||||
//
|
||||
// note that the only game that needs this (SDVX VM) does not accept decimal,
|
||||
// so that key is untested
|
||||
static int KEYPAD_KEY_CODES_ALT[]{
|
||||
1, // 0
|
||||
2, // 1
|
||||
0x20, // 2
|
||||
0x200, // 3
|
||||
4, // 4
|
||||
0x40, // 5
|
||||
0x400, // 6
|
||||
8, // 7
|
||||
0x80, // 8
|
||||
0x800, // 9
|
||||
0x100, // .
|
||||
0x10 // 00
|
||||
};
|
||||
static uint8_t KEYPAD_KEY_CODE_NUMS[]{
|
||||
0, 1, 5, 9, 2, 6, 10, 3, 7, 11, 8, 4
|
||||
};
|
||||
|
||||
void ICCADevice::update_keypad(int unit, bool update_edge) {
|
||||
|
||||
// lock keypad so threads can't interfere
|
||||
std::lock_guard<std::mutex> lock(this->keypad_mutex);
|
||||
|
||||
// reset unit
|
||||
this->keypad[unit] = 0;
|
||||
|
||||
// get eamu key states
|
||||
uint16_t eamu_state = eamuse_get_keypad_state((size_t) unit);
|
||||
|
||||
// iterate keypad
|
||||
bool edge = false;
|
||||
for (int n = 0; n < 12; n++) {
|
||||
int i = n;
|
||||
|
||||
// check if pressed
|
||||
if (eamu_state & (1 << KEYPAD_EAMUSE_MAPPING[i])) {
|
||||
|
||||
if (ICCA_DEVICE_HACK) {
|
||||
this->keypad[unit] |= KEYPAD_KEY_CODES_ALT[i];
|
||||
} else {
|
||||
this->keypad[unit] |= KEYPAD_KEY_CODES[i];
|
||||
}
|
||||
|
||||
if (!this->keypad_last[unit][i] && update_edge) {
|
||||
this->keydown[unit] = (this->keypad_capture[unit] << 4) | KEYPAD_KEY_CODE_NUMS[n];
|
||||
this->keypad_last[unit][i] = true;
|
||||
edge = true;
|
||||
}
|
||||
} else {
|
||||
this->keypad_last[unit][i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
// update keypad capture
|
||||
if (update_edge && edge) {
|
||||
this->keypad_capture[unit]++;
|
||||
this->keypad_capture[unit] |= 0x08;
|
||||
} else {
|
||||
this->keydown[unit] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void ICCADevice::update_status(int unit) {
|
||||
|
||||
// get buffer
|
||||
uint8_t *buffer = &this->status[unit * 16];
|
||||
|
||||
// clear buffer
|
||||
memset(buffer, 0x00, 16);
|
||||
|
||||
// check for card
|
||||
bool card = false;
|
||||
if (this->cards[unit] != nullptr) {
|
||||
|
||||
// copy card into buffer
|
||||
memcpy(buffer + 2, this->cards[unit], 8);
|
||||
card = true;
|
||||
}
|
||||
|
||||
// check for reader type
|
||||
if (this->type_new) {
|
||||
|
||||
// check for card
|
||||
if (card) {
|
||||
|
||||
// set status to card present
|
||||
buffer[0] = 0x02;
|
||||
|
||||
/*
|
||||
* set card type
|
||||
* 0x00 - ISO15696
|
||||
* 0x01 - FELICA
|
||||
*/
|
||||
bool felica = buffer[2] != 0xE0 && buffer[3] != 0x04;
|
||||
buffer[1] = felica ? 0x01 : 0x00;
|
||||
buffer[10] = felica ? 0x01 : 0x00;
|
||||
|
||||
} else if (
|
||||
avs::game::is_model({"LDJ", "TBS"}) ||
|
||||
// SDVX Valkyrie cabinet mode
|
||||
(avs::game::is_model("KFC") && (avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H'))) {
|
||||
|
||||
// set status to 0 otherwise reader power on fails
|
||||
buffer[0] = 0x00;
|
||||
} else {
|
||||
|
||||
// set status to no card present (1 or 4)
|
||||
buffer[0] = 0x04;
|
||||
}
|
||||
} else { // old reader
|
||||
|
||||
// check for card
|
||||
if (card && accept[unit]) {
|
||||
this->hold[unit] = true;
|
||||
}
|
||||
|
||||
// check for hold
|
||||
if (this->hold[unit]) {
|
||||
|
||||
// set status to card present
|
||||
buffer[0] = 0x02;
|
||||
|
||||
/*
|
||||
* sensors
|
||||
* 0x10 - OLD READER FRONT
|
||||
* 0x20 - OLD READER BACK
|
||||
*/
|
||||
|
||||
// activate both sensors
|
||||
buffer[1] = 0x30;
|
||||
} else {
|
||||
|
||||
// card present but reader isn't accepting it
|
||||
if (card) {
|
||||
|
||||
// set card present
|
||||
buffer[0] = 0x02;
|
||||
|
||||
// set front sensor
|
||||
buffer[1] = 0x10;
|
||||
|
||||
} else {
|
||||
|
||||
// no card present
|
||||
buffer[0] = 0x01;
|
||||
}
|
||||
}
|
||||
|
||||
// card type not present for old reader
|
||||
buffer[10] = 0x00;
|
||||
}
|
||||
|
||||
// other flags
|
||||
buffer[11] = 0x03;
|
||||
buffer[12] = keydown[unit];
|
||||
buffer[13] = 0x00;
|
||||
buffer[14] = (uint8_t) (keypad[unit] >> 8);
|
||||
buffer[15] = (uint8_t) (keypad[unit] & 0xFF);
|
||||
}
|
||||
44
acioemu/icca.h
Normal file
44
acioemu/icca.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
|
||||
#include "device.h"
|
||||
#include "hooks/sleephook.h"
|
||||
#include "reader/crypt.h"
|
||||
|
||||
namespace acioemu {
|
||||
extern bool ICCA_DEVICE_HACK;
|
||||
|
||||
class ICCADevice : public ACIODeviceEmu {
|
||||
private:
|
||||
bool type_new;
|
||||
bool flip_order;
|
||||
std::thread *keypad_thread;
|
||||
std::mutex keypad_mutex;
|
||||
uint8_t **cards;
|
||||
time_t *cards_time;
|
||||
uint8_t *status;
|
||||
bool *accept;
|
||||
bool *hold;
|
||||
uint8_t *keydown;
|
||||
uint16_t *keypad;
|
||||
bool **keypad_last;
|
||||
uint8_t *keypad_capture;
|
||||
std::optional<Crypt> *crypt;
|
||||
uint8_t *counter;
|
||||
|
||||
public:
|
||||
explicit ICCADevice(bool flip_order, bool keypad_thread, uint8_t node_count);
|
||||
~ICCADevice() override;
|
||||
|
||||
bool parse_msg(MessageData *msg_in, circular_buffer<uint8_t> *response_buffer) override;
|
||||
|
||||
void update_card(int unit);
|
||||
void update_keypad(int unit, bool update_edge);
|
||||
void update_status(int unit);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user