#define LOG_MODULE "p4ioemu-device" #include "p4ioemu/device.h" #include #include #include #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; } }