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

View File

@@ -0,0 +1,67 @@
#include "bi2x.h"
namespace acio2emu::firmware {
bool BI2XNode::handle_packet(const acio2emu::Packet &in, std::vector<uint8_t> &out) {
auto cur = in.payload.begin();
while ((cur + 1) < in.payload.end()) {
auto cmd = (cur[0] << 8) | cur[1];
out.push_back(*cur++);
out.push_back(*cur++);
out.push_back(0);
switch (cmd) {
case 2: // query firmware version
read_firmware_version(out);
cur = in.payload.end();
break;
case 16:
out.push_back(2);
cur = in.payload.end();
break;
case 800:
case 802:
case 19:
cur = in.payload.end();
break;
case 120:
out.push_back(3);
cur = in.payload.end();
break;
case 801:
out.push_back(33);
out.push_back(0);
cur = in.payload.end();
break;
case 784: // poll input
if (!read_input(out)) {
return false;
}
break;
case 785: { // write output
auto count = write_output(std::span{&*cur, static_cast<size_t>(in.payload.end() - cur)});
if (count < 0) {
return false;
}
cur += count;
break;
}
case 786:
cur += 4;
break;
default:
log_warning("bi2x", "unknown command: {}", cmd);
return false;
}
}
return true;
}
}

22
acio2emu/firmware/bi2x.h Normal file
View File

@@ -0,0 +1,22 @@
#pragma once
#include <vector>
#include <span>
#include <cstdint>
#include "acio2emu/node.h"
#include "util/logging.h"
namespace acio2emu::firmware {
class BI2XNode : public Node {
virtual void read_firmware_version(std::vector<uint8_t> &buffer) = 0;
virtual bool read_input(std::vector<uint8_t> &buffer) = 0;
virtual int write_output(std::span<const uint8_t> buffer) = 0;
/*
* acio2emu::Node
*/
bool handle_packet(const acio2emu::Packet &in, std::vector<uint8_t> &out) override;
};
}

120
acio2emu/handle.cpp Normal file
View File

@@ -0,0 +1,120 @@
#include "handle.h"
#include "util/logging.h"
#include "util/utils.h" // ws2s
namespace acio2emu {
class MasterNode : public Node {
private:
const IOBHandle *iob_;
public:
MasterNode(const IOBHandle *iob) : iob_(iob) { }
bool handle_packet(const Packet &in, std::vector<uint8_t> &out) {
// were we sent a command?
if (in.payload.size() >= 2) {
if (in.payload[0] != 0 || in.payload[1] != 1) {
// unknown command
return false;
}
// assign node ids
out.push_back(0);
out.push_back(1);
for (int i = 0; i < iob_->number_of_nodes(); i++) {
out.push_back(i * 16);
}
}
return true;
}
};
IOBHandle::IOBHandle(std::wstring device) : device_(device) {
nodes_[0] = std::make_unique<MasterNode>(this);
}
bool IOBHandle::register_node(std::unique_ptr<Node> node) {
if ((number_of_nodes_ - 1) >= 16) {
// too many nodes
return false;
}
nodes_[number_of_nodes_++] = std::move(node);
return true;
}
int IOBHandle::number_of_nodes() const {
// don't include the master node
return number_of_nodes_ - 1;
}
void IOBHandle::forward_packet_(const Packet &packet) {
// clear the output queue
output_ = {};
auto node = packet.node / 2;
if (node >= number_of_nodes_) {
log_warning("acio2emu", "cannot forward packet: node out of range: {} >= {}", node, number_of_nodes_);
return;
}
// forward the packet to the node
std::vector<uint8_t> payload;
if (!nodes_[node]->handle_packet(packet, payload)) {
// error in handler
return;
}
// encode the response
encode_packet(output_, node, packet.tag, payload);
}
/*
* CustomHandle
*/
bool IOBHandle::open(LPCWSTR lpFileName) {
if (device_ != lpFileName) {
return false;
}
log_info("acio2emu", "Opened {} (ACIO2)", ws2s(device_));
return true;
}
bool IOBHandle::close() {
log_info("acio2emu", "Closed {} (ACIO2)", ws2s(device_));
return true;
}
int IOBHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) {
auto buffer = reinterpret_cast<uint8_t *>(lpBuffer);
DWORD i = 0;
while (!output_.empty() && i < nNumberOfBytesToRead) {
buffer[i++] = output_.front();
output_.pop();
}
return i;
}
int IOBHandle::write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) {
auto buffer = reinterpret_cast<const uint8_t *>(lpBuffer);
for (DWORD i = 0; i < nNumberOfBytesToWrite; i++) {
if (decoder_.update(buffer[i])) {
// forward the packet to a node
forward_packet_(decoder_.packet());
}
}
return nNumberOfBytesToWrite;
}
int IOBHandle::device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize) {
return -1;
}
}

44
acio2emu/handle.h Normal file
View File

@@ -0,0 +1,44 @@
#pragma once
#include <string>
#include <array>
#include <queue>
#include <memory> // std::unique_ptr
#include <cstdint>
#include "acio2emu/packet.h"
#include "acio2emu/node.h"
#include "hooks/devicehook.h"
namespace acio2emu {
class IOBHandle : public CustomHandle {
private:
std::wstring device_;
std::array<std::unique_ptr<Node>, 17> nodes_;
// the first node is reserved for the "master" node
int number_of_nodes_ = 1;
PacketDecoder decoder_;
std::queue<uint8_t> output_;
void forward_packet_(const Packet &packet);
public:
IOBHandle(std::wstring device);
bool register_node(std::unique_ptr<Node> node);
int number_of_nodes() const;
/*
* CustomHandle
*/
bool open(LPCWSTR lpFileName) override;
int read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) override;
int write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) override;
int device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize) override;
bool close() override;
};
}

40
acio2emu/internal/crc.h Normal file
View File

@@ -0,0 +1,40 @@
#pragma once
#include <cstdint>
#include <cstddef>
namespace acio2emu::detail {
inline uint8_t crc4_lgp_c(uint8_t crc, const uint8_t *data, size_t len) {
static constexpr uint8_t tbl[] = {
0x00, 0x0D, 0x03, 0x0E,
0x06, 0x0B, 0x05, 0x08,
0x0C, 0x01, 0x0F, 0x02,
0x0A, 0x07, 0x09, 0x04,
};
crc &= 15;
for (size_t i = 0; i < len; i++) {
auto b = data[i];
crc = (((crc >> 4) ^ (tbl[(b ^ crc) & 0x0F])) >> 4) ^ tbl[(((crc >> 4) ^ (tbl[(b ^ crc) & 0x0F])) ^ (b >> 4)) & 0x0F];
}
return crc;
}
inline uint8_t crc7_lgp_48(uint8_t crc, const uint8_t *data, size_t len) {
static constexpr uint8_t tbl[] = {
0x00, 0x09, 0x12, 0x1B,
0x24, 0x2D, 0x36, 0x3F,
0x48, 0x41, 0x5A, 0x53,
0x6C, 0x65, 0x7E, 0x77
};
crc &= 127;
for (size_t i = 0; i < len; i++) {
auto b = data[i];
crc = (((crc >> 4) ^ (tbl[(b ^ crc) & 0x0F])) >> 4) ^ tbl[(((crc >> 4) ^ (tbl[(b ^ crc) & 0x0F])) ^ (b >> 4)) & 0x0F];
}
return crc;
}
}

140
acio2emu/internal/lz.h Normal file
View File

@@ -0,0 +1,140 @@
#pragma once
#include <queue>
#include <cstdint>
namespace acio2emu::detail {
class InflateTransformer {
private:
std::queue<uint8_t> output_;
uint8_t flags_ = 0, flag_shift_ = 0;
uint8_t window_[85] = {};
int window_offset_ = 81;
enum class inflateStep {
readFlags,
processFlags,
copyStored,
copyFromWindow,
} step_ = inflateStep::readFlags;
void window_put_(uint8_t b) {
window_[window_offset_++] = b;
window_offset_ %= sizeof(window_);
}
uint8_t window_get_(int offset) {
return window_[offset % sizeof(window_)];
}
public:
void put(uint8_t b) {
auto consumed = false;
while (true) {
switch (step_) {
case inflateStep::readFlags:
if (consumed) {
// need more data
return;
}
consumed = true;
flags_ = b;
flag_shift_ = 0;
step_ = inflateStep::processFlags;
break;
case inflateStep::processFlags:
// have we processed every flag?
if (flag_shift_ > 6) {
step_ = inflateStep::readFlags;
break;
}
if (flags_ & (1 << flag_shift_)) {
flag_shift_++;
if (flags_ & (1 << flag_shift_)) {
// emit 0xAA when both bits are set
output_.push(0xAA);
}
else {
// copy from the window when only the lower bit is set
step_ = inflateStep::copyFromWindow;
}
}
else {
step_ = inflateStep::copyStored;
}
flag_shift_++;
break;
case inflateStep::copyFromWindow: {
if (consumed) {
// need more data
return;
}
consumed = true;
// determine the match size, default is 2-bytes
auto offset = b;
auto size = 2;
if (offset >= 0xAA) {
// 4-byte match
size = 4;
offset -= 0xAB;
}
else if (offset >= 0x55) {
// 3-byte match
size = 3;
offset -= 0x55;
}
for (auto i = 0; i < size; i ++) {
auto cur = window_get_(offset + i);
window_put_(cur);
output_.push(cur);
}
// continue processing flags
step_ = inflateStep::processFlags;
break;
}
case inflateStep::copyStored:
if (consumed) {
// need more data
return;
}
consumed = true;
window_put_(b);
output_.push(b);
// continue processing flags
step_ = inflateStep::processFlags;
break;
}
}
}
int get() {
if (output_.empty()) {
// output queue is empty
return -1;
}
auto b = output_.front();
output_.pop();
return b;
}
};
}

14
acio2emu/node.h Normal file
View File

@@ -0,0 +1,14 @@
#pragma once
#include <vector>
#include "acio2emu/packet.h"
namespace acio2emu {
class Node {
public:
virtual ~Node() {}
virtual bool handle_packet(const Packet &in, std::vector<uint8_t> &out) = 0;
};
}

259
acio2emu/packet.cpp Normal file
View File

@@ -0,0 +1,259 @@
#include "packet.h"
#include "util/logging.h"
#include "acio2emu/internal/crc.h"
namespace acio2emu {
static constexpr uint8_t SOF = 0xAA;
static constexpr uint8_t ESC = 0xFF;
static void encode_payload_(std::queue<uint8_t> &out, const std::vector<uint8_t> &payload) {
for (auto b : payload) {
if (b == SOF || b == ESC) {
out.push(ESC);
b = ~b;
}
out.push(b);
}
// compute and write the payload's CRC
out.push(detail::crc7_lgp_48(0x7F, payload.data(), payload.size()) ^ 0x7F);
}
bool encode_packet(std::queue<uint8_t> &out, uint8_t node, uint8_t tag, const std::vector<uint8_t> &payload) {
auto size = payload.size();
if (size > 127) {
log_warning("acio2emu", "cannot encode packet: payload too large: {} > 127", payload.size());
return false;
}
// build the header
uint8_t header[5] = {
SOF,
static_cast<uint8_t>(node * 3),
tag,
static_cast<uint8_t>(size),
0,
};
// compute the header's CRC
header[4] = detail::crc4_lgp_c(0x0F, &header[1], sizeof(header) - 1) ^ 0x0F;
// push the header to the output queue
for (size_t i = 0; i < sizeof(header); i++) {
out.push(header[i]);
}
encode_payload_(out, payload);
return true;
}
void PacketDecoder::set_step_(readStep s) {
#ifndef NDEBUG
auto valid = true;
switch (s) {
case readStep::idle:
case readStep::readNode:
// transition from any step/state allowed
break;
case readStep::readTag:
if (step_ != readStep::readNode) {
valid = false;
}
break;
case readStep::readPayloadSize:
if (step_ != readStep::readTag) {
valid = false;
}
break;
case readStep::readPayloadFlags:
if (step_ != readStep::readPayloadSize) {
valid = false;
}
break;
case readStep::readReplacementByte:
if (step_ != readStep::readPayloadFlags) {
valid = false;
}
break;
case readStep::readPayload:
if (step_ != readStep::readPayloadFlags &&
step_ != readStep::readReplacementByte &&
step_ != readStep::readEscaped
) {
valid = false;
}
break;
case readStep::readEscaped:
if (step_ != readStep::readPayload) {
valid = false;
}
break;
default:
log_fatal("acio2emu", "cannot set step: unknown value: {}", s);
break;
}
if (!valid) {
log_fatal("acio2emu", "illegal transition detected: {} -> {}", step_, s);
}
#endif
step_ = s;
}
int PacketDecoder::update_payload_size_(uint8_t b) {
if ((b & 0x80) == 0) {
payload_size_ = (payload_size_ << 7) | (b & 0x7F);
// finished
return 0;
}
else if ((b & 0x40) != 0 && payload_size_count_ < 5) {
payload_size_count_++;
payload_size_count_ = (payload_size_count_ << 6) | (b & 0x3F);
// continuation required
return 1;
}
else {
// invalid value or invalid state
return -1;
}
}
uint8_t PacketDecoder::deobfuscate_(uint8_t b) {
if ((b ^ 0xAA) == 0) {
return b;
}
auto mask = 0x55;
if ((b & 0x80) == 0) {
mask = 0x7F;
}
return (b ^ lcg_()) & mask;
}
void PacketDecoder::reset_(readStep s) {
set_step_(s);
packet_ = {};
payload_size_ = 0;
payload_size_count_ = 0;
}
bool PacketDecoder::update(uint8_t b) {
// is this the start of a packet?
if (b == SOF) {
reset_(readStep::readNode);
return false;
}
switch (step_) {
case readStep::readNode:
packet_.node = b;
set_step_(readStep::readTag);
break;
case readStep::readTag:
packet_.tag = b;
set_step_(readStep::readPayloadSize);
break;
case readStep::readPayloadSize: {
auto status = update_payload_size_(b);
if (status == 0) {
// finished reading payload size
packet_.payload.reserve(payload_size_);
set_step_(readStep::readPayloadFlags);
}
else if (status == -1) {
// reset on error
reset_(readStep::idle);
}
break;
}
case readStep::readPayloadFlags:
obfuscated_ = (b & (1 << 4)) != 0;
encoding_ = static_cast<payloadEncoding>(b >> 5);
if (obfuscated_) {
lcg_.seed(packet_.tag ^ 0x55);
}
if (encoding_ == payloadEncoding::replace) {
set_step_(readStep::readReplacementByte);
}
else {
set_step_(readStep::readPayload);
if (encoding_ == payloadEncoding::lz) {
// reset the InflateTransformer
inflate_ = {};
}
}
break;
case readStep::readReplacementByte:
substitute_ = b;
set_step_(readStep::readPayload);
break;
case readStep::readPayload:
// do we need to deobfuscate?
if (obfuscated_) {
b = deobfuscate_(b);
}
if (encoding_ == payloadEncoding::lz) {
inflate_.put(b);
for (int i = inflate_.get(); i >= 0; i = inflate_.get()) {
packet_.payload.push_back(i);
}
}
else if (encoding_ == payloadEncoding::replace && b == substitute_) {
packet_.payload.push_back(SOF);
}
else if (encoding_ == payloadEncoding::byteStuffing && b == ESC) {
set_step_(readStep::readEscaped);
break;
}
else {
packet_.payload.push_back(b);
}
break;
case readStep::readEscaped:
b = ~b;
if (obfuscated_) {
b = deobfuscate_(b);
}
packet_.payload.push_back(b);
set_step_(readStep::readPayload);
break;
default:
break;
}
if ((step_ == readStep::readPayload || step_ == readStep::readPayloadFlags) &&
(packet_.payload.size() >= payload_size_)) {
set_step_(readStep::idle);
// finished reading packet
return true;
}
return false;
}
const Packet &PacketDecoder::packet() {
return packet_;
}
}

65
acio2emu/packet.h Normal file
View File

@@ -0,0 +1,65 @@
#pragma once
#include <vector>
#include <queue>
#include <random> // std::linear_congruential_engine
#include <cstdint>
#include "acio2emu/internal/lz.h"
namespace acio2emu {
struct Packet {
uint8_t node;
uint8_t tag;
std::vector<uint8_t> payload;
};
class PacketDecoder {
private:
Packet packet_ = {};
// order matters, don't change this enum!
enum payloadEncoding {
byteStuffing,
raw,
unknown,
replace,
lz,
} encoding_;
uint32_t payload_size_ = 0, payload_size_count_ = 0;
// payloadEncoding::replace state
uint8_t substitute_;
// payloadEncoding::lz state
detail::InflateTransformer inflate_;
// deobfuscation state
bool obfuscated_;
std::linear_congruential_engine<uint32_t, 1103515245, 12345, 0> lcg_;
enum class readStep {
idle,
readNode,
readTag,
readPayloadSize,
readPayloadFlags,
readReplacementByte,
readPayload,
readEscaped,
} step_ = readStep::idle;
void set_step_(readStep s);
void reset_(readStep s);
int update_payload_size_(uint8_t b);
uint8_t deobfuscate_(uint8_t b);
public:
bool update(uint8_t b);
const Packet &packet();
};
bool encode_packet(std::queue<uint8_t> &out, uint8_t node, uint8_t tag, const std::vector<uint8_t> &payload);
}