Bemanitools v5.26 release

This commit is contained in:
icex2
2019-09-27 22:36:50 +02:00
commit cbd7720349
718 changed files with 62731 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
libs += p3ioemu
src_p3ioemu := \
devmgr.c \
emu.c \
uart.c \
+266
View File
@@ -0,0 +1,266 @@
#include <windows.h>
#include <setupapi.h>
#include <stdbool.h>
#include <string.h>
#include "hook/table.h"
#include "p3io/guid.h"
#include "p3ioemu/devmgr.h"
#include "util/defs.h"
#include "util/log.h"
#include "util/str.h"
/* Link pointers */
static HDEVINFO (WINAPI * next_SetupDiGetClassDevsW)(
const GUID *class_guid,
const wchar_t *enumerator,
HWND hwnd,
DWORD flags);
static HDEVINFO (WINAPI * next_SetupDiGetClassDevsA)(
const GUID *class_guid,
const char *enumerator,
HWND hwnd,
DWORD flags);
static BOOL (WINAPI *next_SetupDiEnumDeviceInterfaces)(
HDEVINFO dev_info,
SP_DEVINFO_DATA *info_data,
const GUID *iface_guid,
DWORD index,
SP_DEVICE_INTERFACE_DATA *ifd);
static BOOL (WINAPI *next_SetupDiGetDeviceInterfaceDetailW)(
HDEVINFO dev_info,
SP_DEVICE_INTERFACE_DATA *ifd,
SP_DEVICE_INTERFACE_DETAIL_DATA_W *detail,
DWORD size,
DWORD *required_size,
SP_DEVINFO_DATA *info_data);
static BOOL (WINAPI *next_SetupDiDestroyDeviceInfoList)(HDEVINFO dev_info);
/* API hooks */
static HDEVINFO WINAPI my_SetupDiGetClassDevsW(
const GUID *class_guid,
const wchar_t *enumerator,
HWND hwnd,
DWORD flags);
static HDEVINFO WINAPI my_SetupDiGetClassDevsA(
const GUID *class_guid,
const char *enumerator,
HWND hwnd,
DWORD flags);
static BOOL WINAPI my_SetupDiEnumDeviceInterfaces(
HDEVINFO dev_info,
SP_DEVINFO_DATA *info_data,
const GUID *iface_guid,
DWORD index,
SP_DEVICE_INTERFACE_DATA *ifd);
static BOOL WINAPI my_SetupDiGetDeviceInterfaceDetailW(
HDEVINFO dev_info,
SP_DEVICE_INTERFACE_DATA *ifd,
SP_DEVICE_INTERFACE_DETAIL_DATA_W *detail,
DWORD size,
DWORD *required_size,
SP_DEVINFO_DATA *info_data);
static BOOL WINAPI my_SetupDiDestroyDeviceInfoList(HDEVINFO dev_info);
static const struct hook_symbol p3io_setupapi_syms[] = {
{
.name = "SetupDiGetClassDevsW",
.patch = my_SetupDiGetClassDevsW,
.link = (void **) &next_SetupDiGetClassDevsW,
}, {
.name = "SetupDiGetClassDevsA",
.patch = my_SetupDiGetClassDevsA,
.link = (void **) &next_SetupDiGetClassDevsA,
}, {
#if 0
.name = "SetupDiEnumDeviceInfo",
.patch = my_SetupDiEnumDeviceInfo,
.link = (void **) &next_SetupDiEnumDeviceInfo,
}, {
#endif
.name = "SetupDiEnumDeviceInterfaces",
.patch = my_SetupDiEnumDeviceInterfaces,
.link = (void **) &next_SetupDiEnumDeviceInterfaces,
}, {
.name = "SetupDiGetDeviceInterfaceDetailW",
.patch = my_SetupDiGetDeviceInterfaceDetailW,
.link = (void **) &next_SetupDiGetDeviceInterfaceDetailW,
}, {
.name = "SetupDiDestroyDeviceInfoList",
.patch = my_SetupDiDestroyDeviceInfoList,
.link = (void **) &next_SetupDiDestroyDeviceInfoList,
},
};
/* p3iolib appends \p3io to whatever path is returned by SETUPAPI */
static const wchar_t p3io_path_prefix[] = L"$p3io";
static const wchar_t p3io_path[] = L"$p3io\\p3io";
static HDEVINFO p3io_hdevinfo;
static HDEVINFO WINAPI my_SetupDiGetClassDevsA(
const GUID *class_guid,
const char *enumerator,
HWND hwnd,
DWORD flags)
{
HDEVINFO result;
result = next_SetupDiGetClassDevsA(class_guid, enumerator, hwnd, flags);
if (result != INVALID_HANDLE_VALUE && IsEqualGUID(class_guid, &p3io_guid)) {
p3io_hdevinfo = result;
}
return result;
}
static HDEVINFO WINAPI my_SetupDiGetClassDevsW(
const GUID *class_guid,
const wchar_t *enumerator,
HWND hwnd,
DWORD flags)
{
HDEVINFO result;
result = next_SetupDiGetClassDevsW(class_guid, enumerator, hwnd, flags);
if (result != INVALID_HANDLE_VALUE && IsEqualGUID(class_guid, &p3io_guid)) {
p3io_hdevinfo = result;
}
return result;
}
static BOOL WINAPI my_SetupDiEnumDeviceInterfaces(
HDEVINFO dev_info,
SP_DEVINFO_DATA *info_data,
const GUID *iface_guid,
DWORD index,
SP_DEVICE_INTERFACE_DATA *ifd)
{
if (dev_info != p3io_hdevinfo) {
return next_SetupDiEnumDeviceInterfaces(
dev_info,
info_data,
iface_guid,
index,
ifd);
}
/* Not implemented */
log_assert(info_data == NULL);
/* Class GUID is not the same thing as interface GUID but whatever. p3iolib
treats them as the same thing. */
if (index > 0) {
SetLastError(ERROR_NO_MORE_ITEMS);
return FALSE;
}
if (ifd == NULL || ifd->cbSize != sizeof(*ifd)) {
SetLastError(ERROR_INVALID_USER_BUFFER);
return FALSE;
}
memcpy(&ifd->InterfaceClassGuid, &p3io_guid, sizeof(GUID));
ifd->Flags = SPINT_ACTIVE | SPINT_DEFAULT;
ifd->Reserved = 0;
SetLastError(ERROR_SUCCESS);
return TRUE;
}
static BOOL WINAPI my_SetupDiGetDeviceInterfaceDetailW(
HDEVINFO dev_info,
SP_DEVICE_INTERFACE_DATA *ifd,
SP_DEVICE_INTERFACE_DETAIL_DATA_W *detail,
DWORD size,
DWORD *required_size,
SP_DEVINFO_DATA *info_data)
{
if (dev_info != p3io_hdevinfo) {
return next_SetupDiGetDeviceInterfaceDetailW(
dev_info,
ifd,
detail,
size,
required_size,
info_data);
}
/* Not implemented */
log_assert(info_data == NULL);
if (required_size != NULL) {
*required_size = sizeof(*detail) + sizeof(p3io_path_prefix);
}
if (detail != NULL) {
if (size < sizeof(*detail) + sizeof(p3io_path_prefix)) {
SetLastError(ERROR_INSUFFICIENT_BUFFER);
return FALSE;
}
if (detail->cbSize != sizeof(*detail)) {
SetLastError(ERROR_INVALID_USER_BUFFER);
return FALSE;
}
memcpy( detail->DevicePath,
p3io_path_prefix,
sizeof(p3io_path_prefix));
}
SetLastError(ERROR_SUCCESS);
return TRUE;
}
static BOOL WINAPI my_SetupDiDestroyDeviceInfoList(HDEVINFO dev_info)
{
if (dev_info == p3io_hdevinfo) {
p3io_hdevinfo = NULL;
}
return next_SetupDiDestroyDeviceInfoList(dev_info);
}
void p3io_setupapi_insert_hooks(HMODULE target)
{
hook_table_apply(
target,
"setupapi.dll",
p3io_setupapi_syms,
lengthof(p3io_setupapi_syms));
log_info("Inserted P3IO setupapi hooks into %p", target);
}
bool p3io_setupapi_match_path(const wchar_t *path)
{
return wstr_eq(path, p3io_path);
}
+14
View File
@@ -0,0 +1,14 @@
#ifndef P3IO_DEVMGR_H
#define P3IO_DEVMGR_H
/* Not called setupapi.h so that we don't collide with the real setupapi.h */
#include <windows.h>
#include <stdbool.h>
#include <stddef.h>
void p3io_setupapi_insert_hooks(HMODULE target);
bool p3io_setupapi_match_path(const wchar_t *path);
#endif
+554
View File
@@ -0,0 +1,554 @@
#define LOG_MODULE "p3ioemu"
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <windows.h>
#include "hook/iohook.h"
#include "p3io/cmd.h"
#include "p3io/frame.h"
#include "p3io/ioctl.h"
#include "p3ioemu/devmgr.h"
#include "p3ioemu/emu.h"
#include "p3ioemu/uart.h"
#include "util/iobuf.h"
#include "util/log.h"
static HANDLE p3io_emu_fd;
static uint8_t p3io_emu_resp_bytes[256];
static struct iobuf p3io_emu_resp;
static const struct p3io_ops *p3io_ops;
static void *p3io_ops_ctx;
static HRESULT p3io_emu_handle_open(struct irp *irp);
static HRESULT p3io_emu_handle_close(struct irp *irp);
static HRESULT p3io_emu_handle_ioctl(struct irp *irp);
static HRESULT p3io_emu_handle_read(struct irp *irp);
static HRESULT p3io_emu_handle_write(struct irp *irp);
static HRESULT p3io_cmd_dispatch(const union p3io_req_any *req);
static void p3io_cmd_get_version(
const struct p3io_hdr *req,
struct p3io_resp_version *resp);
static void p3io_cmd_set_watchdog(
const struct p3io_req_u8 *req,
struct p3io_resp_u8 *resp);
static void p3io_cmd_set_outputs(
const struct p3io_req_set_outputs *req,
struct p3io_resp_u8 *resp);
static void p3io_cmd_read_plug(
const struct p3io_req_read_plug *req,
struct p3io_resp_read_plug *resp);
static void p3io_cmd_get_cab_type_or_dipsw(
const struct p3io_req_get_cab_type_or_dipsw *req,
struct p3io_resp_get_cab_type_or_dipsw *resp);
static void p3io_cmd_get_video_freq(
const struct p3io_req_u8 *req,
struct p3io_resp_u8 *resp);
static void p3io_cmd_set_mode(
const struct p3io_req_u8 *req,
struct p3io_resp_u8 *resp);
static void p3io_cmd_get_coinstock(
const struct p3io_req_u8 *req,
struct p3io_resp_coin_stock *resp);
static void p3io_cmd_set_coin_counter(
const struct p3io_req_set_coin_counter *req,
struct p3io_resp_u8 *resp);
static void p3io_cmd_unknown(
const union p3io_req_any *req,
struct p3io_resp_u8 *resp);
void p3io_emu_init(const struct p3io_ops *ops, void *ctx)
{
log_assert(p3io_emu_fd == NULL);
log_assert(ops != NULL);
p3io_emu_resp.bytes = p3io_emu_resp_bytes;
p3io_emu_resp.nbytes = sizeof(p3io_emu_resp_bytes);
p3io_emu_resp.pos = 0;
p3io_ops = ops;
p3io_ops_ctx = ctx;
p3io_emu_fd = iohook_open_dummy_fd();
}
void p3io_emu_fini(void)
{
if (p3io_emu_fd == NULL) {
return;
}
CloseHandle(p3io_emu_fd);
p3io_emu_fd = NULL;
if (p3io_ops->close != NULL) {
p3io_ops->close(p3io_ops_ctx);
}
}
HRESULT p3io_emu_dispatch_irp(struct irp *irp)
{
log_assert(irp != NULL);
if (irp->op != IRP_OP_OPEN && irp->fd != p3io_emu_fd) {
return irp_invoke_next(irp);
}
switch (irp->op) {
case IRP_OP_OPEN: return p3io_emu_handle_open(irp);
case IRP_OP_CLOSE: return p3io_emu_handle_close(irp);
case IRP_OP_READ: return p3io_emu_handle_read(irp);
case IRP_OP_WRITE: return p3io_emu_handle_write(irp);
case IRP_OP_IOCTL: return p3io_emu_handle_ioctl(irp);
default: return E_NOTIMPL;
}
}
static HRESULT p3io_emu_handle_open(struct irp *irp)
{
if (!p3io_setupapi_match_path(irp->open_filename)) {
return irp_invoke_next(irp);
}
log_info("P3IO device opened");
irp->fd = p3io_emu_fd;
return S_OK;
}
static HRESULT p3io_emu_handle_close(struct irp *irp)
{
log_info("P3IO device closed");
return S_OK;
}
static HRESULT p3io_emu_handle_ioctl(struct irp *irp)
{
uint32_t *dest;
uint32_t pad;
HRESULT hr;
if (irp->ioctl != P3IO_IOCTL_READ_JAMMA) {
log_warning("Unknown ioctl: %x", irp->ioctl);
return E_NOTIMPL;
}
if (irp->read.nbytes < sizeof(uint32_t)) {
log_warning("Insufficient ioctl response buffer space");
return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
}
pad = 0;
if (p3io_ops->read_jamma != NULL) {
hr = p3io_ops->read_jamma(p3io_ops_ctx, &pad);
if (FAILED(hr)) {
return hr;
}
}
dest = (uint32_t *) irp->read.bytes;
*dest = ~_byteswap_ulong(pad);
irp->read.pos = sizeof(pad);
return S_OK;
}
static HRESULT p3io_emu_handle_read(struct irp *irp)
{
struct const_iobuf tmp;
iobuf_flip(&tmp, &p3io_emu_resp);
p3io_emu_resp.pos = 0;
return iobuf_move(&irp->read, &tmp);
}
static HRESULT p3io_emu_handle_write(struct irp *irp)
{
union p3io_req_any req;
struct iobuf deframe;
HRESULT hr;
memset(&req, 0, sizeof(req));
deframe.bytes = req.raw;
deframe.nbytes = sizeof(req.raw);
deframe.pos = 0;
hr = p3io_frame_decode(&deframe, &irp->write);
if (FAILED(hr)) {
return hr;
}
if (deframe.pos < sizeof(req.hdr)) {
log_warning("Received truncated P3IO frame");
return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
}
p3io_emu_resp.pos = 0;
return p3io_cmd_dispatch(&req);
}
static HRESULT p3io_cmd_dispatch(const union p3io_req_any *req)
{
union p3io_resp_any resp;
uint8_t cmd;
cmd = p3io_req_cmd(req);
switch (cmd) {
case P3IO_CMD_GET_VERSION:
p3io_cmd_get_version(&req->hdr, &resp.version);
break;
case P3IO_CMD_SET_WATCHDOG:
p3io_cmd_set_watchdog(&req->u8, &resp.u8);
break;
case P3IO_CMD_SET_OUTPUTS:
p3io_cmd_set_outputs(&req->set_outputs, &resp.u8);
break;
case P3IO_CMD_READ_PLUG:
p3io_cmd_read_plug(&req->read_plug, &resp.read_plug);
break;
case P3IO_CMD_GET_CAB_TYPE_OR_DIPSW:
p3io_cmd_get_cab_type_or_dipsw(&req->cab_type_or_dipsw,
&resp.cab_type_or_dipsw);
break;
case P3IO_CMD_GET_VIDEO_FREQ:
p3io_cmd_get_video_freq(&req->u8, &resp.u8);
break;
case P3IO_CMD_SET_MODE:
p3io_cmd_set_mode(&req->u8, &resp.u8);
break;
case P3IO_CMD_GET_COINSTOCK:
p3io_cmd_get_coinstock(&req->u8, &resp.coin_stock);
break;
case P3IO_CMD_SET_COINCOUNTER:
p3io_cmd_set_coin_counter(&req->set_coin_counter, &resp.u8);
break;
case P3IO_CMD_RS232_OPEN_CLOSE:
p3io_uart_cmd_open_close(&req->rs232_open_close, &resp.u8);
break;
case P3IO_CMD_RS232_READ:
p3io_uart_cmd_read(&req->rs232_read, &resp.rs232_read);
break;
case P3IO_CMD_RS232_WRITE:
p3io_uart_cmd_write(&req->rs232_write, &resp.rs232_write);
break;
default:
p3io_cmd_unknown(req, &resp.u8);
break;
}
p3io_frame_encode(&p3io_emu_resp, &resp, resp.hdr.nbytes + 1);
return S_OK;
}
static void p3io_cmd_get_version(
const struct p3io_hdr *req,
struct p3io_resp_version *resp)
{
log_misc("%s", __func__);
p3io_resp_init(&resp->hdr, sizeof(resp), req);
resp->status = 0;
resp->str[0] = 'H';
resp->str[1] = 'D';
resp->str[2] = 'X';
resp->str[3] = '\0';
resp->major = 1;
resp->minor = 2;
resp->patch = 3;
}
static void p3io_cmd_set_watchdog(
const struct p3io_req_u8 *req,
struct p3io_resp_u8 *resp)
{
log_misc("%s(%02x)", __func__, req->u8);
p3io_resp_init(&resp->hdr, sizeof(*resp), &req->hdr);
resp->status = 0;
resp->u8 = 0;
}
static void p3io_cmd_set_outputs(
const struct p3io_req_set_outputs *req,
struct p3io_resp_u8 *resp)
{
uint32_t outputs;
HRESULT hr;
if (p3io_ops->set_outputs != NULL) {
outputs = _byteswap_ulong(req->outputs);
hr = p3io_ops->set_outputs(p3io_ops_ctx, outputs);
} else {
hr = S_OK;
}
p3io_resp_init(&resp->hdr, sizeof(*resp), &req->hdr);
resp->status = FAILED(hr);
resp->u8 = 0;
}
static void p3io_cmd_read_plug(
const struct p3io_req_read_plug *req,
struct p3io_resp_read_plug *resp)
{
HRESULT hr;
log_misc("%s(%02x)", __func__, req->flags);
p3io_resp_init(&resp->hdr, sizeof(*resp), &req->hdr);
resp->present = 1;
/*
* Some notes about the flags available here:
* 0x10: black plug rom read
* 0x12: black plug eeprom read
* 0x00: white plug rom read
* 0x02: white plug eeprom read
*
* The structure of the buffer is the same for all flags set and the game
* just grabs whatever it currently needs from the buffer. i.e. if we
* send everything on every read (rom + eeprom) always, the game doesn't
* care
*/
if (req->flags & 0x10) {
if (p3io_ops->get_roundplug) {
hr = p3io_ops->get_roundplug(p3io_ops_ctx, 0, resp->rom,
resp->eeprom);
} else {
log_warning("Reading black roundplug but no function available");
memset(resp->rom, 0, 8);
memset(resp->eeprom, 0, 32);
hr = S_OK;
}
} else {
if (p3io_ops->get_roundplug) {
hr = p3io_ops->get_roundplug(p3io_ops_ctx, 1, resp->rom,
resp->eeprom);
} else {
log_warning("Reading white roundplug but no function available");
memset(resp->rom, 0, 8);
memset(resp->eeprom, 0, 32);
hr = S_OK;
}
}
log_misc("Reading %s roundplug, success 0x%lX, rom %02X%02X%02X%02X%02X%02X"
"%02X%02X, eeprom sig %X%X%X%X%X%X",
req->flags & 0x10 ? "black" : "white", hr, resp->rom[0], resp->rom[1],
resp->rom[2], resp->rom[3], resp->rom[4], resp->rom[5], resp->rom[6],
resp->rom[7], resp->eeprom[0], resp->eeprom[1], resp->eeprom[2],
resp->eeprom[3], resp->eeprom[4], resp->eeprom[5]);
resp->status = FAILED(hr);
}
static void p3io_cmd_get_cab_type_or_dipsw(
const struct p3io_req_get_cab_type_or_dipsw *req,
struct p3io_resp_get_cab_type_or_dipsw *resp)
{
HRESULT hr;
uint8_t dipsw;
enum p3io_cab_type type;
p3io_resp_init(&resp->hdr, sizeof(*resp), &req->hdr);
hr = S_OK;
if (req->cab_type_or_dipsw == 0) {
if (p3io_ops->get_cab_type) {
hr = p3io_ops->get_cab_type(p3io_ops_ctx, &type);
if (hr == S_OK) {
switch (type) {
case P3IO_CAB_TYPE_SD:
log_misc("%s: Returning cab type SD", __func__);
resp->status = 1;
break;
case P3IO_CAB_TYPE_HD:
log_misc("%s: Returning cab type HD", __func__);
resp->status = 2;
break;
default:
log_assert(false);
break;
}
} else {
resp->status = 0;
}
} else {
resp->status = 0;
}
} else if (req->cab_type_or_dipsw == 1) {
if (p3io_ops->get_dipsw) {
hr = p3io_ops->get_dipsw(p3io_ops_ctx, &dipsw);
if (hr == S_OK) {
resp->status = dipsw;
} else {
resp->status = 0;
}
} else {
resp->status = 0;
}
} else {
log_warning("Unknown value for cab type or dipsw: %d",
req->cab_type_or_dipsw);
resp->status = 0;
}
}
static void p3io_cmd_get_video_freq(
const struct p3io_req_u8 *req,
struct p3io_resp_u8 *resp)
{
HRESULT hr;
enum p3io_video_freq freq;
p3io_resp_init(&resp->hdr, sizeof(*resp), &req->hdr);
if (p3io_ops->get_video_freq != NULL) {
hr = p3io_ops->get_video_freq(p3io_ops_ctx, &freq);
} else {
hr = S_OK;
freq = P3IO_VIDEO_FREQ_31KHZ;
}
if (hr == S_OK) {
switch (freq) {
case P3IO_VIDEO_FREQ_15KHZ:
log_misc("%s: Returning 15 kHz", __func__);
resp->status = 0;
resp->u8 = 0x00;
break;
case P3IO_VIDEO_FREQ_31KHZ:
log_misc("%s: Returning 31 kHz", __func__);
resp->status = 0;
resp->u8 = 0x80;
break;
default:
log_assert(false);
break;
}
} else {
log_misc("%s: Returning error! (hr=%x)", __func__, (int) hr);
resp->status = 1;
resp->u8 = 0x00;
}
}
static void p3io_cmd_set_mode(
const struct p3io_req_u8 *req,
struct p3io_resp_u8 *resp)
{
log_misc("%s(%02x)", __func__, req->u8);
p3io_resp_init(&resp->hdr, sizeof(*resp), &req->hdr);
resp->status = 0;
resp->u8 = 0;
}
static void p3io_cmd_get_coinstock(
const struct p3io_req_u8 *req,
struct p3io_resp_coin_stock *resp)
{
uint16_t slots[2];
HRESULT hr;
memset(slots, 0, sizeof(slots));
if (p3io_ops->get_coinstock != NULL) {
hr = p3io_ops->get_coinstock(p3io_ops_ctx, slots, lengthof(slots));
} else {
hr = S_OK;
}
p3io_resp_init(&resp->hdr, sizeof(*resp), &req->hdr);
resp->status = 0;
resp->error = FAILED(hr);
resp->slots[0] = _byteswap_ushort(slots[0]);
resp->slots[1] = _byteswap_ushort(slots[1]);
}
static void p3io_cmd_set_coin_counter(
const struct p3io_req_set_coin_counter *req,
struct p3io_resp_u8 *resp)
{
log_misc("%s(%02x %02x)", __func__, req->coin_counter[0],
req->coin_counter[1]);
p3io_resp_init(&resp->hdr, sizeof(*resp), &req->hdr);
resp->status = 0;
resp->u8 = 0;
}
static void p3io_cmd_unknown(
const union p3io_req_any *req,
struct p3io_resp_u8 *resp)
{
log_warning("Unsupported P3IO command: %02x", p3io_req_cmd(req));
p3io_resp_init(&resp->hdr, sizeof(*resp), &req->hdr);
resp->status = 1;
resp->u8 = 0;
}
+66
View File
@@ -0,0 +1,66 @@
#ifndef P3IO_EMU_H
#define P3IO_EMU_H
#include <windows.h>
#include <stddef.h>
#include <stdint.h>
#include "hook/iohook.h"
/**
* Enum for available video frequencies to select from. Depending on the
* game, this feature is used or unused.
*/
enum p3io_video_freq {
P3IO_VIDEO_FREQ_15KHZ = 0,
P3IO_VIDEO_FREQ_31KHZ = 1,
};
/**
* Enum for available cabinet/display types to select from. Depending on the
* game, this feature is used or unused.
*/
enum p3io_cab_type {
P3IO_CAB_TYPE_SD = 0,
P3IO_CAB_TYPE_HD = 1,
};
/**
* P3IO operation dispatching table. Many operations are optional and not used
* by every game that's running on that type of hardware which means you can
* ommit hooking up implementations for them.
*/
struct p3io_ops {
void (*close)(void *ctx);
HRESULT (*read_jamma)(void *ctx, uint32_t *state);
HRESULT (*set_outputs)(void *ctx, uint32_t state);
HRESULT (*get_dipsw)(void *ctx, uint8_t *state);
HRESULT (*get_cab_type)(void *ctx, enum p3io_cab_type *type);
HRESULT (*get_video_freq)(void *ctx, enum p3io_video_freq *freq);
HRESULT (*get_coinstock)(void *ctx, uint16_t *slots, size_t nslots);
HRESULT (*get_roundplug)(void *ctx, uint8_t plug_id, uint8_t* rom,
uint8_t* eeprom);
};
/**
* Initialize the p3io emulation backend.
*
* @param ops Dispatch table with operation hooks for your target game.
* @param ctx A context which is passed along with every call to a function of
* your provided dispatch table.
*/
void p3io_emu_init(const struct p3io_ops *ops, void *ctx);
/**
* Shutdown the p3io emulation backend.
*/
void p3io_emu_fini(void);
/**
* p3io emulation dispatch irp call. This needs to be hooked to the iohook
* module in order to drive the emulation backend with detoured system calls.
*/
HRESULT p3io_emu_dispatch_irp(struct irp *irp);
#endif
+445
View File
@@ -0,0 +1,445 @@
#define LOG_MODULE "p3ioemu-uart"
#include <windows.h>
#include <ntdef.h>
#include <devioctl.h>
#include <ntddser.h>
#include <string.h>
#include "hook/iohook.h"
#include "p3io/cmd.h"
#include "p3ioemu/uart.h"
#include "util/iobuf.h"
#include "util/log.h"
static HRESULT p3io_uart_open(
const wchar_t *path,
uint32_t baud_rate,
HANDLE *fd);
static HRESULT p3io_uart_close(HANDLE fd);
static HRESULT p3io_uart_read(HANDLE fd, struct iobuf *iobuf);
static HRESULT p3io_uart_write(HANDLE fd, struct const_iobuf *iobuf);
static const wchar_t *p3io_uart_paths[2];
static HANDLE p3io_uart_fds[2];
static const uint32_t p3io_uart_baud_codes[] = {
0, 0, 19200, 38400, 57600
};
void p3io_uart_set_path(size_t uart_no, const wchar_t *path)
{
log_assert(uart_no <= 1);
p3io_uart_paths[uart_no] = path;
}
void p3io_uart_cmd_open_close(
const struct p3io_req_rs232_open_close *req,
struct p3io_resp_u8 *resp)
{
const wchar_t *path;
uint32_t baud_rate;
HRESULT hr;
log_assert(req != NULL);
log_assert(resp != NULL);
if (req->port_no > 1) {
log_warning("Invalid UART number %i", req->port_no);
hr = E_INVALIDARG;
goto end;
}
switch (req->subcmd) {
case P3IO_RS232_CMD_OPEN:
log_info("Opening remote RS232 port #%d", req->port_no);
if (req->baud_code < lengthof(p3io_uart_baud_codes)) {
baud_rate = p3io_uart_baud_codes[req->baud_code];
} else {
baud_rate = 0;
}
if (baud_rate == 0) {
log_warning("Invalid baud rate code: %02x", req->baud_code);
hr = E_FAIL;
goto end;
}
path = p3io_uart_paths[req->port_no];
if (path == NULL) {
log_warning("UART #%i: No downstream connection", req->port_no);
hr = E_FAIL;
goto end;
}
hr = p3io_uart_open(path, baud_rate, &p3io_uart_fds[req->port_no]);
if (FAILED(hr)) {
log_warning("p3io_uart_open() failed: %x", (int) hr);
}
break;
case P3IO_RS232_CMD_CLOSE:
log_info("Closing remote RS232 port #%d", req->port_no);
hr = p3io_uart_close(p3io_uart_fds[req->port_no]);
p3io_uart_fds[req->port_no] = NULL;
break;
default:
log_warning("Unknown subcommand %02x", req->subcmd);
hr = E_FAIL;
break;
}
end:
p3io_resp_init(&resp->hdr, sizeof(resp), &req->hdr);
resp->status = 0;
resp->u8 = FAILED(hr);
}
void p3io_uart_cmd_read(
const struct p3io_req_rs232_read *req,
struct p3io_resp_rs232_read *resp)
{
struct iobuf iobuf;
HRESULT hr;
log_assert(req != NULL);
log_assert(resp != NULL);
iobuf.bytes = resp->bytes;
iobuf.nbytes = req->nbytes;
iobuf.pos = 0;
if (req->port_no > 1) {
log_warning("Invalid UART number %i", req->port_no);
hr = E_INVALIDARG;
goto end;
}
if (req->nbytes > sizeof(resp->bytes)) {
log_warning("Excessive read %i", req->nbytes);
hr = E_INVALIDARG;
goto end;
}
hr = p3io_uart_read(p3io_uart_fds[req->port_no], &iobuf);
end:
/* Variable-length response, init the header manually */
resp->hdr.nbytes = iobuf.pos + 3;
resp->hdr.seq_no = req->hdr.seq_no;
resp->status = FAILED(hr);
resp->nbytes = iobuf.pos;
}
void p3io_uart_cmd_write(
const struct p3io_req_rs232_write *req,
struct p3io_resp_rs232_write *resp)
{
struct const_iobuf iobuf;
HRESULT hr;
iobuf.bytes = req->bytes;
iobuf.nbytes = req->nbytes;
iobuf.pos = 0;
if (req->port_no > 1) {
log_warning("Invalid UART number %i", req->port_no);
hr = E_INVALIDARG;
goto end;
}
hr = p3io_uart_write(p3io_uart_fds[req->port_no], &iobuf);
end:
p3io_resp_init(&resp->hdr, sizeof(resp), &req->hdr);
resp->status = FAILED(hr);
resp->nbytes = iobuf.pos;
}
static HRESULT p3io_uart_open(
const wchar_t *path,
uint32_t baud_rate,
HANDLE *fd)
{
struct irp irp;
uint32_t comm_mask;
uint32_t flags;
SERIAL_QUEUE_SIZE qs;
SERIAL_TIMEOUTS timeouts;
SERIAL_LINE_CONTROL lc;
SERIAL_BAUD_RATE baud;
SERIAL_HANDFLOW handflow;
HRESULT hr;
if (*fd != NULL) {
log_warning("Port is already open");
return HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED);
}
memset(&irp, 0, sizeof(irp));
irp.op = IRP_OP_OPEN;
irp.open_filename = path;
irp.open_access = GENERIC_READ | GENERIC_WRITE;
irp.open_share = 0;
irp.open_sa = NULL;
irp.open_creation = OPEN_EXISTING;
irp.open_flags = 0;
irp.open_tmpl = NULL;
hr = irp_invoke_next(&irp);
if (FAILED(hr)) {
goto fail;
}
*fd = irp.fd;
memset(&irp, 0, sizeof(irp));
irp.op = IRP_OP_IOCTL;
irp.fd = *fd;
irp.ioctl = IOCTL_SERIAL_SET_WAIT_MASK;
irp.write.bytes = (const void *) &comm_mask;
irp.write.nbytes = sizeof(comm_mask);
comm_mask = EV_RXCHAR;
hr = irp_invoke_next(&irp);
if (FAILED(hr)) {
goto fail;
}
memset(&irp, 0, sizeof(irp));
irp.op = IRP_OP_IOCTL;
irp.fd = *fd;
irp.ioctl = IOCTL_SERIAL_SET_QUEUE_SIZE;
irp.write.bytes = (const void *) &qs;
irp.write.nbytes = sizeof(qs);
qs.InSize = 0x4000;
qs.OutSize = 0x4000;
hr = irp_invoke_next(&irp);
if (FAILED(hr)) {
goto fail;
}
memset(&irp, 0, sizeof(irp));
irp.op = IRP_OP_IOCTL;
irp.fd = *fd;
irp.ioctl = IOCTL_SERIAL_PURGE;
irp.write.bytes = (const void *) &flags;
irp.write.nbytes = sizeof(flags);
flags = PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR;
hr = irp_invoke_next(&irp);
if (FAILED(hr)) {
goto fail;
}
memset(&irp, 0, sizeof(irp));
irp.op = IRP_OP_IOCTL;
irp.fd = *fd;
irp.ioctl = IOCTL_SERIAL_SET_TIMEOUTS;
irp.write.bytes = (const void *) &timeouts;
irp.write.nbytes = sizeof(timeouts);
timeouts.ReadIntervalTimeout = -1;
timeouts.ReadTotalTimeoutMultiplier = 10;
timeouts.ReadTotalTimeoutConstant = 0;
timeouts.WriteTotalTimeoutMultiplier = 100;
timeouts.WriteTotalTimeoutConstant = 0;
hr = irp_invoke_next(&irp);
if (FAILED(hr)) {
goto fail;
}
memset(&irp, 0, sizeof(irp));
irp.op = IRP_OP_IOCTL;
irp.fd = *fd;
irp.ioctl = IOCTL_SERIAL_SET_LINE_CONTROL;
irp.write.bytes = (const void *) &lc;
irp.write.nbytes = sizeof(lc);
lc.WordLength = 8;
lc.Parity = NO_PARITY;
lc.StopBits = STOP_BIT_1;
hr = irp_invoke_next(&irp);
if (FAILED(hr)) {
goto fail;
}
memset(&irp, 0, sizeof(irp));
irp.op = IRP_OP_IOCTL;
irp.fd = *fd;
irp.ioctl = IOCTL_SERIAL_SET_BAUD_RATE;
irp.write.bytes = (const void *) &baud;
irp.write.nbytes = sizeof(baud);
baud.BaudRate = baud_rate;
hr = irp_invoke_next(&irp);
if (FAILED(hr)) {
goto fail;
}
memset(&irp, 0, sizeof(irp));
irp.op = IRP_OP_IOCTL;
irp.fd = *fd;
irp.ioctl = IOCTL_SERIAL_SET_HANDFLOW;
irp.write.bytes = (const void *) &handflow;
irp.write.nbytes = sizeof(handflow);
handflow.ControlHandShake = 0;
handflow.FlowReplace = 0;
handflow.XonLimit = 0;
handflow.XoffLimit = 0;
hr = irp_invoke_next(&irp);
if (FAILED(hr)) {
goto fail;
}
return S_OK;
fail:
if (*fd != NULL) {
irp.op = IRP_OP_CLOSE;
irp.fd = *fd;
irp_invoke_next(&irp);
*fd = NULL;
}
return hr;
}
static HRESULT p3io_uart_close(HANDLE fd)
{
struct irp irp;
HRESULT hr;
if (fd != NULL) {
memset(&irp, 0, sizeof(irp));
irp.op = IRP_OP_CLOSE;
irp.fd = fd;
hr = irp_invoke_next(&irp);
if (FAILED(hr)) {
log_warning("Error closing port: %x", (int) hr);
}
} else {
log_warning("Port is already closed");
hr = S_OK;
}
return hr;
}
static HRESULT p3io_uart_read(HANDLE fd, struct iobuf *iobuf)
{
struct irp irp;
SERIAL_STATUS status;
HRESULT hr;
if (fd == NULL) {
log_warning("Read from unopened port");
return E_FAIL;
}
/* Peek RX buffer */
memset(&irp, 0, sizeof(irp));
irp.op = IRP_OP_IOCTL;
irp.fd = fd;
irp.ioctl = IOCTL_SERIAL_GET_COMMSTATUS;
irp.read.bytes = (uint8_t *) &status;
irp.read.nbytes = sizeof(status);
hr = irp_invoke_next(&irp);
if (FAILED(hr)) {
log_warning("UART FIFO peek failed: %x", (int) hr);
return hr;
}
/* Return immediately if no data available */
if (status.AmountInInQueue == 0) {
return S_OK;
}
/* Issue read */
memset(&irp, 0, sizeof(irp));
irp.op = IRP_OP_READ;
irp.fd = fd;
memcpy(&irp.read, iobuf, sizeof(*iobuf));
hr = irp_invoke_next(&irp);
if (FAILED(hr)) {
log_warning("Read error: %x", (int) hr);
return hr;
}
memcpy(iobuf, &irp.read, sizeof(*iobuf));
return S_OK;
}
static HRESULT p3io_uart_write(HANDLE fd, struct const_iobuf *iobuf)
{
struct irp irp;
HRESULT hr;
if (fd == NULL) {
log_warning("Write to unopened port");
return E_FAIL;
}
memset(&irp, 0, sizeof(irp));
irp.op = IRP_OP_WRITE;
irp.fd = fd;
memcpy(&irp.write, iobuf, sizeof(*iobuf));
hr = irp_invoke_next(&irp);
if (SUCCEEDED(hr)) {
memcpy(iobuf, &irp.write, sizeof(*iobuf));
} else {
log_warning("Write error: %x", (int) hr);
}
return hr;
}
+20
View File
@@ -0,0 +1,20 @@
#ifndef P3IO_UART_H
#define P3IO_UART_H
#include "p3io/cmd.h"
void p3io_uart_set_path(size_t uart_no, const wchar_t *path);
void p3io_uart_cmd_open_close(
const struct p3io_req_rs232_open_close *req,
struct p3io_resp_u8 *resp);
void p3io_uart_cmd_read(
const struct p3io_req_rs232_read *req,
struct p3io_resp_rs232_read *resp);
void p3io_uart_cmd_write(
const struct p3io_req_rs232_write *req,
struct p3io_resp_rs232_write *resp);
#endif