1
0
mirror of https://github.com/djhackersdev/bemanitools.git synced 2025-02-20 20:41:10 +01:00

fix(iidx/ezusb): Fix IO buffer inconsistency on ezusb ioctl level

This commit is contained in:
icex2 2023-04-03 01:06:19 +02:00 committed by icex2
parent 4c127d26e5
commit da94ad8f76

View File

@ -28,6 +28,10 @@
#include "util/log.h"
#include "util/str.h"
// The max buffer size in iidx's ezusb client library is 4096 for the initial
// firmware download.
#define MAX_IOCTL_BUFFER_SIZE 4096
static HRESULT ezusb_get_device_descriptor(struct irp *irp);
static HRESULT ezusb_vendor_req(struct irp *irp);
static HRESULT ezusb_upload_fw(struct irp *irp);
@ -115,43 +119,114 @@ static HRESULT ezusb_open(struct irp *irp)
static HRESULT ezusb_ioctl(struct irp *irp)
{
HRESULT res;
// Stack alloc'd and fixed sized buffers to avoid processing costs with
// allocations. Ensure buffers are large enough for any operation
uint8_t write_buffer_local[MAX_IOCTL_BUFFER_SIZE];
uint8_t read_buffer_local[MAX_IOCTL_BUFFER_SIZE];
uint8_t *write_buffer_orig;
uint8_t *read_buffer_orig;
// Save original buffer that is owned and managed by the caller/the game
// Do NOT read/write these buffers directly because the game's ezusb
// interface library does not access to them entirely thread safe.
// This causes verious odd bugs due to data read/write inconsistencies
// between data access and modification by at least two different threads.
//
// The game's ezusb (client) library was created on a platform with no
// true multi-core processing (Pentium 4 era of hardware). However, the
// developers utilized threading primitives of the Win32 API to ensure
// a high rate of data updates to reduce input latency. This was architected
// using a dedicated polling thread in the ezusb client library that drives
// the IO polling on a high update rate. The captured data was stored in
// a shared buffer that is also accessible by other threads from the main
// game binary. However, the data access to the same shared buffer with the
// polling backend is not synchronized
//
// Thus, the various odd and flaky ezusb communication bugs we see on
// modern, and true multi-core hardware, are the concurrency management
// mistakes that are now creeping up. The developers back then had no
// proper means to test these due to the lack of hardware capabilities.
// Save the IO buffer that is used by the ezusb client backend and shared
// with the game's main thread
write_buffer_orig = (uint8_t*) irp->write.bytes;
read_buffer_orig = irp->read.bytes;
// Prepare our own thread locally managed and non shared buffers for any
// further data operations that are part of the ezusb emulation stack
memset(write_buffer_local, 0, sizeof(write_buffer_local));
memset(read_buffer_local, 0, sizeof(read_buffer_local));
// Sanity check and visibility, in case this ever overflows
if (irp->write.nbytes > sizeof(write_buffer_local)) {
log_fatal("Insufficient local write buffer available for ioctl, local "
"size %d, ioctl buffer size %d",
sizeof(write_buffer_local),
irp->write.nbytes);
}
if (irp->read.nbytes > sizeof(read_buffer_local)) {
log_fatal("Insufficient local read buffer available for ioctl, local "
"size %d, ioctl buffer size %d",
sizeof(read_buffer_local),
irp->read.nbytes);
}
// Temporarily hook our local buffers to the irp
irp->write.bytes = write_buffer_local;
irp->read.bytes = read_buffer_local;
// Move data from the shared buffers to the local one
// Probably the "most atomic way possible" to have the least amount of
// overlap with the game's shared buffer
memcpy(write_buffer_local, write_buffer_orig, irp->write.nbytes);
memcpy(read_buffer_local, read_buffer_orig, irp->read.nbytes);
/* For debugging */
#ifdef EZUSB_EMU_DEBUG_DUMP
/* For debugging */
ezusb_emu_util_log_usb_msg(
"BEFORE",
irp->ioctl,
irp->read.bytes,
irp->read.nbytes,
irp->read.bytes,
irp->read.nbytes,
irp->write.bytes,
irp->write.nbytes);
read_buffered.bytes,
read_buffered.nbytes,
read_buffered.bytes,
read_buffered.nbytes,
write_buffered.bytes,
write_buffered.nbytes);
#endif
/* Cases are listed in order of first receipt */
switch (irp->ioctl) {
case IOCTL_Ezusb_GET_DEVICE_DESCRIPTOR:
return ezusb_get_device_descriptor(irp);
res = ezusb_get_device_descriptor(irp);
break;
case IOCTL_Ezusb_VENDOR_REQUEST:
return ezusb_vendor_req(irp);
res = ezusb_vendor_req(irp);
break;
case IOCTL_EZUSB_ANCHOR_DOWNLOAD:
return ezusb_upload_fw(irp);
res = ezusb_upload_fw(irp);
break;
case IOCTL_EZUSB_BULK_READ:
/* Misnomer: can be bulk or interrupt. */
return ezusb_pipe_read(irp);
res = ezusb_pipe_read(irp);
break;
case IOCTL_EZUSB_BULK_WRITE:
/* Ditto. */
return ezusb_pipe_write(irp);
res = ezusb_pipe_write(irp);
break;
default:
log_warning("Unknown ioctl %08x", irp->ioctl);
return E_INVALIDARG;
res = E_INVALIDARG;
}
#ifdef EZUSB_EMU_DEBUG_DUMP
@ -159,13 +234,24 @@ static HRESULT ezusb_ioctl(struct irp *irp)
ezusb_emu_util_log_usb_msg(
"AFTER",
irp->ioctl,
irp->read.bytes,
irp->read.nbytes,
irp->read.bytes,
irp->read.nbytes,
irp->write.bytes,
irp->write.nbytes);
read_buffered.bytes,
read_buffered.nbytes,
read_buffered.bytes,
read_buffered.nbytes,
write_buffered.bytes,
write_buffered.nbytes);
#endif
// Move data back to shared IO buffer. Again, keep this keeps the access
// overlap as minimal as possible
memcpy(write_buffer_orig, write_buffer_local, irp->write.nbytes);
memcpy(read_buffer_orig, read_buffer_local, irp->read.nbytes);
// Re-store the original irp buffer state
irp->write.bytes = (const uint8_t*) write_buffer_orig;
irp->read.bytes = read_buffer_orig;
return res;
}
/*
* USB TRANSFER LAYER