Files
djhackersdev_bemanitools/src/main/p4ioemu/device.c
T

439 lines
11 KiB
C

#define LOG_MODULE "p4ioemu-device"
#include "p4ioemu/device.h"
#include <setupapi.h>
#include <usb100.h>
#include <windows.h>
#include "hook/iohook.h"
#include "p4io/cmd.h"
#include "p4io/ioctl.h"
#include "util/hex.h"
#include "util/log.h"
#include "util/str.h"
// #define P4IOEMU_DEBUG_DUMP
static const struct p4ioemu_device_msg_hook *p4ioemu_device_msg_hook;
static HANDLE p4ioemu_p4io_fd;
static uint8_t p4ioemu_p4io_cmd_read_buffer[4096];
static uint32_t p4ioemu_p4io_cmd_buffer_resp_len;
static uint8_t p4ioemu_p4io_last_cmd;
static uint8_t p4ioemu_p4io_last_seq_num;
static uint32_t p4ioemu_p4io_command_handle(
uint8_t cmd,
const void *payload,
uint32_t payload_len,
void *resp,
uint32_t resp_max_len)
{
switch (cmd) {
case P4IO_CMD_INIT: {
log_misc("P4IOEMU_P4IO_CMD_INIT");
/* no data to send to host */
memset(resp, 0, resp_max_len);
/* driver expects this buffer size, fails otherwise */
return 0;
}
case P4IO_CMD_GET_DEVICE_INFO: {
log_misc("P4IOEMU_P4IO_CMD_GET_DEVICE_INFO");
struct p4io_resp_device_info *info =
(struct p4io_resp_device_info *) resp;
info->type = 0x37133713;
info->version_major = 5;
info->version_minor = 7;
info->version_revision = 3;
memcpy(info->product_code, "P4IO", 4);
memcpy(info->build_date, "build_date", 11);
memcpy(info->build_time, "build_time", 11);
return sizeof(struct p4io_resp_device_info);
}
case P4IO_CMD_DALLAS_READ_ID: {
const struct p4io_req_read_roundplug *req =
(const struct p4io_req_read_roundplug *) payload;
log_misc("P4IOEMU_P4IO_CMD_DALLAS_READ_ID: %d", req->type);
if (p4ioemu_device_msg_hook->roundplug_read_id) {
p4ioemu_device_msg_hook->roundplug_read_id(req->type, resp, 8);
} else {
memset(resp, 0, 8);
}
return 8;
}
case P4IO_CMD_DALLAS_READ_MEM: {
const struct p4io_req_read_roundplug *req =
(const struct p4io_req_read_roundplug *) payload;
log_misc("P4IOEMU_P4IO_CMD_DALLAS_READ_MEM: %d", req->type);
if (p4ioemu_device_msg_hook->roundplug_read_mem) {
p4ioemu_device_msg_hook->roundplug_read_mem(
req->type, resp, 32);
} else {
memset(resp, 0, 32);
}
return 32;
}
case P4IO_CMD_SCI_UPDATE: {
// log_misc("P4IOEMU_P4IO_CMD_SCI_UPDATE");
// TODO we need a game which uses it
memset(resp, 0, 61);
return 0;
}
case P4IO_CMD_RESET_WTD: {
// log_misc("P4IOEMU_P4IO_CMD_RESET_WTD");
return 0;
}
default:
/* forward to game specific handler */
return 0xFFFFFFFF;
}
}
static void p4ioemu_p4io_dump_buffer(const void *buffer, uint32_t len)
{
char buffer_str[4096];
hex_encode_uc(buffer, len, buffer_str, sizeof(buffer_str));
log_warning("Package dump: %s", buffer_str);
}
static HRESULT p4ioemu_p4io_bulk_read(void *resp, uint32_t nbytes)
{
struct p4io_cmd_package *package;
uint32_t max_payload_len;
uint32_t payload_len;
if (nbytes < sizeof(struct p4io_cmd_header)) {
log_warning("Buffer for bulk read endpoint too short: %d", nbytes);
return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
}
package = (struct p4io_cmd_package *) resp;
max_payload_len = nbytes - sizeof(struct p4io_cmd_header);
if (max_payload_len < p4ioemu_p4io_cmd_buffer_resp_len) {
log_warning(
"Too much response data (%d) for buffer (%d), truncated",
p4ioemu_p4io_cmd_buffer_resp_len,
max_payload_len);
payload_len = max_payload_len;
} else {
payload_len = p4ioemu_p4io_cmd_buffer_resp_len;
}
package->header.sof = P4IO_SOF;
package->header.cmd = p4ioemu_p4io_last_cmd;
package->header.seq_num = p4ioemu_p4io_last_seq_num;
package->header.payload_len = payload_len;
memcpy(package->payload, p4ioemu_p4io_cmd_read_buffer, payload_len);
return S_OK;
}
static HRESULT p4ioemu_p4io_bulk_write(const void *req, uint32_t nbytes)
{
const struct p4io_cmd_package *package;
if (nbytes < sizeof(struct p4io_cmd_header)) {
log_warning("Command on bulk write endpoint too short: %d", nbytes);
p4ioemu_p4io_dump_buffer(req, nbytes);
return E_INVALIDARG;
}
package = (struct p4io_cmd_package *) req;
if (package->header.sof != P4IO_SOF) {
log_warning("Command on bulk endpoint too short: %d", nbytes);
p4ioemu_p4io_dump_buffer(req, nbytes);
return E_INVALIDARG;
}
p4ioemu_p4io_last_cmd = package->header.cmd;
p4ioemu_p4io_last_seq_num = package->header.seq_num;
/* handle commands that are common p4io ones first */
p4ioemu_p4io_cmd_buffer_resp_len = p4ioemu_p4io_command_handle(
package->header.cmd,
package->payload,
package->header.payload_len,
p4ioemu_p4io_cmd_read_buffer,
sizeof(p4ioemu_p4io_cmd_read_buffer));
/* forward to game specific handlers */
if (p4ioemu_p4io_cmd_buffer_resp_len == 0xFFFFFFFF) {
if (p4ioemu_device_msg_hook->command_handle) {
p4ioemu_p4io_cmd_buffer_resp_len =
p4ioemu_device_msg_hook->command_handle(
package->header.cmd,
package->payload,
package->header.payload_len,
p4ioemu_p4io_cmd_read_buffer,
sizeof(p4ioemu_p4io_cmd_read_buffer));
}
}
if (p4ioemu_p4io_cmd_buffer_resp_len == 0xFFFFFFFF) {
log_warning("Unhandled cmd 0x%X", package->header.cmd);
return E_NOTIMPL;
}
return S_OK;
}
/* For debugging */
#ifdef P4IOEMU_DEBUG_DUMP
static void p4ioemu_device_log_ioctl_msg(
const char *prefix,
uint32_t ctl_code,
const void *ctl,
uint32_t ctl_size,
void *header,
uint32_t header_bytes,
void *data,
uint32_t data_bytes)
{
char header_str[4096];
char data_str[4096];
const char *ctl_code_str;
switch (ctl_code) {
case IOCTL_P4IO_GET_DEVICE_NAME:
ctl_code_str = "IOCTL_P4IO_GET_DEVICE_NAME";
break;
case IOCTL_P4IO_READ_JAMMA_2:
ctl_code_str = "IOCTL_P4IO_READ_JAMMA_2";
break;
default:
ctl_code_str = "UNKNOWN";
break;
}
hex_encode_uc(header, header_bytes, header_str, sizeof(header_str));
hex_encode_uc(data, data_bytes, data_str, sizeof(data_str));
log_warning(
"[P4IO IOCTL DUMP %s][%s] ctl_code 0x%X, ctl_size %d, header(%d) "
"%s |||| data(%d) %s",
prefix,
ctl_code_str,
ctl_code,
ctl_size,
header_bytes,
header_str,
data_bytes,
data_str);
}
static void
p4ioemu_device_log(const char *prefix, const void *data, uint32_t data_bytes)
{
char data_str[4096];
hex_encode_uc(data, data_bytes, data_str, sizeof(data_str));
log_warning("[P4IO DUMP %s] data(%d) %s", prefix, data_bytes, data_str);
}
#endif
static HRESULT p4ioemu_device_open(struct irp *irp)
{
log_assert(irp != NULL);
/* FIXME I don't know what kind of path the game expects to be returned
on the setupapi calls, so i assumed it wants \\p4io. However, the
game also adds the node \\p4io to that -> result: \\p4io\\p4io */
if (!wstr_eq(irp->open_filename, L"\\p4io\\p4io")) {
return iohook_invoke_next(irp);
}
irp->fd = p4ioemu_p4io_fd;
log_info("P4IO opened");
return S_OK;
}
static HRESULT p4ioemu_device_read(struct irp *irp)
{
HRESULT hr;
memset(irp->read.bytes, 0, irp->read.nbytes);
hr = p4ioemu_p4io_bulk_read(irp->read.bytes, irp->read.nbytes);
if (FAILED(hr)) {
return hr;
}
/* game expects a read size of 0x40, always */
irp->read.pos = 0x40;
#ifdef P4IOEMU_DEBUG_DUMP
p4ioemu_device_log("POST READ", dest->bytes, dest->nbytes);
#endif
return hr;
}
static HRESULT p4ioemu_device_write(struct irp *irp)
{
HRESULT hr;
/* For debugging */
#ifdef P4IOEMU_DEBUG_DUMP
p4ioemu_device_log("WRITE", src->bytes, src->nbytes);
#endif
hr = p4ioemu_p4io_bulk_write(irp->write.bytes, irp->write.nbytes);
if (FAILED(hr)) {
return hr;
}
irp->write.pos = irp->write.nbytes;
return hr;
}
static HRESULT p4ioemu_device_ioctl(struct irp *irp)
{
log_assert(irp != NULL);
/* For debugging */
#ifdef P4IOEMU_DEBUG_DUMP
p4ioemu_device_log_ioctl_msg(
"BEFORE",
code,
in_bytes,
in_nbytes,
in_bytes,
in_nbytes,
out_bytes,
out_nbytes);
#endif
/* Cases are listed in order of first receipt */
switch (irp->ioctl) {
case P4IO_IOCTL_GET_DEVICE_NAME: {
const char dev_name[] = "kactools p4ioemu";
if (irp->read.nbytes < strlen(dev_name)) {
log_fatal("Device name string does not fit into buffer");
}
memset(irp->read.bytes, 0, irp->read.nbytes);
memcpy(irp->read.bytes, dev_name, strlen(dev_name));
irp->read.pos = strlen(dev_name);
return S_OK;
}
case P4IO_IOCTL_READ_JAMMA_2: {
p4ioemu_device_msg_hook->jamma2_read(
irp->read.bytes, irp->read.nbytes);
irp->read.pos = irp->read.nbytes;
return S_OK;
}
default:
log_warning("Unknown ioctl %08x", irp->ioctl);
return E_INVALIDARG;
}
/* For debugging */
#ifdef P4IOEMU_DEBUG_DUMP
p4ioemu_device_log_ioctl_msg(
"AFTER",
code,
in_bytes,
in_nbytes,
in_bytes,
in_nbytes,
out_bytes,
out_nbytes);
#endif
}
static HRESULT p4ioemu_device_close(struct irp *irp)
{
log_info("P4IO closed");
return S_OK;
}
void p4ioemu_init(const struct p4ioemu_device_msg_hook *msg_hook)
{
log_assert(p4ioemu_p4io_fd == NULL);
HRESULT hr;
hr = iohook_open_nul_fd(&p4ioemu_p4io_fd);
if (hr != S_OK) {
log_fatal("Opening nul fd failed: %08lx", hr);
}
p4ioemu_device_msg_hook = msg_hook;
}
void p4ioemu_fini(void)
{
if (p4ioemu_p4io_fd != NULL) {
CloseHandle(p4ioemu_p4io_fd);
}
p4ioemu_p4io_fd = NULL;
}
HRESULT
p4ioemu_dispatch_irp(struct irp *irp)
{
log_assert(irp != NULL);
if (irp->op != IRP_OP_OPEN && irp->fd != p4ioemu_p4io_fd) {
return iohook_invoke_next(irp);
}
switch (irp->op) {
case IRP_OP_OPEN:
return p4ioemu_device_open(irp);
case IRP_OP_CLOSE:
return p4ioemu_device_close(irp);
case IRP_OP_READ:
return p4ioemu_device_read(irp);
case IRP_OP_WRITE:
return p4ioemu_device_write(irp);
case IRP_OP_IOCTL:
return p4ioemu_device_ioctl(irp);
default:
return E_NOTIMPL;
}
}