Bemanitools v5.26 release
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
libs += acioemu
|
||||
|
||||
src_acioemu := \
|
||||
addr.c \
|
||||
emu.c \
|
||||
h44b.c \
|
||||
hdxs.c \
|
||||
icca.c \
|
||||
iccb.c \
|
||||
pipe.c \
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include "acio/acio.h"
|
||||
|
||||
#include "acioemu/addr.h"
|
||||
#include "acioemu/emu.h"
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
void ac_io_emu_cmd_assign_addrs(
|
||||
struct ac_io_emu *emu,
|
||||
const struct ac_io_message *req,
|
||||
uint8_t node_count)
|
||||
{
|
||||
struct ac_io_message resp;
|
||||
uint16_t cmd;
|
||||
|
||||
log_assert(emu != NULL);
|
||||
log_assert(req != NULL);
|
||||
|
||||
cmd = ac_io_u16(req->cmd.code);
|
||||
|
||||
if (cmd != AC_IO_CMD_ASSIGN_ADDRS) {
|
||||
log_warning(
|
||||
"Address 0 expects address assignment cmd, got %04x",
|
||||
cmd);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
memset(&resp, 0, sizeof(resp));
|
||||
resp.addr = 0;
|
||||
resp.cmd.code = req->cmd.code;
|
||||
resp.cmd.seq_no = req->cmd.seq_no;
|
||||
resp.cmd.nbytes = sizeof(resp.cmd.count);
|
||||
resp.cmd.count = node_count;
|
||||
|
||||
ac_io_emu_response_push(emu, &resp, 0);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#ifndef AC_IO_EMU_ADDR_H
|
||||
#define AC_IO_EMU_ADDR_H
|
||||
|
||||
#include "acio/acio.h"
|
||||
|
||||
#include "acioemu/emu.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
void ac_io_emu_cmd_assign_addrs(
|
||||
struct ac_io_emu *emu,
|
||||
const struct ac_io_message *req,
|
||||
uint8_t node_count);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,194 @@
|
||||
#include <windows.h> /* Usermode API */
|
||||
|
||||
#include <ntdef.h> /* Kernel-mode API for ioctls */
|
||||
#include <devioctl.h>
|
||||
#include <ntddser.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include "acioemu/emu.h"
|
||||
#include "acioemu/pipe.h"
|
||||
|
||||
#include "hook/iohook.h"
|
||||
|
||||
#include "util/log.h"
|
||||
#include "util/str.h"
|
||||
|
||||
static HRESULT ac_io_emu_open(struct ac_io_emu *emu, struct irp *irp);
|
||||
static HRESULT ac_io_emu_close(struct ac_io_emu *emu, struct irp *irp);
|
||||
static HRESULT ac_io_emu_read(struct ac_io_emu *emu, struct irp *irp);
|
||||
static HRESULT ac_io_emu_write(struct ac_io_emu *emu, struct irp *irp);
|
||||
static HRESULT ac_io_emu_ioctl(struct ac_io_emu *emu, struct irp *irp);
|
||||
|
||||
void ac_io_emu_init(struct ac_io_emu *emu, const wchar_t *filename)
|
||||
{
|
||||
log_assert(emu != NULL);
|
||||
log_assert(filename != NULL);
|
||||
|
||||
memset(emu, 0, sizeof(*emu));
|
||||
emu->fd = iohook_open_dummy_fd();
|
||||
emu->wfilename = wstr_dup(filename);
|
||||
wstr_narrow(filename, &emu->filename);
|
||||
ac_io_in_init(&emu->in);
|
||||
ac_io_out_init(&emu->out);
|
||||
}
|
||||
|
||||
void ac_io_emu_fini(struct ac_io_emu *emu)
|
||||
{
|
||||
log_assert(emu != NULL);
|
||||
|
||||
free(emu->filename);
|
||||
free(emu->wfilename);
|
||||
|
||||
if (emu->fd != NULL) {
|
||||
CloseHandle(emu->fd);
|
||||
}
|
||||
|
||||
memset(emu, 0, sizeof(*emu));
|
||||
}
|
||||
|
||||
bool ac_io_emu_match_irp(const struct ac_io_emu *emu, const struct irp *irp)
|
||||
{
|
||||
log_assert(emu != NULL);
|
||||
log_assert(irp != NULL);
|
||||
|
||||
if (irp->op == IRP_OP_OPEN) {
|
||||
return wstr_eq(emu->wfilename, irp->open_filename);
|
||||
} else {
|
||||
return irp->fd == emu->fd;
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT ac_io_emu_dispatch_irp(struct ac_io_emu *emu, struct irp *irp)
|
||||
{
|
||||
log_assert(irp != NULL);
|
||||
|
||||
switch (irp->op) {
|
||||
case IRP_OP_OPEN: return ac_io_emu_open(emu, irp);
|
||||
case IRP_OP_CLOSE: return ac_io_emu_close(emu, irp);
|
||||
case IRP_OP_READ: return ac_io_emu_read(emu, irp);
|
||||
case IRP_OP_WRITE: return ac_io_emu_write(emu, irp);
|
||||
case IRP_OP_IOCTL: return ac_io_emu_ioctl(emu, irp);
|
||||
case IRP_OP_FSYNC: return S_FALSE;
|
||||
default: return E_NOTIMPL;
|
||||
}
|
||||
}
|
||||
|
||||
static HRESULT ac_io_emu_open(struct ac_io_emu *emu, struct irp *irp)
|
||||
{
|
||||
irp->fd = emu->fd;
|
||||
log_info("%s: ACIO port opened", emu->filename);
|
||||
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
static HRESULT ac_io_emu_close(struct ac_io_emu *emu, struct irp *irp)
|
||||
{
|
||||
log_info("%s: ACIO port closed", emu->filename);
|
||||
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
static HRESULT ac_io_emu_read(struct ac_io_emu *emu, struct irp *irp)
|
||||
{
|
||||
ac_io_in_drain(&emu->in, &irp->read);
|
||||
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
static HRESULT ac_io_emu_write(struct ac_io_emu *emu, struct irp *irp)
|
||||
{
|
||||
const struct ac_io_message *msg;
|
||||
|
||||
for (;;) {
|
||||
ac_io_out_supply(&emu->out, &irp->write);
|
||||
|
||||
if (!ac_io_out_have_message(&emu->out)) {
|
||||
break;
|
||||
}
|
||||
|
||||
msg = ac_io_out_get_message(&emu->out);
|
||||
|
||||
if (msg != NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
ac_io_in_supply(&emu->in, NULL, 0);
|
||||
ac_io_out_consume_message(&emu->out);
|
||||
}
|
||||
|
||||
return ac_io_out_have_message(&emu->out) ? S_OK : S_FALSE;
|
||||
}
|
||||
|
||||
static HRESULT ac_io_emu_ioctl(struct ac_io_emu *emu, struct irp *irp)
|
||||
{
|
||||
SERIAL_STATUS *status;
|
||||
|
||||
log_assert(irp != NULL);
|
||||
|
||||
switch (irp->ioctl) {
|
||||
case IOCTL_SERIAL_GET_COMMSTATUS:
|
||||
if (irp->read.bytes == NULL) {
|
||||
log_warning("IOCTL_SERIAL_GET_COMMSTATUS: Output buffer is NULL");
|
||||
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
|
||||
if (irp->read.nbytes < sizeof(*status)) {
|
||||
log_warning("IOCTL_SERIAL_GET_COMMSTATUS: Buffer is too small");
|
||||
|
||||
return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
|
||||
}
|
||||
|
||||
status = (SERIAL_STATUS *) irp->read.bytes;
|
||||
status->Errors = 0;
|
||||
status->AmountInInQueue = ac_io_in_is_msg_pending(&emu->in);
|
||||
|
||||
irp->read.pos = sizeof(*status);
|
||||
|
||||
return S_FALSE;
|
||||
|
||||
default:
|
||||
return S_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
const struct ac_io_message *ac_io_emu_request_peek(const struct ac_io_emu *emu)
|
||||
{
|
||||
log_assert(emu != NULL);
|
||||
|
||||
return ac_io_out_get_message(&emu->out);
|
||||
}
|
||||
|
||||
void ac_io_emu_request_pop(struct ac_io_emu *emu)
|
||||
{
|
||||
log_assert(emu != NULL);
|
||||
|
||||
ac_io_out_consume_message(&emu->out);
|
||||
}
|
||||
|
||||
void ac_io_emu_response_push(
|
||||
struct ac_io_emu *emu,
|
||||
const struct ac_io_message *resp,
|
||||
uint64_t delay_us)
|
||||
{
|
||||
log_assert(emu != NULL);
|
||||
log_assert(resp != NULL);
|
||||
|
||||
ac_io_in_supply(&emu->in, resp, delay_us);
|
||||
}
|
||||
|
||||
void ac_io_emu_response_push_thunk(
|
||||
struct ac_io_emu *emu,
|
||||
ac_io_in_thunk_t thunk,
|
||||
void *ctx,
|
||||
uint64_t delay_us)
|
||||
{
|
||||
log_assert(emu != NULL);
|
||||
log_assert(thunk != NULL);
|
||||
|
||||
ac_io_in_supply_thunk(&emu->in, thunk, ctx, delay_us);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
#ifndef ACIOEMU_EMU_H
|
||||
#define ACIOEMU_EMU_H
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include "acio/acio.h"
|
||||
|
||||
#include "acioemu/pipe.h"
|
||||
|
||||
#include "hook/iohook.h"
|
||||
|
||||
struct ac_io_emu {
|
||||
HANDLE fd;
|
||||
wchar_t *wfilename;
|
||||
char *filename;
|
||||
struct ac_io_in in;
|
||||
struct ac_io_out out;
|
||||
};
|
||||
|
||||
void ac_io_emu_init(struct ac_io_emu *emu, const wchar_t *filename);
|
||||
void ac_io_emu_fini(struct ac_io_emu *emu);
|
||||
bool ac_io_emu_match_irp(const struct ac_io_emu *emu, const struct irp *irp);
|
||||
HRESULT ac_io_emu_dispatch_irp(struct ac_io_emu *emu, struct irp *irp);
|
||||
const struct ac_io_message *ac_io_emu_request_peek(const struct ac_io_emu *emu);
|
||||
void ac_io_emu_request_pop(struct ac_io_emu *emu);
|
||||
void ac_io_emu_response_push(
|
||||
struct ac_io_emu *emu,
|
||||
const struct ac_io_message *resp,
|
||||
uint64_t delay_ms);
|
||||
void ac_io_emu_response_push_thunk(
|
||||
struct ac_io_emu *emu,
|
||||
ac_io_in_thunk_t thunk,
|
||||
void *ctx,
|
||||
uint64_t delay_ms);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,118 @@
|
||||
#define LOG_MODULE "acioemu-h44b"
|
||||
|
||||
#include "acioemu/h44b.h"
|
||||
|
||||
#include <windows.h> /* for _BitScanForward */
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "acio/h44b.h"
|
||||
|
||||
#include "acioemu/emu.h"
|
||||
|
||||
#include "bemanitools/jbio.h"
|
||||
|
||||
#include "util/hex.h"
|
||||
|
||||
static void ac_io_emu_h44b_cmd_send_version(
|
||||
struct ac_io_emu_h44b *h44b,
|
||||
const struct ac_io_message *req);
|
||||
|
||||
static void ac_io_emu_h44b_send_status(
|
||||
struct ac_io_emu_h44b *h44b,
|
||||
const struct ac_io_message *req,
|
||||
uint8_t status);
|
||||
|
||||
void ac_io_emu_h44b_init(
|
||||
struct ac_io_emu_h44b *h44b,
|
||||
struct ac_io_emu *emu,
|
||||
uint8_t unit_no)
|
||||
{
|
||||
memset(h44b, 0, sizeof(*h44b));
|
||||
h44b->emu = emu;
|
||||
h44b->unit_no = unit_no;
|
||||
}
|
||||
|
||||
void ac_io_emu_h44b_dispatch_request(
|
||||
struct ac_io_emu_h44b *h44b,
|
||||
const struct ac_io_message *req)
|
||||
{
|
||||
uint16_t cmd_code;
|
||||
|
||||
cmd_code = ac_io_u16(req->cmd.code);
|
||||
|
||||
switch (cmd_code) {
|
||||
case AC_IO_CMD_GET_VERSION:
|
||||
log_misc("AC_IO_CMD_GET_VERSION(%d)", req->addr);
|
||||
ac_io_emu_h44b_cmd_send_version(h44b, req);
|
||||
|
||||
break;
|
||||
|
||||
case AC_IO_CMD_START_UP:
|
||||
log_misc("AC_IO_CMD_START_UP(%d)", req->addr);
|
||||
ac_io_emu_h44b_send_status(h44b, req, 0x00);
|
||||
|
||||
break;
|
||||
|
||||
case AC_IO_H44B_CMD_SET_OUTPUTS:
|
||||
/* Not using the struct ac_io_h44b_output here */
|
||||
for (int i = 0; i < 6; i++) {
|
||||
jb_io_set_rgb_led((enum jb_io_rgb_led) i,
|
||||
req->cmd.raw[i * 3],
|
||||
req->cmd.raw[i * 3 + 1],
|
||||
req->cmd.raw[i * 3 + 2]);
|
||||
}
|
||||
|
||||
jb_io_write_outputs();
|
||||
|
||||
ac_io_emu_h44b_send_status(h44b, req, 0x00);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
log_warning("Unknown ACIO message %04x on h44b node, addr=%d",
|
||||
cmd_code, req->addr);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void ac_io_emu_h44b_cmd_send_version(
|
||||
struct ac_io_emu_h44b *h44b,
|
||||
const struct ac_io_message *req)
|
||||
{
|
||||
struct ac_io_message resp;
|
||||
|
||||
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
|
||||
resp.cmd.code = req->cmd.code;
|
||||
resp.cmd.seq_no = req->cmd.seq_no;
|
||||
resp.cmd.nbytes = sizeof(resp.cmd.version);
|
||||
resp.cmd.version.type = ac_io_u32(AC_IO_NODE_TYPE_H44B);
|
||||
resp.cmd.version.flag = 0x00;
|
||||
resp.cmd.version.major = 0x01;
|
||||
resp.cmd.version.minor = 0x00;
|
||||
resp.cmd.version.revision = 0x02;
|
||||
memcpy(resp.cmd.version.product_code, "H44B",
|
||||
sizeof(resp.cmd.version.product_code));
|
||||
strncpy(resp.cmd.version.date, __DATE__, sizeof(resp.cmd.version.date));
|
||||
strncpy(resp.cmd.version.time, __TIME__, sizeof(resp.cmd.version.time));
|
||||
|
||||
ac_io_emu_response_push(h44b->emu, &resp, 0);
|
||||
}
|
||||
|
||||
static void ac_io_emu_h44b_send_status(
|
||||
struct ac_io_emu_h44b *h44b,
|
||||
const struct ac_io_message *req,
|
||||
uint8_t status)
|
||||
{
|
||||
struct ac_io_message resp;
|
||||
|
||||
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
|
||||
resp.cmd.code = req->cmd.code;
|
||||
resp.cmd.seq_no = req->cmd.seq_no;
|
||||
resp.cmd.nbytes = sizeof(resp.cmd.status);
|
||||
resp.cmd.status = status;
|
||||
|
||||
ac_io_emu_response_push(h44b->emu, &resp, 0);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#ifndef AC_IO_EMU_H44B_H
|
||||
#define AC_IO_EMU_H44B_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "acioemu/emu.h"
|
||||
|
||||
struct ac_io_emu_h44b {
|
||||
struct ac_io_emu *emu;
|
||||
uint8_t unit_no;
|
||||
// TODO
|
||||
};
|
||||
|
||||
void acioemu_h44b_init(void);
|
||||
|
||||
void ac_io_emu_h44b_init(
|
||||
struct ac_io_emu_h44b *h44b,
|
||||
struct ac_io_emu *emu,
|
||||
uint8_t unit_no);
|
||||
|
||||
void ac_io_emu_h44b_dispatch_request(
|
||||
struct ac_io_emu_h44b *h44b,
|
||||
const struct ac_io_message *req);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,133 @@
|
||||
#define LOG_MODULE "acioemu-hdxs"
|
||||
|
||||
#include "acio/acio.h"
|
||||
|
||||
#include "acioemu/emu.h"
|
||||
#include "acioemu/hdxs.h"
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
static void ac_io_emu_hdxs_cmd_send_version(
|
||||
struct ac_io_emu_hdxs *hdxs,
|
||||
const struct ac_io_message *req);
|
||||
|
||||
static void ac_io_emu_hdxs_send_empty(
|
||||
struct ac_io_emu_hdxs *hdxs,
|
||||
const struct ac_io_message *req);
|
||||
|
||||
static void ac_io_emu_hdxs_send_status(
|
||||
struct ac_io_emu_hdxs *hdxs,
|
||||
const struct ac_io_message *req,
|
||||
uint8_t status);
|
||||
|
||||
void ac_io_emu_hdxs_init(
|
||||
struct ac_io_emu_hdxs *hdxs,
|
||||
struct ac_io_emu *emu)
|
||||
{
|
||||
log_assert(hdxs != NULL);
|
||||
log_assert(emu != NULL);
|
||||
|
||||
hdxs->emu = emu;
|
||||
}
|
||||
|
||||
void ac_io_emu_hdxs_dispatch_request(
|
||||
struct ac_io_emu_hdxs *hdxs,
|
||||
const struct ac_io_message *req)
|
||||
{
|
||||
uint16_t cmd_code;
|
||||
|
||||
log_assert(hdxs != NULL);
|
||||
log_assert(req != NULL);
|
||||
|
||||
cmd_code = ac_io_u16(req->cmd.code);
|
||||
|
||||
switch (cmd_code) {
|
||||
case AC_IO_CMD_GET_VERSION:
|
||||
log_misc("AC_IO_CMD_GET_VERSION(%d)", req->addr);
|
||||
ac_io_emu_hdxs_cmd_send_version(hdxs, req);
|
||||
|
||||
break;
|
||||
|
||||
case AC_IO_CMD_START_UP:
|
||||
log_misc("AC_IO_CMD_START_UP(%d)", req->addr);
|
||||
ac_io_emu_hdxs_send_status(hdxs, req, 0x00);
|
||||
|
||||
break;
|
||||
|
||||
case AC_IO_CMD_CLEAR:
|
||||
log_misc("AC_IO_CMD_CLEAR(%d)", req->addr);
|
||||
|
||||
case 0x110:
|
||||
case 0x112:
|
||||
case 0x128:
|
||||
ac_io_emu_hdxs_send_status(hdxs, req, 0x00);
|
||||
|
||||
break;
|
||||
|
||||
case AC_IO_CMD_KEEPALIVE:
|
||||
ac_io_emu_hdxs_send_empty(hdxs, req);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
log_warning(
|
||||
"Unknown ACIO message %04x on HDXS node, addr=%d",
|
||||
cmd_code,
|
||||
req->addr);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void ac_io_emu_hdxs_cmd_send_version(
|
||||
struct ac_io_emu_hdxs *hdxs,
|
||||
const struct ac_io_message *req)
|
||||
{
|
||||
struct ac_io_message resp;
|
||||
|
||||
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
|
||||
resp.cmd.code = req->cmd.code;
|
||||
resp.cmd.seq_no = req->cmd.seq_no;
|
||||
resp.cmd.nbytes = sizeof(resp.cmd.version);
|
||||
resp.cmd.version.type = ac_io_u32(AC_IO_NODE_TYPE_LED_STRIP);
|
||||
resp.cmd.version.flag = 0x00;
|
||||
resp.cmd.version.major = 0x01;
|
||||
resp.cmd.version.minor = 0x06;
|
||||
resp.cmd.version.revision = 0x00;
|
||||
memcpy(resp.cmd.version.product_code, "HDXS",
|
||||
sizeof(resp.cmd.version.product_code));
|
||||
strncpy(resp.cmd.version.date, __DATE__, sizeof(resp.cmd.version.date));
|
||||
strncpy(resp.cmd.version.time, __TIME__, sizeof(resp.cmd.version.time));
|
||||
|
||||
ac_io_emu_response_push(hdxs->emu, &resp, 0);
|
||||
}
|
||||
|
||||
static void ac_io_emu_hdxs_send_empty(
|
||||
struct ac_io_emu_hdxs *hdxs,
|
||||
const struct ac_io_message *req)
|
||||
{
|
||||
struct ac_io_message resp;
|
||||
|
||||
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
|
||||
resp.cmd.code = req->cmd.code;
|
||||
resp.cmd.seq_no = req->cmd.seq_no;
|
||||
resp.cmd.nbytes = 0;
|
||||
|
||||
ac_io_emu_response_push(hdxs->emu, &resp, 0);
|
||||
}
|
||||
|
||||
static void ac_io_emu_hdxs_send_status(
|
||||
struct ac_io_emu_hdxs *hdxs,
|
||||
const struct ac_io_message *req,
|
||||
uint8_t status)
|
||||
{
|
||||
struct ac_io_message resp;
|
||||
|
||||
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
|
||||
resp.cmd.code = req->cmd.code;
|
||||
resp.cmd.seq_no = req->cmd.seq_no;
|
||||
resp.cmd.nbytes = sizeof(resp.cmd.status);
|
||||
resp.cmd.status = status;
|
||||
|
||||
ac_io_emu_response_push(hdxs->emu, &resp, 0);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef AC_IO_EMU_HDXS_H
|
||||
#define AC_IO_EMU_HDXS_H
|
||||
|
||||
#include "acio/acio.h"
|
||||
|
||||
#include "acioemu/emu.h"
|
||||
|
||||
struct ac_io_emu_hdxs {
|
||||
struct ac_io_emu *emu;
|
||||
// TODO ops vtbl
|
||||
};
|
||||
|
||||
void ac_io_emu_hdxs_init(
|
||||
struct ac_io_emu_hdxs *hdxs,
|
||||
struct ac_io_emu *emu);
|
||||
|
||||
void ac_io_emu_hdxs_dispatch_request(
|
||||
struct ac_io_emu_hdxs *hdxs,
|
||||
const struct ac_io_message *req);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,385 @@
|
||||
#define LOG_MODULE "acioemu-icca"
|
||||
|
||||
#include "acioemu/icca.h"
|
||||
|
||||
#include <windows.h> /* for _BitScanForward */
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "acio/icca.h"
|
||||
|
||||
#include "acioemu/emu.h"
|
||||
#include "acioemu/icca.h"
|
||||
|
||||
#include "bemanitools/eamio.h"
|
||||
|
||||
#include "util/time.h"
|
||||
|
||||
enum ac_io_icca_subcmd {
|
||||
AC_IO_ICCA_SUBCMD_CARD_SLOT_CLOSE = 0x00,
|
||||
AC_IO_ICCA_SUBCMD_CARD_SLOT_OPEN = 0x11,
|
||||
AC_IO_ICCA_SUBCMD_CARD_SLOT_EJECT = 0x12,
|
||||
};
|
||||
|
||||
enum ac_io_icca_flag {
|
||||
AC_IO_ICCA_FLAG_FRONT_SENSOR = 0x10,
|
||||
AC_IO_ICCA_FLAG_REAR_SENSOR = 0x20,
|
||||
AC_IO_ICCA_FLAG_SOLENOID = 0x40
|
||||
};
|
||||
|
||||
enum ac_io_icca_status_code {
|
||||
AC_IO_ICCA_STATUS_FAULT = 0x00,
|
||||
AC_IO_ICCA_STATUS_IDLE = 0x01,
|
||||
AC_IO_ICCA_STATUS_GOT_UID = 0x02
|
||||
};
|
||||
|
||||
static void ac_io_emu_icca_cmd_send_version(
|
||||
struct ac_io_emu_icca *icca,
|
||||
const struct ac_io_message *req);
|
||||
|
||||
static void ac_io_emu_icca_send_state(
|
||||
struct ac_io_emu_icca *icca,
|
||||
const struct ac_io_message *req,
|
||||
uint64_t delay_us);
|
||||
|
||||
static void ac_io_emu_icca_send_empty(
|
||||
struct ac_io_emu_icca *icca,
|
||||
const struct ac_io_message *req);
|
||||
|
||||
static void ac_io_emu_icca_send_status(
|
||||
struct ac_io_emu_icca *icca,
|
||||
const struct ac_io_message *req,
|
||||
uint8_t status);
|
||||
|
||||
void ac_io_emu_icca_init(
|
||||
struct ac_io_emu_icca *icca,
|
||||
struct ac_io_emu *emu,
|
||||
uint8_t unit_no)
|
||||
{
|
||||
memset(icca, 0, sizeof(*icca));
|
||||
icca->emu = emu;
|
||||
icca->unit_no = unit_no;
|
||||
// queue must be started
|
||||
icca->fault = true;
|
||||
}
|
||||
|
||||
void ac_io_emu_icca_dispatch_request(
|
||||
struct ac_io_emu_icca *icca,
|
||||
const struct ac_io_message *req)
|
||||
{
|
||||
uint16_t cmd_code;
|
||||
uint64_t delay_us;
|
||||
|
||||
cmd_code = ac_io_u16(req->cmd.code);
|
||||
|
||||
switch (cmd_code) {
|
||||
case AC_IO_CMD_GET_VERSION:
|
||||
log_misc("AC_IO_CMD_GET_VERSION(%d)", req->addr);
|
||||
ac_io_emu_icca_cmd_send_version(icca, req);
|
||||
|
||||
break;
|
||||
|
||||
case AC_IO_CMD_START_UP:
|
||||
log_misc("AC_IO_CMD_START_UP(%d)", req->addr);
|
||||
icca->detected_new_reader = false;
|
||||
ac_io_emu_icca_send_status(icca, req, 0x00);
|
||||
|
||||
break;
|
||||
|
||||
case AC_IO_CMD_CLEAR:
|
||||
log_misc("AC_IO_ICCA_CMD_CLEAR(%d)", req->addr);
|
||||
ac_io_emu_icca_send_status(icca, req, 0x00);
|
||||
|
||||
break;
|
||||
|
||||
case AC_IO_CMD_KEEPALIVE:
|
||||
ac_io_emu_icca_send_empty(icca, req);
|
||||
|
||||
break;
|
||||
|
||||
case AC_IO_ICCA_CMD_QUEUE_LOOP_START:
|
||||
log_misc("AC_IO_CMD_QUEUE_LOOP_START(%d)", req->addr);
|
||||
// queue started, reset error state
|
||||
icca->fault = false;
|
||||
ac_io_emu_icca_send_status(icca, req, 0x00);
|
||||
icca->polling_started = true;
|
||||
|
||||
break;
|
||||
|
||||
case AC_IO_CMD_UNKN_00FF:
|
||||
log_misc("AC_IO_CMD_UNKN_00FF(%d)", req->addr);
|
||||
ac_io_emu_icca_send_status(icca, req, 0x00);
|
||||
|
||||
break;
|
||||
|
||||
case AC_IO_ICCA_CMD_UNKN_0120:
|
||||
log_misc("AC_IO_ICCA_CMD_UNKN_0120(%d)", req->addr);
|
||||
ac_io_emu_icca_send_status(icca, req, 0x00);
|
||||
|
||||
break;
|
||||
|
||||
case AC_IO_ICCA_CMD_BEGIN_KEYPAD:
|
||||
log_misc("AC_IO_ICCA_CMD_BEGIN_KEYPAD(%d)", req->addr);
|
||||
ac_io_emu_icca_send_status(icca, req, 0x00);
|
||||
icca->keypad_started = true;
|
||||
|
||||
break;
|
||||
|
||||
case AC_IO_ICCA_CMD_ENGAGE:
|
||||
ac_io_emu_icca_send_state(icca, req, 0);
|
||||
|
||||
break;
|
||||
|
||||
case AC_IO_ICCA_CMD_SET_SLOT_STATE:
|
||||
{
|
||||
struct ac_io_icca_misc* misc =
|
||||
(struct ac_io_icca_misc*) &req->cmd.raw;
|
||||
uint8_t cmd;
|
||||
|
||||
switch (misc->subcmd) {
|
||||
case AC_IO_ICCA_SUBCMD_CARD_SLOT_CLOSE:
|
||||
cmd = EAM_IO_CARD_SLOT_CMD_CLOSE;
|
||||
break;
|
||||
|
||||
case AC_IO_ICCA_SUBCMD_CARD_SLOT_OPEN:
|
||||
cmd = EAM_IO_CARD_SLOT_CMD_OPEN;
|
||||
break;
|
||||
|
||||
case AC_IO_ICCA_SUBCMD_CARD_SLOT_EJECT:
|
||||
cmd = EAM_IO_CARD_SLOT_CMD_EJECT;
|
||||
icca->engaged = false;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
cmd = EAM_IO_CARD_SLOT_CMD_READ;
|
||||
break;
|
||||
|
||||
default:
|
||||
cmd = 0xFF;
|
||||
log_warning("Unhandled slot command %X, node %d",
|
||||
misc->subcmd, icca->unit_no);
|
||||
break;
|
||||
}
|
||||
|
||||
if (cmd != 0xFF) {
|
||||
if (!eam_io_card_slot_cmd(icca->unit_no, cmd)) {
|
||||
log_warning("Eamio failed to handle slot cmd %d for node %d",
|
||||
cmd, icca->unit_no);
|
||||
}
|
||||
}
|
||||
|
||||
/* response with current slot state */
|
||||
ac_io_emu_icca_send_status(icca, req, misc->subcmd);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case AC_IO_ICCA_CMD_POLL:
|
||||
delay_us = time_get_elapsed_us(time_get_counter() - icca->time_counter_last_poll);
|
||||
|
||||
/* emulating delay implemented by hardware. do not delay messages that exceed a certain threshold. */
|
||||
if (delay_us > 16000) {
|
||||
delay_us = 0;
|
||||
}
|
||||
|
||||
icca->time_counter_last_poll = time_get_counter();
|
||||
ac_io_emu_icca_send_state(icca, req, delay_us);
|
||||
|
||||
break;
|
||||
|
||||
case AC_IO_ICCA_CMD_POLL_FELICA:
|
||||
icca->detected_new_reader = true;
|
||||
ac_io_emu_icca_send_status(icca, req, 0x01);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
log_warning("Unknown ACIO message %04x on ICCA node, addr=%d",
|
||||
cmd_code, req->addr);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void ac_io_emu_icca_cmd_send_version(
|
||||
struct ac_io_emu_icca *icca,
|
||||
const struct ac_io_message *req)
|
||||
{
|
||||
struct ac_io_message resp;
|
||||
|
||||
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
|
||||
resp.cmd.code = req->cmd.code;
|
||||
resp.cmd.seq_no = req->cmd.seq_no;
|
||||
resp.cmd.nbytes = sizeof(resp.cmd.version);
|
||||
resp.cmd.version.type = ac_io_u32(AC_IO_NODE_TYPE_ICCA);
|
||||
resp.cmd.version.flag = 0x00;
|
||||
resp.cmd.version.major = 0x01;
|
||||
resp.cmd.version.minor = 0x06;
|
||||
resp.cmd.version.revision = 0x00;
|
||||
memcpy(resp.cmd.version.product_code, "ICCA",
|
||||
sizeof(resp.cmd.version.product_code));
|
||||
strncpy(resp.cmd.version.date, __DATE__, sizeof(resp.cmd.version.date));
|
||||
strncpy(resp.cmd.version.time, __TIME__, sizeof(resp.cmd.version.time));
|
||||
|
||||
ac_io_emu_response_push(icca->emu, &resp, 0);
|
||||
}
|
||||
|
||||
static void ac_io_emu_icca_send_empty(
|
||||
struct ac_io_emu_icca *icca,
|
||||
const struct ac_io_message *req)
|
||||
{
|
||||
struct ac_io_message resp;
|
||||
|
||||
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
|
||||
resp.cmd.code = req->cmd.code;
|
||||
resp.cmd.seq_no = req->cmd.seq_no;
|
||||
resp.cmd.nbytes = 0;
|
||||
|
||||
ac_io_emu_response_push(icca->emu, &resp, 0);
|
||||
}
|
||||
|
||||
static void ac_io_emu_icca_send_status(
|
||||
struct ac_io_emu_icca *icca,
|
||||
const struct ac_io_message *req,
|
||||
uint8_t status)
|
||||
{
|
||||
struct ac_io_message resp;
|
||||
|
||||
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
|
||||
resp.cmd.code = req->cmd.code;
|
||||
resp.cmd.seq_no = req->cmd.seq_no;
|
||||
resp.cmd.nbytes = sizeof(resp.cmd.status);
|
||||
resp.cmd.status = status;
|
||||
|
||||
ac_io_emu_response_push(icca->emu, &resp, 0);
|
||||
}
|
||||
|
||||
static void ac_io_emu_icca_send_state(
|
||||
struct ac_io_emu_icca *icca,
|
||||
const struct ac_io_message *req,
|
||||
uint64_t delay_us)
|
||||
{
|
||||
struct ac_io_message resp;
|
||||
struct ac_io_icca_state *body;
|
||||
unsigned long bit;
|
||||
uint8_t event;
|
||||
uint16_t keypad;
|
||||
uint16_t keypad_rise;
|
||||
uint8_t sensor_state;
|
||||
bool card_full_insert;
|
||||
|
||||
if (!eam_io_poll(icca->unit_no)) {
|
||||
log_warning("Polling eamio failed");
|
||||
}
|
||||
|
||||
memset(&resp, 0, sizeof(resp));
|
||||
|
||||
keypad = eam_io_get_keypad_state(icca->unit_no);
|
||||
sensor_state = eam_io_get_sensor_state(icca->unit_no);
|
||||
|
||||
keypad_rise = keypad & (icca->last_keypad ^ keypad);
|
||||
card_full_insert = sensor_state & (1 << EAM_IO_SENSOR_FRONT) &&
|
||||
sensor_state & (1 << EAM_IO_SENSOR_BACK);
|
||||
|
||||
if (sensor_state != icca->last_sensor) {
|
||||
|
||||
if (card_full_insert) {
|
||||
|
||||
if (!eam_io_card_slot_cmd(icca->unit_no,
|
||||
EAM_IO_CARD_SLOT_CMD_READ)) {
|
||||
log_warning("EAM_IO_CARD_SLOT_CMD_READ to unit %d failed",
|
||||
icca->unit_no);
|
||||
}
|
||||
|
||||
icca->card_result = eam_io_read_card(
|
||||
icca->unit_no,
|
||||
icca->uid,
|
||||
sizeof(icca->uid));
|
||||
|
||||
// fault if sensor says to read but we got no card
|
||||
icca->fault = (icca->card_result == EAM_IO_CARD_NONE);
|
||||
} else {
|
||||
icca->fault = false;
|
||||
}
|
||||
}
|
||||
|
||||
icca->last_sensor = sensor_state;
|
||||
icca->last_keypad = keypad;
|
||||
|
||||
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
|
||||
resp.cmd.code = req->cmd.code;
|
||||
resp.cmd.seq_no = req->cmd.seq_no;
|
||||
resp.cmd.nbytes = sizeof(struct ac_io_icca_state);
|
||||
|
||||
body = (struct ac_io_icca_state *) &resp.cmd.raw;
|
||||
|
||||
if (icca->fault) {
|
||||
body->status_code = AC_IO_ICCA_STATUS_FAULT;
|
||||
} else if (card_full_insert) {
|
||||
body->status_code = AC_IO_ICCA_STATUS_GOT_UID;
|
||||
} else {
|
||||
body->status_code = AC_IO_ICCA_STATUS_IDLE;
|
||||
}
|
||||
|
||||
body->sensor_state = 0;
|
||||
|
||||
if (sensor_state & (1 << EAM_IO_SENSOR_FRONT)) {
|
||||
body->sensor_state |= AC_IO_ICCA_FLAG_FRONT_SENSOR;
|
||||
}
|
||||
|
||||
if (sensor_state & (1 << EAM_IO_SENSOR_BACK)) {
|
||||
body->sensor_state |= AC_IO_ICCA_FLAG_REAR_SENSOR;
|
||||
}
|
||||
|
||||
if (icca->engaged) {
|
||||
body->sensor_state |= AC_IO_ICCA_FLAG_SOLENOID;
|
||||
}
|
||||
|
||||
memcpy(body->uid, icca->uid, sizeof(body->uid));
|
||||
body->card_type = 0;
|
||||
if (body->status_code == AC_IO_ICCA_STATUS_GOT_UID){
|
||||
if (icca->detected_new_reader){
|
||||
// sensor_state actually refers to cardtype for wavepass readers
|
||||
// EAM_IO_CARD_ISO15696 = 1 -> 0
|
||||
// EAM_IO_CARD_FELICA = 2 -> 1
|
||||
body->card_type = icca->card_result - 1;
|
||||
body->sensor_state = icca->card_result - 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (keypad_rise) {
|
||||
|
||||
if (icca->key_events[0]) {
|
||||
event = (icca->key_events[0] + 0x10) & 0xF0;
|
||||
} else {
|
||||
event = 0x00;
|
||||
}
|
||||
|
||||
_BitScanForward(&bit, keypad_rise);
|
||||
event |= 0x80 | bit;
|
||||
|
||||
icca->key_events[1] = icca->key_events[0];
|
||||
icca->key_events[0] = event;
|
||||
}
|
||||
|
||||
// this doesn't seem to be an error code. If this is not set to 0x03
|
||||
// on slotted readers (only?), the game throws an unknown status error
|
||||
if (icca->keypad_started) {
|
||||
body->keypad_started = 0x03;
|
||||
} else {
|
||||
body->keypad_started = 0x00;
|
||||
}
|
||||
body->key_events[0] = icca->key_events[0];
|
||||
body->key_events[1] = icca->key_events[1];
|
||||
body->key_state = ac_io_u16(keypad);
|
||||
|
||||
// replace status code if polling hasn't started
|
||||
// this fixes SDVX IC CARD boot errors
|
||||
if (!icca->polling_started) {
|
||||
body->status_code = AC_IO_ICCA_STATUS_FAULT;
|
||||
}
|
||||
|
||||
ac_io_emu_response_push(icca->emu, &resp, delay_us);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
#ifndef AC_IO_EMU_ICCA_H
|
||||
#define AC_IO_EMU_ICCA_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "acioemu/emu.h"
|
||||
|
||||
struct ac_io_emu_icca {
|
||||
struct ac_io_emu *emu;
|
||||
uint8_t unit_no;
|
||||
bool fault;
|
||||
bool engaged;
|
||||
uint8_t last_sensor;
|
||||
uint16_t last_keypad;
|
||||
uint8_t key_events[2];
|
||||
uint8_t uid[8];
|
||||
uint8_t card_result;
|
||||
bool detected_new_reader;
|
||||
bool keypad_started;
|
||||
bool polling_started;
|
||||
uint64_t time_counter_last_poll;
|
||||
};
|
||||
|
||||
void ac_io_emu_icca_init(
|
||||
struct ac_io_emu_icca *icca,
|
||||
struct ac_io_emu *emu,
|
||||
uint8_t unit_no);
|
||||
|
||||
void ac_io_emu_icca_dispatch_request(
|
||||
struct ac_io_emu_icca *icca,
|
||||
const struct ac_io_message *req);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,227 @@
|
||||
#define LOG_MODULE "acioemu-iccb"
|
||||
|
||||
#include "acioemu/iccb.h"
|
||||
|
||||
#include <windows.h> /* for _BitScanForward */
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "acio/iccb.h"
|
||||
|
||||
#include "acioemu/emu.h"
|
||||
|
||||
#include "bemanitools/eamio.h"
|
||||
|
||||
static void ac_io_emu_iccb_cmd_send_version(
|
||||
struct ac_io_emu_iccb *iccb,
|
||||
const struct ac_io_message *req);
|
||||
|
||||
static void ac_io_emu_iccb_send_state(
|
||||
struct ac_io_emu_iccb *iccb,
|
||||
const struct ac_io_message *req);
|
||||
|
||||
static void ac_io_emu_iccb_send_empty(
|
||||
struct ac_io_emu_iccb *iccb,
|
||||
const struct ac_io_message *req);
|
||||
|
||||
static void ac_io_emu_iccb_send_status(
|
||||
struct ac_io_emu_iccb *iccb,
|
||||
const struct ac_io_message *req,
|
||||
uint8_t status);
|
||||
|
||||
void ac_io_emu_iccb_init(
|
||||
struct ac_io_emu_iccb *iccb,
|
||||
struct ac_io_emu *emu,
|
||||
uint8_t unit_no)
|
||||
{
|
||||
memset(iccb, 0, sizeof(*iccb));
|
||||
iccb->emu = emu;
|
||||
iccb->unit_no = unit_no;
|
||||
}
|
||||
|
||||
void ac_io_emu_iccb_dispatch_request(
|
||||
struct ac_io_emu_iccb *iccb,
|
||||
const struct ac_io_message *req)
|
||||
{
|
||||
uint16_t cmd_code;
|
||||
|
||||
cmd_code = ac_io_u16(req->cmd.code);
|
||||
|
||||
switch (cmd_code) {
|
||||
case AC_IO_CMD_GET_VERSION:
|
||||
log_misc("AC_IO_CMD_GET_VERSION(%d)", req->addr);
|
||||
ac_io_emu_iccb_cmd_send_version(iccb, req);
|
||||
|
||||
break;
|
||||
|
||||
case AC_IO_CMD_START_UP:
|
||||
log_misc("AC_IO_CMD_START_UP(%d)", req->addr);
|
||||
ac_io_emu_iccb_send_status(iccb, req, 0x00);
|
||||
|
||||
break;
|
||||
|
||||
case AC_IO_CMD_KEEPALIVE:
|
||||
ac_io_emu_iccb_send_empty(iccb, req);
|
||||
|
||||
break;
|
||||
|
||||
case AC_IO_ICCB_CMD_QUEUE_LOOP_START:
|
||||
log_misc("AC_IO_CMD_QUEUE_LOOP_START(%d)", req->addr);
|
||||
ac_io_emu_iccb_send_status(iccb, req, 0x00);
|
||||
|
||||
break;
|
||||
|
||||
case AC_IO_ICCB_CMD_UNK_0100:
|
||||
case AC_IO_ICCB_CMD_UNK_0116:
|
||||
case AC_IO_ICCB_CMD_UNK_0120:
|
||||
log_misc("AC_IO_ICCB_CMD_UNK_%04X(%d)", cmd_code, req->addr);
|
||||
ac_io_emu_iccb_send_status(iccb, req, 0x00);
|
||||
|
||||
break;
|
||||
|
||||
case AC_IO_ICCB_CMD_SLEEP:
|
||||
ac_io_emu_iccb_send_status(iccb, req, 0x00);
|
||||
|
||||
break;
|
||||
|
||||
case AC_IO_ICCB_CMD_UNK_135:
|
||||
/* log_misc("AC_IO_ICCB_CMD_UNK_135"); */
|
||||
ac_io_emu_iccb_send_state(iccb, req);
|
||||
|
||||
break;
|
||||
|
||||
case AC_IO_ICCB_CMD_POLL:
|
||||
/* log_misc("AC_IO_ICCB_CMD_POLL"); */
|
||||
ac_io_emu_iccb_send_state(iccb, req);
|
||||
|
||||
break;
|
||||
|
||||
case AC_IO_ICCB_CMD_READ_CARD:
|
||||
/* log_misc("AC_IO_ICCB_CMD_READ_CARD"); */
|
||||
ac_io_emu_iccb_send_state(iccb, req);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
log_warning("Unknown ACIO message %04x on ICCB node, addr=%d",
|
||||
cmd_code, req->addr);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void ac_io_emu_iccb_cmd_send_version(
|
||||
struct ac_io_emu_iccb *iccb,
|
||||
const struct ac_io_message *req)
|
||||
{
|
||||
struct ac_io_message resp;
|
||||
|
||||
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
|
||||
resp.cmd.code = req->cmd.code;
|
||||
resp.cmd.seq_no = req->cmd.seq_no;
|
||||
resp.cmd.nbytes = sizeof(resp.cmd.version);
|
||||
resp.cmd.version.type = ac_io_u32(AC_IO_NODE_TYPE_ICCB);
|
||||
resp.cmd.version.flag = 0x00;
|
||||
resp.cmd.version.major = 0x01;
|
||||
resp.cmd.version.minor = 0x05;
|
||||
resp.cmd.version.revision = 0x01;
|
||||
memcpy(resp.cmd.version.product_code, "ICCB",
|
||||
sizeof(resp.cmd.version.product_code));
|
||||
strncpy(resp.cmd.version.date, __DATE__, sizeof(resp.cmd.version.date));
|
||||
strncpy(resp.cmd.version.time, __TIME__, sizeof(resp.cmd.version.time));
|
||||
|
||||
ac_io_emu_response_push(iccb->emu, &resp, 0);
|
||||
}
|
||||
|
||||
static void ac_io_emu_iccb_send_empty(
|
||||
struct ac_io_emu_iccb *iccb,
|
||||
const struct ac_io_message *req)
|
||||
{
|
||||
struct ac_io_message resp;
|
||||
|
||||
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
|
||||
resp.cmd.code = req->cmd.code;
|
||||
resp.cmd.seq_no = req->cmd.seq_no;
|
||||
resp.cmd.nbytes = 0;
|
||||
|
||||
ac_io_emu_response_push(iccb->emu, &resp, 0);
|
||||
}
|
||||
|
||||
static void ac_io_emu_iccb_send_status(
|
||||
struct ac_io_emu_iccb *iccb,
|
||||
const struct ac_io_message *req,
|
||||
uint8_t status)
|
||||
{
|
||||
struct ac_io_message resp;
|
||||
|
||||
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
|
||||
resp.cmd.code = req->cmd.code;
|
||||
resp.cmd.seq_no = req->cmd.seq_no;
|
||||
resp.cmd.nbytes = sizeof(resp.cmd.status);
|
||||
resp.cmd.status = status;
|
||||
|
||||
ac_io_emu_response_push(iccb->emu, &resp, 0);
|
||||
}
|
||||
|
||||
static void ac_io_emu_iccb_send_state(
|
||||
struct ac_io_emu_iccb *iccb,
|
||||
const struct ac_io_message *req)
|
||||
{
|
||||
struct ac_io_message resp;
|
||||
struct ac_io_iccb_state *body;
|
||||
bool sensor;
|
||||
|
||||
/* state update */
|
||||
|
||||
sensor = eam_io_get_sensor_state(iccb->unit_no);
|
||||
|
||||
if (sensor != iccb->last_sensor) {
|
||||
if (sensor) {
|
||||
iccb->card_result = eam_io_read_card(
|
||||
iccb->unit_no,
|
||||
iccb->uid,
|
||||
sizeof(iccb->uid));
|
||||
|
||||
// fault if sensor says to read but we got no card
|
||||
iccb->fault = (iccb->card_result == EAM_IO_CARD_NONE);
|
||||
} else {
|
||||
iccb->fault = false;
|
||||
}
|
||||
}
|
||||
|
||||
iccb->last_sensor = sensor;
|
||||
|
||||
if (iccb->fault) {
|
||||
memset(iccb->uid, 0, sizeof(iccb->uid));
|
||||
}
|
||||
|
||||
/* create response */
|
||||
|
||||
resp.addr = req->addr | AC_IO_RESPONSE_FLAG;
|
||||
resp.cmd.code = req->cmd.code;
|
||||
resp.cmd.seq_no = req->cmd.seq_no;
|
||||
resp.cmd.nbytes = sizeof(struct ac_io_iccb_state);
|
||||
|
||||
body = (struct ac_io_iccb_state *) &resp.cmd.raw;
|
||||
|
||||
if (sensor) {
|
||||
body->sensor_state = AC_IO_ICCB_SENSOR_CARD;
|
||||
} else {
|
||||
body->sensor_state = AC_IO_ICCB_SENSOR_NO_CARD;
|
||||
}
|
||||
|
||||
if (!iccb->fault) {
|
||||
memcpy(body->uid, iccb->uid, sizeof(body->uid));
|
||||
}
|
||||
|
||||
body->card_type = 0x30 | (iccb->card_result - 1);
|
||||
body->unk2 = 0;
|
||||
// this doesn't seem to be an error code. If this is not set to 0x03
|
||||
// on slotted readers (only?), the game throws an unknown status error
|
||||
body->unk3 = 0x03;
|
||||
memset(body->unk4, 0, sizeof(body->unk4));
|
||||
|
||||
ac_io_emu_response_push(iccb->emu, &resp, 0);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
#ifndef AC_IO_EMU_ICCB_H
|
||||
#define AC_IO_EMU_ICCB_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "acioemu/emu.h"
|
||||
|
||||
struct ac_io_emu_iccb {
|
||||
struct ac_io_emu *emu;
|
||||
uint8_t unit_no;
|
||||
bool fault;
|
||||
bool last_sensor;
|
||||
uint8_t uid[8];
|
||||
uint8_t card_result;
|
||||
};
|
||||
|
||||
void ac_io_emu_iccb_init(
|
||||
struct ac_io_emu_iccb *iccb,
|
||||
struct ac_io_emu *emu,
|
||||
uint8_t unit_no);
|
||||
|
||||
void ac_io_emu_iccb_dispatch_request(
|
||||
struct ac_io_emu_iccb *iccb,
|
||||
const struct ac_io_message *req);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,366 @@
|
||||
#define LOG_MODULE "acioemu-emu"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "acioemu/pipe.h"
|
||||
|
||||
#include "util/defs.h"
|
||||
#include "util/list.h"
|
||||
#include "util/mem.h"
|
||||
#include "util/time.h"
|
||||
|
||||
static struct ac_io_in_queued *ac_io_in_queued_alloc(struct ac_io_in *in,
|
||||
uint64_t delay_us);
|
||||
static void ac_io_in_queued_populate(
|
||||
struct ac_io_in_queued *iq,
|
||||
const struct ac_io_message *msg);
|
||||
static void ac_io_in_queued_putc(struct ac_io_in_queued *iq, uint8_t b);
|
||||
|
||||
static bool ac_io_out_supply_byte(struct ac_io_out *out, uint8_t b);
|
||||
static bool ac_io_out_supply_frame_byte(struct ac_io_out *out, uint8_t b);
|
||||
static bool ac_io_out_detect_broadcast_eof(struct ac_io_out *out);
|
||||
static bool ac_io_out_detect_command_eof(struct ac_io_out *out);
|
||||
static bool ac_io_out_check_sum(struct ac_io_out *out);
|
||||
static bool ac_io_out_accept_message(struct ac_io_out *out);
|
||||
static bool ac_io_out_reject_message(struct ac_io_out *out);
|
||||
|
||||
static bool ac_io_enable_legacy_mode = false;
|
||||
|
||||
static struct ac_io_in_queued *ac_io_in_queued_alloc(struct ac_io_in *in,
|
||||
uint64_t delay_us)
|
||||
{
|
||||
struct ac_io_in_queued *iq;
|
||||
|
||||
/* TODO ordered insert by scheduled_time */
|
||||
|
||||
iq = xmalloc(sizeof(*iq));
|
||||
iq->iobuf.bytes = iq->bytes;
|
||||
iq->iobuf.nbytes = 1;
|
||||
iq->iobuf.pos = 0;
|
||||
iq->scheduled_time = time_get_counter();
|
||||
iq->delay_us = delay_us;
|
||||
iq->bytes[0] = AC_IO_SOF;
|
||||
|
||||
list_append(&in->queue, &iq->node);
|
||||
|
||||
return iq;
|
||||
}
|
||||
|
||||
static void ac_io_in_queued_populate(
|
||||
struct ac_io_in_queued *iq,
|
||||
const struct ac_io_message *msg)
|
||||
{
|
||||
uint8_t checksum;
|
||||
const uint8_t *src;
|
||||
size_t nbytes;
|
||||
size_t i;
|
||||
|
||||
if (msg->addr == AC_IO_BROADCAST) {
|
||||
nbytes = offsetof(struct ac_io_message, bcast.raw) + msg->bcast.nbytes;
|
||||
} else {
|
||||
nbytes = offsetof(struct ac_io_message, cmd.raw) + msg->cmd.nbytes;
|
||||
}
|
||||
|
||||
src = (const uint8_t *) msg;
|
||||
checksum = 0;
|
||||
|
||||
for (i = 0 ; i < nbytes ; i++) {
|
||||
ac_io_in_queued_putc(iq, src[i]);
|
||||
checksum += src[i];
|
||||
}
|
||||
|
||||
ac_io_in_queued_putc(iq, checksum);
|
||||
}
|
||||
|
||||
static void ac_io_in_queued_putc(struct ac_io_in_queued *iq, uint8_t b)
|
||||
{
|
||||
if (b == AC_IO_SOF || b == AC_IO_ESCAPE) {
|
||||
iq->bytes[iq->iobuf.nbytes++] = AC_IO_ESCAPE;
|
||||
iq->bytes[iq->iobuf.nbytes++] = ~b;
|
||||
} else {
|
||||
iq->bytes[iq->iobuf.nbytes++] = b;
|
||||
}
|
||||
}
|
||||
|
||||
void ac_io_legacy_mode(void)
|
||||
{
|
||||
log_info("Running acioemu legacy mode");
|
||||
ac_io_enable_legacy_mode = true;
|
||||
}
|
||||
|
||||
void ac_io_in_init(struct ac_io_in *in)
|
||||
{
|
||||
list_init(&in->queue);
|
||||
}
|
||||
|
||||
void ac_io_in_supply(struct ac_io_in *in, const struct ac_io_message *msg,
|
||||
uint64_t delay_us)
|
||||
{
|
||||
struct ac_io_in_queued *dest;
|
||||
|
||||
dest = ac_io_in_queued_alloc(in, delay_us);
|
||||
dest->thunk = NULL;
|
||||
|
||||
if (msg == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
ac_io_in_queued_populate(dest, msg);
|
||||
}
|
||||
|
||||
void ac_io_in_supply_thunk(
|
||||
struct ac_io_in *in,
|
||||
ac_io_in_thunk_t thunk,
|
||||
void *ctx,
|
||||
uint64_t delay_us)
|
||||
{
|
||||
struct ac_io_in_queued *dest;
|
||||
|
||||
dest = ac_io_in_queued_alloc(in, delay_us);
|
||||
|
||||
dest->thunk = thunk;
|
||||
dest->thunk_ctx = ctx;
|
||||
}
|
||||
|
||||
void ac_io_in_drain(struct ac_io_in *in, struct iobuf *dest)
|
||||
{
|
||||
struct ac_io_in_queued *iq;
|
||||
struct list_node *node;
|
||||
struct ac_io_message msg;
|
||||
size_t nmoved;
|
||||
uint64_t now;
|
||||
uint64_t elapsed_us;
|
||||
|
||||
now = time_get_counter();
|
||||
|
||||
do {
|
||||
node = list_peek_head(&in->queue);
|
||||
|
||||
if (node == NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
iq = containerof(node, struct ac_io_in_queued, node);
|
||||
elapsed_us = time_get_elapsed_us(
|
||||
now > iq->scheduled_time ? now - iq->scheduled_time : 0);
|
||||
|
||||
if (elapsed_us < iq->delay_us) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (iq->thunk != NULL) {
|
||||
iq->thunk(iq->thunk_ctx, &msg);
|
||||
iq->thunk = NULL;
|
||||
|
||||
ac_io_in_queued_populate(iq, &msg);
|
||||
}
|
||||
|
||||
nmoved = iobuf_move(dest, &iq->iobuf);
|
||||
|
||||
if (iq->iobuf.pos == iq->iobuf.nbytes) {
|
||||
list_pop_head(&in->queue);
|
||||
free(iq);
|
||||
}
|
||||
|
||||
/* Legacy mode for old libacio versions like DistorteD:
|
||||
spit out single responses instead of combining them into a single
|
||||
io buffer. Some old libacio versions expect separate messages and
|
||||
will error (code 0x00000002) on multiple messages in a single buffer
|
||||
because they just read the first message and drop the remaining ones.
|
||||
If using legacy mode on newer libacio versions (e.g. Copula),
|
||||
the game will error with the same error code.
|
||||
*/
|
||||
if (ac_io_enable_legacy_mode) {
|
||||
break;
|
||||
}
|
||||
} while (nmoved > 0);
|
||||
}
|
||||
|
||||
bool ac_io_in_is_msg_pending(const struct ac_io_in *in)
|
||||
{
|
||||
return list_peek_head_const(&in->queue) != NULL;
|
||||
}
|
||||
|
||||
void ac_io_out_init(struct ac_io_out *out)
|
||||
{
|
||||
out->pos = 0;
|
||||
out->in_frame = false;
|
||||
out->escape = false;
|
||||
out->have_message = false;
|
||||
}
|
||||
|
||||
void ac_io_out_supply(struct ac_io_out *out, struct const_iobuf *src)
|
||||
{
|
||||
log_assert(!ac_io_out_have_message(out));
|
||||
|
||||
while (src->pos < src->nbytes) {
|
||||
if (!ac_io_out_supply_byte(out, src->bytes[src->pos++])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool ac_io_out_supply_byte(struct ac_io_out *out, uint8_t b)
|
||||
{
|
||||
if (out->in_frame) {
|
||||
return ac_io_out_supply_frame_byte(out, b);
|
||||
} else {
|
||||
|
||||
if (b == AC_IO_SOF) {
|
||||
out->in_frame = true;
|
||||
} else if (b == AC_IO_ESCAPE) {
|
||||
log_warning("Framing error: Escape byte outside frame");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static bool ac_io_out_supply_frame_byte(struct ac_io_out *out, uint8_t b)
|
||||
{
|
||||
if (out->escape) {
|
||||
out->escape = false;
|
||||
|
||||
if (b == AC_IO_SOF || b == AC_IO_ESCAPE) {
|
||||
log_warning("Framing error: Control byte after escape byte");
|
||||
}
|
||||
|
||||
b = ~b;
|
||||
} else if (b == AC_IO_ESCAPE) {
|
||||
out->escape = true;
|
||||
|
||||
return true;
|
||||
} else if (b == AC_IO_SOF) {
|
||||
|
||||
if (out->pos == 0) {
|
||||
/* Got autobaud/empty message */
|
||||
out->have_message = true;
|
||||
|
||||
return false;
|
||||
} else {
|
||||
log_warning("Truncated message");
|
||||
out->pos = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Payload byte */
|
||||
|
||||
out->bytes[out->pos++] = b;
|
||||
|
||||
/* Handle contextually-implied end-of-packet events */
|
||||
|
||||
if (out->pos > offsetof(struct ac_io_message, addr)) {
|
||||
|
||||
if (out->msg.addr == AC_IO_BROADCAST) {
|
||||
return ac_io_out_detect_broadcast_eof(out);
|
||||
} else {
|
||||
return ac_io_out_detect_command_eof(out);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ac_io_out_detect_broadcast_eof(struct ac_io_out *out)
|
||||
{
|
||||
size_t end;
|
||||
|
||||
if (out->pos > offsetof(struct ac_io_message, bcast.nbytes)) {
|
||||
end = offsetof(struct ac_io_message,bcast.raw)
|
||||
+ out->msg.bcast.nbytes
|
||||
+ 1;
|
||||
|
||||
if (out->pos == end) {
|
||||
return ac_io_out_check_sum(out);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ac_io_out_detect_command_eof(struct ac_io_out *out)
|
||||
{
|
||||
size_t end;
|
||||
|
||||
if (out->pos > offsetof(struct ac_io_message, cmd.nbytes)) {
|
||||
end = offsetof(struct ac_io_message, cmd.raw)
|
||||
+ out->msg.cmd.nbytes
|
||||
+ 1;
|
||||
|
||||
if (out->pos == end) {
|
||||
return ac_io_out_check_sum(out);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ac_io_out_check_sum(struct ac_io_out *out)
|
||||
{
|
||||
uint8_t checksum;
|
||||
size_t i;
|
||||
|
||||
checksum = 0;
|
||||
|
||||
for (i = 0 ; i < out->pos - 1 ; i++) {
|
||||
checksum += out->bytes[i];
|
||||
}
|
||||
|
||||
if (checksum == out->bytes[out->pos - 1]) {
|
||||
return ac_io_out_accept_message(out);
|
||||
} else {
|
||||
log_warning("Checksum bad: expected %02x got %02x",
|
||||
checksum, out->bytes[out->pos - 1]);
|
||||
|
||||
return ac_io_out_reject_message(out);
|
||||
}
|
||||
}
|
||||
|
||||
static bool ac_io_out_accept_message(struct ac_io_out *out)
|
||||
{
|
||||
out->have_message = true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool ac_io_out_reject_message(struct ac_io_out *out)
|
||||
{
|
||||
ac_io_out_consume_message(out);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ac_io_out_have_message(const struct ac_io_out *out)
|
||||
{
|
||||
return out->have_message;
|
||||
}
|
||||
|
||||
const struct ac_io_message *ac_io_out_get_message(const struct ac_io_out *out)
|
||||
{
|
||||
if (out->pos == 0) {
|
||||
/* Autobaud byte/empty frame */
|
||||
return NULL;
|
||||
} else {
|
||||
return &out->msg;
|
||||
}
|
||||
}
|
||||
|
||||
void ac_io_out_consume_message(struct ac_io_out *out)
|
||||
{
|
||||
if (out->pos != 0) {
|
||||
out->in_frame = false;
|
||||
}
|
||||
|
||||
out->pos = 0;
|
||||
out->have_message = false;
|
||||
out->escape = false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
#ifndef ACIOEMU_PIPE_H
|
||||
#define ACIOEMU_PIPE_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "acio/acio.h"
|
||||
|
||||
#include "util/iobuf.h"
|
||||
#include "util/list.h"
|
||||
#include "util/log.h"
|
||||
|
||||
/* This uses the USB convention where OUT and IN are from the host's (game's)
|
||||
perspective. So an OUT transaction comes in to us and vice versa.
|
||||
|
||||
(I made those terms up, not Konami). */
|
||||
|
||||
typedef void (*ac_io_in_thunk_t)(void *ctx, struct ac_io_message *msg);
|
||||
|
||||
struct ac_io_in_queued {
|
||||
struct list_node node;
|
||||
struct const_iobuf iobuf;
|
||||
uint64_t scheduled_time;
|
||||
uint64_t delay_us;
|
||||
ac_io_in_thunk_t thunk;
|
||||
void *thunk_ctx;
|
||||
uint8_t bytes[sizeof(struct ac_io_message)];
|
||||
};
|
||||
|
||||
struct ac_io_in {
|
||||
struct list queue;
|
||||
};
|
||||
|
||||
struct ac_io_out {
|
||||
union {
|
||||
uint8_t bytes[sizeof(struct ac_io_message)];
|
||||
struct ac_io_message msg;
|
||||
};
|
||||
|
||||
size_t pos;
|
||||
bool in_frame;
|
||||
bool escape;
|
||||
bool have_message;
|
||||
};
|
||||
|
||||
void ac_io_legacy_mode(void);
|
||||
|
||||
void ac_io_in_init(struct ac_io_in *in);
|
||||
void ac_io_in_supply(struct ac_io_in *in, const struct ac_io_message *msg,
|
||||
uint64_t delay);
|
||||
void ac_io_in_supply_thunk(
|
||||
struct ac_io_in *in,
|
||||
ac_io_in_thunk_t thunk,
|
||||
void *ctx,
|
||||
uint64_t delay);
|
||||
void ac_io_in_drain(struct ac_io_in *in, struct iobuf *dest);
|
||||
bool ac_io_in_is_msg_pending(const struct ac_io_in *in);
|
||||
|
||||
void ac_io_out_init(struct ac_io_out *out);
|
||||
void ac_io_out_supply(struct ac_io_out *out, struct const_iobuf *src);
|
||||
bool ac_io_out_have_message(const struct ac_io_out *out);
|
||||
const struct ac_io_message *ac_io_out_get_message(const struct ac_io_out *out);
|
||||
void ac_io_out_consume_message(struct ac_io_out *out);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user