Files
djhackersdev_bemanitools/src/main/ddrhook-util/usbmem.c
T
icex2 e4a221e15b refactor: Entire code base, thread and log usage
Boils down to:
- Include headers
- Reduce boiler plate with helpers
- Swap out explicit usages with core API layer
  and ensure the right API is configured beforehand
2024-02-25 09:30:53 +01:00

525 lines
18 KiB
C

#define LOG_MODULE "usbmem"
// clang-format off
// Don't format because the order is important here
#include <windows.h>
#include <devioctl.h>
#include <ntdef.h>
#include <ntddser.h>
// clang-format on
#include <stdbool.h>
#include <string.h>
#include <wchar.h>
#include "core/log.h"
#include "hook/iohook.h"
#include "util/crc.h"
#include "util/fs.h"
#include "util/iobuf.h"
#include "util/str.h"
#define USBMEM_DEVICE_COUNT 2
#define USBMEM_COMMAND_BUF_SIZE 128
#define USBMEM_DATA_BUF_SIZE (1024 + 5)
static HANDLE usbmem_fd;
static char usbmem_response[USBMEM_DATA_BUF_SIZE];
static bool usbmem_pending;
static size_t usbmem_response_length;
static bool usbmem_enabled;
static HRESULT usbmem_open(struct irp *irp);
static HRESULT usbmem_close(struct irp *irp);
static HRESULT usbmem_write(struct irp *irp);
static HRESULT usbmem_read(struct irp *irp);
static HRESULT usbmem_ioctl(struct irp *irp);
typedef enum {
USBMEM_FILE_TYPE_NONE = 0,
USBMEM_FILE_TYPE_READ,
USBMEM_FILE_TYPE_WRITE,
} USBMEM_FILE_TYPE;
struct USBMEM_STATE {
bool connected;
bool opened;
bool errored;
USBMEM_FILE_TYPE file_type;
char basepath[MAX_PATH];
char path[MAX_PATH];
char filename[MAX_PATH];
uint8_t *buffer;
size_t buffer_len;
size_t buffer_index;
int buffer_frame;
};
static int target_device_id;
static struct USBMEM_STATE usbmem_state[USBMEM_DEVICE_COUNT];
static void usbmem_reset_file_state(int port)
{
if (usbmem_state[port].buffer) {
free(usbmem_state[port].buffer);
usbmem_state[port].buffer = NULL;
}
usbmem_state[port].buffer_len = 0;
usbmem_state[port].buffer_index = 0;
usbmem_state[port].buffer_frame = 0;
usbmem_state[port].file_type = USBMEM_FILE_TYPE_NONE;
}
void usbmem_init(const char *path_p1, const char *path_p2, const bool enabled)
{
log_assert(usbmem_fd == NULL);
HRESULT hr;
char usb_data_path[USBMEM_DEVICE_COUNT][MAX_PATH];
hr = iohook_open_nul_fd(&usbmem_fd);
if (hr != S_OK) {
log_fatal("Opening nul fd failed: %08lx", hr);
}
usbmem_enabled = enabled;
GetFullPathNameA(path_p1, sizeof(usb_data_path[0]), usb_data_path[0], NULL);
log_misc("USB memory data path (P1): %s", usb_data_path[0]);
GetFullPathNameA(path_p2, sizeof(usb_data_path[1]), usb_data_path[1], NULL);
log_misc("USB memory data path (P2): %s", usb_data_path[1]);
if (enabled && !path_exists(usb_data_path[0]) &&
!path_exists(usb_data_path[1])) {
log_warning("USB memory data path does not exist, disabling");
usbmem_enabled = false;
}
target_device_id = -1;
for (int i = 0; i < USBMEM_DEVICE_COUNT; i++) {
usbmem_state[i].connected = false;
usbmem_state[i].opened = false;
usbmem_state[i].errored = false;
strcpy(usbmem_state[i].basepath, usb_data_path[i]);
memset(usbmem_state[i].path, 0, sizeof(usbmem_state[i].path));
memset(usbmem_state[i].filename, 0, sizeof(usbmem_state[i].filename));
usbmem_reset_file_state(i);
}
}
void usbmem_fini(void)
{
if (usbmem_fd != NULL) {
CloseHandle(usbmem_fd);
}
usbmem_fd = NULL;
}
HRESULT
usbmem_dispatch_irp(struct irp *irp)
{
log_assert(irp != NULL);
if (irp->op != IRP_OP_OPEN && irp->fd != usbmem_fd) {
return iohook_invoke_next(irp);
}
switch (irp->op) {
case IRP_OP_OPEN:
return usbmem_open(irp);
case IRP_OP_CLOSE:
return usbmem_close(irp);
case IRP_OP_READ:
return usbmem_read(irp);
case IRP_OP_WRITE:
return usbmem_write(irp);
case IRP_OP_IOCTL:
return usbmem_ioctl(irp);
default:
return E_NOTIMPL;
}
}
static HRESULT usbmem_open(struct irp *irp)
{
log_assert(irp != NULL);
if (!wstr_eq(irp->open_filename, L"COM3")) {
return iohook_invoke_next(irp);
}
irp->fd = usbmem_fd;
log_info("USB edit data PCB opened");
return S_OK;
}
static HRESULT usbmem_close(struct irp *irp)
{
log_info("USB edit data PCB closed");
return S_OK;
}
static HRESULT usbmem_write(struct irp *irp)
{
struct const_iobuf *src;
char request[USBMEM_COMMAND_BUF_SIZE];
uint32_t nbytes;
log_assert(irp != NULL);
log_assert(irp->write.bytes != NULL);
src = &irp->write;
nbytes = src->nbytes > USBMEM_COMMAND_BUF_SIZE ? USBMEM_COMMAND_BUF_SIZE :
src->nbytes;
memcpy(request, src->bytes, nbytes);
request[nbytes - 1] = '\0'; /* This is always a CR. */
if (!usbmem_pending && target_device_id >= 0 &&
target_device_id < USBMEM_DEVICE_COUNT &&
usbmem_state[target_device_id].file_type == USBMEM_FILE_TYPE_READ) {
memset(usbmem_response, 0, sizeof(usbmem_response));
// log_misc("Read progress %08x/%08x bytes",
// usbmem_state[target_device_id].buffer_index,
// usbmem_state[target_device_id].buffer_len);
if (usbmem_state[target_device_id].buffer_index <
usbmem_state[target_device_id].buffer_len) {
usbmem_response_length = sizeof(usbmem_response);
usbmem_response[0] = 0x02; // 1 = 0x80 buffer, 2 = 0x400 buffer
usbmem_response[1] = usbmem_state[target_device_id].buffer_frame;
usbmem_response[2] = ~usbmem_response[1];
if (usbmem_state[target_device_id].buffer_frame == 0) {
snprintf(
usbmem_response + 4,
sizeof(usbmem_response) - 5,
"%zu ",
usbmem_state[target_device_id].buffer_len);
} else {
size_t len = sizeof(usbmem_response) - 5;
if (usbmem_state[target_device_id].buffer_index + len >
usbmem_state[target_device_id].buffer_len)
len = usbmem_state[target_device_id].buffer_len -
usbmem_state[target_device_id].buffer_index;
memcpy(
usbmem_response + 3,
usbmem_state[target_device_id].buffer +
usbmem_state[target_device_id].buffer_index,
len);
usbmem_state[target_device_id].buffer_index += len;
}
usbmem_state[target_device_id].buffer_frame++;
uint16_t crc =
crc16_msb(usbmem_response + 3, sizeof(usbmem_response) - 5, 0);
usbmem_response[sizeof(usbmem_response) - 2] = crc >> 8;
usbmem_response[sizeof(usbmem_response) - 1] = crc & 0xff;
} else {
usbmem_response_length = 1;
usbmem_response[0] = 0x04; // End
}
} else if (strlen(request) > 0) {
log_misc(">%s", request);
// Try to detect device ID
// The only commands without a device ID are "sver", "start", and
// "init".
char target_device_val = request[strlen(request) - 1];
char *target_device_id_ptr = strstr(request, " ");
if (target_device_id_ptr != NULL) {
target_device_val = *(target_device_id_ptr - 1);
}
// TODO: Rewrite this code to more cleanly handle hotplugging USB drives
if (str_eq(request, "sver")) {
str_cpy(
usbmem_response,
sizeof(usbmem_response),
"done GQHDXJAA DJHACKRS");
} else if (str_eq(request, "init") || str_eq(request, "start")) {
str_cpy(usbmem_response, sizeof(usbmem_response), "done");
} else if (target_device_val == 'a' || target_device_val == 'b') {
// Counterintuitively, b is P1 and a is P2
target_device_id = target_device_val == 'b' ? 0 : 1;
if (!usbmem_enabled) {
// Ignore all other USB device specific commands and pretend a
// device isn't plugged in when USB memory emulation is
// disabled.
str_cpy(usbmem_response, sizeof(usbmem_response), "fail");
} else if (usbmem_state[target_device_id].errored) {
// If the device went through the entire process once and the
// file didn't exist then just force it to be disabled until the
// game is restarted because otherwise it'll get stuck in a
// loop.
// TODO: This could be better emulated by using a keybind to
// simulate inserting and ejecting the USB drive to additionally
// clear the error flag.
str_cpy(usbmem_response, sizeof(usbmem_response), "fail");
} else if (str_eq(request, "on_a") || str_eq(request, "on_b")) {
usbmem_state[target_device_id].connected =
path_exists(usbmem_state[target_device_id].basepath);
usbmem_state[target_device_id].errored = false;
usbmem_reset_file_state(target_device_id);
if (usbmem_state[target_device_id].connected)
str_cpy(usbmem_response, sizeof(usbmem_response), "done");
else
str_cpy(
usbmem_response,
sizeof(usbmem_response),
"not connected");
} else if (str_eq(request, "offa") || str_eq(request, "offb")) {
if (usbmem_state[target_device_id].connected)
str_cpy(usbmem_response, sizeof(usbmem_response), "done");
else
str_cpy(
usbmem_response,
sizeof(usbmem_response),
"not connected");
usbmem_state[target_device_id].connected = false;
usbmem_reset_file_state(target_device_id);
} else if (str_eq(request, "opna") || str_eq(request, "opnb")) {
bool basepath_exists =
path_exists(usbmem_state[target_device_id].basepath);
if (!usbmem_state[target_device_id].connected ||
!basepath_exists) {
usbmem_state[target_device_id].opened = false;
str_cpy(
usbmem_response,
sizeof(usbmem_response),
"not connected");
} else {
usbmem_state[target_device_id].opened = true;
str_cpy(usbmem_response, sizeof(usbmem_response), "done");
}
} else if (str_eq(request, "clsa") || str_eq(request, "clsb")) {
if (usbmem_state[target_device_id].opened)
str_cpy(usbmem_response, sizeof(usbmem_response), "done");
else
str_cpy(
usbmem_response,
sizeof(usbmem_response),
"not connected");
usbmem_state[target_device_id].opened = false;
usbmem_reset_file_state(target_device_id);
} else if (
strncmp(request, "cda ", 4) == 0 ||
strncmp(request, "cdb ", 4) == 0) {
char *path = request + 4;
if (!usbmem_state[target_device_id].connected ||
!usbmem_state[target_device_id].opened) {
str_cpy(usbmem_response, sizeof(usbmem_response), "done");
} else if (path[1] == ':') {
// Absolute path
char temp[MAX_PATH];
snprintf(
temp,
sizeof(temp),
"%s\\%s",
usbmem_state[target_device_id].basepath,
path + 3);
if (!path_exists(temp)) {
log_warning("Couldn't find path %s", temp);
str_cpy(
usbmem_response, sizeof(usbmem_response), "done");
} else {
log_misc("Changing path to %s", temp);
str_cpy(
usbmem_state[target_device_id].path,
sizeof(usbmem_state[target_device_id].path),
temp);
str_cpy(
usbmem_response, sizeof(usbmem_response), "done");
}
} else {
str_cpy(usbmem_response, sizeof(usbmem_response), "fail");
}
} else if (
strncmp(request, "rda ", 4) == 0 ||
strncmp(request, "rdb ", 4) == 0) {
usbmem_reset_file_state(target_device_id);
if (!usbmem_state[target_device_id].connected ||
!usbmem_state[target_device_id].opened) {
str_cpy(usbmem_response, sizeof(usbmem_response), "fail");
} else {
char temp[MAX_PATH] = {0};
char *filename = request + 4;
snprintf(
temp,
sizeof(temp),
"%s\\%s",
usbmem_state[target_device_id].path,
filename);
if (usbmem_state[target_device_id].buffer) {
free(usbmem_state[target_device_id].buffer);
usbmem_state[target_device_id].buffer = NULL;
}
usbmem_state[target_device_id].file_type =
USBMEM_FILE_TYPE_NONE;
usbmem_state[target_device_id].buffer_len = 0;
usbmem_state[target_device_id].buffer_index = 0;
usbmem_state[target_device_id].buffer_frame = 0;
memset(
usbmem_state[target_device_id].filename,
0,
sizeof(usbmem_state[target_device_id].filename));
if (!path_exists(temp)) {
log_warning("Couldn't find file %s", temp);
str_cpy(
usbmem_response, sizeof(usbmem_response), "fail");
usbmem_state[target_device_id].errored = true;
} else {
bool loaded = file_load(
temp,
(void **) &usbmem_state[target_device_id].buffer,
&usbmem_state[target_device_id].buffer_len,
false);
if (loaded) {
log_misc("Reading file %s", temp);
usbmem_state[target_device_id].file_type =
USBMEM_FILE_TYPE_READ;
str_cpy(
usbmem_state[target_device_id].filename,
sizeof(usbmem_state[target_device_id].filename),
filename);
str_cpy(
usbmem_response,
sizeof(usbmem_response),
"start");
} else {
log_warning("Couldn't read file %s", temp);
str_cpy(
usbmem_response,
sizeof(usbmem_response),
"fail");
usbmem_state[target_device_id].errored = true;
}
}
}
} else if (
strncmp(request, "wra ", 4) == 0 ||
strncmp(request, "wrb ", 4) == 0) {
// Open file for writing
usbmem_reset_file_state(target_device_id);
str_cpy(
usbmem_response, sizeof(usbmem_response), "not supported");
} else if (
strncmp(request, "wha ", 4) == 0 ||
strncmp(request, "whb ", 4) == 0) {
// Something relating to writing?
str_cpy(
usbmem_response, sizeof(usbmem_response), "not supported");
} else if (
strncmp(request, "lma ", 4) == 0 ||
strncmp(request, "lmb ", 4) == 0) {
// What is "lm"?
str_cpy(usbmem_response, sizeof(usbmem_response), "done");
} else {
str_cpy(usbmem_response, sizeof(usbmem_response), "fail");
}
} else {
str_cpy(usbmem_response, sizeof(usbmem_response), "fail");
}
str_cat(usbmem_response, sizeof(usbmem_response), "\r>");
usbmem_response_length = strlen(usbmem_response);
}
usbmem_pending = true;
src->pos = nbytes;
return S_OK;
}
static HRESULT usbmem_read(struct irp *irp)
{
struct iobuf *dest;
log_assert(irp != NULL);
log_assert(irp->read.bytes != NULL);
dest = &irp->read;
if (usbmem_pending && usbmem_response_length != USBMEM_DATA_BUF_SIZE) {
log_misc("%s", usbmem_response);
}
if (usbmem_pending && usbmem_response_length == 0) {
str_cpy(usbmem_response, sizeof(usbmem_response), "\r>");
usbmem_response_length = strlen(usbmem_response);
}
dest->pos = usbmem_response_length;
memcpy(dest->bytes, usbmem_response, usbmem_response_length);
usbmem_pending = false;
usbmem_response_length = 0;
memset(usbmem_response, 0, sizeof(usbmem_response));
return S_OK;
}
static HRESULT usbmem_ioctl(struct irp *irp)
{
SERIAL_STATUS *status;
log_assert(irp != NULL);
switch (irp->ioctl) {
case IOCTL_SERIAL_GET_COMMSTATUS:
if (irp->read.bytes == NULL) {
log_warning(
"IOCTL_SERIAL_GET_COMMSTATUS: Output buffer is NULL");
return E_INVALIDARG;
}
if (irp->read.nbytes < sizeof(*status)) {
log_warning("IOCTL_SERIAL_GET_COMMSTATUS: Buffer is too small");
return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
}
status = (SERIAL_STATUS *) irp->read.bytes;
status->Errors = 0;
status->AmountInInQueue = usbmem_pending ? 1 : 0;
irp->read.pos = sizeof(*status);
break;
default:
break;
}
return S_OK;
}