439 lines
11 KiB
C
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;
|
|
}
|
|
}
|