From 3d4df4cd64d96e7fc40c247425808e2b687908bf Mon Sep 17 00:00:00 2001 From: Will Xyen Date: Sun, 18 Oct 2020 14:46:34 -0700 Subject: [PATCH] [memfile] Add support for faking files in memory --- dist/sdvx5/sdvxhook2.conf | 3 + src/main/hooklib/Module.mk | 1 + src/main/hooklib/memfile.c | 268 +++++++++++++++++++++++++++++++++ src/main/hooklib/memfile.h | 42 ++++++ src/main/sdvxhook2/config-io.c | 20 +++ src/main/sdvxhook2/config-io.h | 1 + src/main/sdvxhook2/dllmain.c | 31 +++- src/main/util/str.c | 9 ++ src/main/util/str.h | 1 + 9 files changed, 375 insertions(+), 1 deletion(-) create mode 100644 src/main/hooklib/memfile.c create mode 100644 src/main/hooklib/memfile.h diff --git a/dist/sdvx5/sdvxhook2.conf b/dist/sdvx5/sdvxhook2.conf index a982aa8..464628e 100644 --- a/dist/sdvx5/sdvxhook2.conf +++ b/dist/sdvx5/sdvxhook2.conf @@ -10,6 +10,9 @@ io.disable_poll_limiter=false # Forces game to think headphones are attached io.force_headphones=false +# Disables the built in file hooks, requiring manual file creation (/dev/raw/j.dest) +io.disable_file_hooks=false + # Run the game in a framed window (requires windowed option) gfx.framed=true diff --git a/src/main/hooklib/Module.mk b/src/main/hooklib/Module.mk index 261a31f..110726f 100644 --- a/src/main/hooklib/Module.mk +++ b/src/main/hooklib/Module.mk @@ -7,4 +7,5 @@ src_hooklib := \ config-adapter.c \ rs232.c \ setupapi.c \ + memfile.c \ diff --git a/src/main/hooklib/memfile.c b/src/main/hooklib/memfile.c new file mode 100644 index 0000000..c53d5ab --- /dev/null +++ b/src/main/hooklib/memfile.c @@ -0,0 +1,268 @@ +#include "hooklib/memfile.h" + +#include +#include + +#include "hook/hr.h" +#include "hook/iohook.h" +#include "hook/table.h" + +#include "util/array.h" +#include "util/log.h" +#include "util/str.h" + +struct file_entry { + char *path; + wchar_t *wpath; + HANDLE fd; + int64_t pos; + + const void *data; + uint32_t sz; + enum memfile_hook_path_mode path_mode; +}; + +static struct array hooked_files; +static CRITICAL_SECTION hooked_files_cs; + + +BOOL my_GetFileInformationByHandle( + HANDLE hFile, + LPBY_HANDLE_FILE_INFORMATION lpFileInformation +); + +BOOL (*real_GetFileInformationByHandle)( + HANDLE hFile, + LPBY_HANDLE_FILE_INFORMATION lpFileInformation +); + +static const struct hook_symbol memfile_hook_kernel32_syms[] = { + {.name = "GetFileInformationByHandle", + .patch = my_GetFileInformationByHandle, + .link = (void **) &real_GetFileInformationByHandle}, +}; + + +void memfile_hook_init(void) +{ + array_init(&hooked_files); + InitializeCriticalSection(&hooked_files_cs); + + hook_table_apply( + NULL, + "Kernel32.dll", + memfile_hook_kernel32_syms, + lengthof(memfile_hook_kernel32_syms)); + + log_info("Initialized"); +} + +void memfile_hook_fini(void) +{ + EnterCriticalSection(&hooked_files_cs); + struct file_entry *entry; + + for (size_t i = 0; i < hooked_files.nitems; i++) { + entry = array_item(struct file_entry, &hooked_files, i); + + free(entry->wpath); + free(entry->path); + + if (entry->fd != NULL) { + CloseHandle(entry->fd); + } + } + + array_fini(&hooked_files); + LeaveCriticalSection(&hooked_files_cs); + + DeleteCriticalSection(&hooked_files_cs); + + log_info("Finished"); +} + +void memfile_hook_add_fd( + const char *path, enum memfile_hook_path_mode path_mode, const void *data, uint32_t sz) +{ + log_assert(path != NULL); + + HRESULT hr; + struct file_entry *entry = NULL; + + EnterCriticalSection(&hooked_files_cs); + entry = array_append(struct file_entry, &hooked_files); + + entry->path = strdup(path); + entry->wpath = str_widen(path); + + hr = iohook_open_nul_fd(&entry->fd); + + if (hr != S_OK) { + LeaveCriticalSection(&hooked_files_cs); + log_fatal("Opening nul fd failed: %08lx", hr); + } + + entry->pos = 0; + + entry->data = data; + entry->sz = sz; + entry->path_mode = path_mode; + + LeaveCriticalSection(&hooked_files_cs); + + log_misc("memfile_hook_add_fd: path %s, mode %d, data size %d", path, path_mode, sz); +} + +static struct file_entry *memfile_hook_match_irp(const struct irp *irp) +{ + struct file_entry *entry; + log_assert(irp != NULL); + + if (irp->op == IRP_OP_OPEN) { + for (size_t i = 0; i < hooked_files.nitems; i++) { + entry = array_item(struct file_entry, &hooked_files, i); + + if (entry->path_mode == ABSOLUTE_MATCH) { + if (wstr_eq(entry->wpath, irp->open_filename)) { + log_misc("Memfile Open: Absolute matched: %s", entry->path); + return entry; + } + } else if (entry->path_mode == ENDING_MATCH) { + if (wstr_ends_with(irp->open_filename, entry->wpath)) { + log_misc("Memfile Open: Ending matched: %s", entry->path); + return entry; + } + } + } + } else { + for (size_t i = 0; i < hooked_files.nitems; i++) { + entry = array_item(struct file_entry, &hooked_files, i); + + if (entry->fd == irp->fd) { + return entry; + } + } + } + + return NULL; +} + +static HRESULT memfile_hook_irp_read(struct file_entry *entry, struct irp *irp) +{ + log_assert(entry != NULL); + log_assert(irp != NULL); + + size_t nread = min(entry->sz - entry->pos, irp->read.nbytes); + + if (nread > 0) { + memcpy(irp->read.bytes + irp->read.pos, (uint8_t*)entry->data + entry->pos, nread); + + entry->pos += nread; + irp->read.pos += nread; + } + + return S_OK; +} + +static HRESULT memfile_hook_irp_seek(struct file_entry *entry, struct irp *irp) +{ + log_assert(entry != NULL); + log_assert(irp != NULL); + + switch (irp->seek_origin) { + case FILE_BEGIN: + entry->pos = irp->seek_offset; + break; + case FILE_CURRENT: + entry->pos += irp->seek_offset; + break; + case FILE_END: + entry->pos = entry->sz + irp->seek_offset; + break; + default: + log_fatal("Invalid seek origin"); + } + + irp->seek_pos = entry->pos; + return S_OK; +} + +static HRESULT +memfile_hook_irp_handler(struct file_entry *entry, struct irp *irp) +{ + log_assert(entry != NULL); + log_assert(irp != NULL); + + switch (irp->op) { + case IRP_OP_OPEN: + irp->fd = entry->fd; + entry->pos = 0; + return S_OK; + + case IRP_OP_CLOSE: + return S_OK; + + case IRP_OP_READ: + return memfile_hook_irp_read(entry, irp); + + case IRP_OP_WRITE: + log_warning("write attempted on memfile, unsupported"); + return S_OK; + + case IRP_OP_IOCTL: + log_warning("ioctl attempted on memfile, unsupported"); + return S_OK; + + case IRP_OP_FSYNC: + log_warning("fsync attempted on memfile, unsupported"); + return S_OK; + + case IRP_OP_SEEK: + return memfile_hook_irp_seek(entry, irp); + + default: + return E_NOTIMPL; + } +} + +HRESULT memfile_hook_dispatch_irp(struct irp *irp) +{ + log_assert(irp != NULL); + + HRESULT hr; + + EnterCriticalSection(&hooked_files_cs); + + struct file_entry *entry = memfile_hook_match_irp(irp); + + if (!entry) { + LeaveCriticalSection(&hooked_files_cs); + return iohook_invoke_next(irp); + } + + hr = memfile_hook_irp_handler(entry, irp); + + LeaveCriticalSection(&hooked_files_cs); + return hr; +} + +// this isn't a standard iohook IRP atm. +BOOL my_GetFileInformationByHandle( + HANDLE hFile, + LPBY_HANDLE_FILE_INFORMATION lpFileInformation +) +{ + struct file_entry *entry; + for (size_t i = 0; i < hooked_files.nitems; i++) { + entry = array_item(struct file_entry, &hooked_files, i); + + if (entry->fd == hFile) { + lpFileInformation->dwFileAttributes = FILE_ATTRIBUTE_READONLY; + lpFileInformation->nFileSizeHigh = 0; + lpFileInformation->nFileSizeLow = entry->sz; + return true; + } + } + + return real_GetFileInformationByHandle(hFile, lpFileInformation); +} diff --git a/src/main/hooklib/memfile.h b/src/main/hooklib/memfile.h new file mode 100644 index 0000000..98e0285 --- /dev/null +++ b/src/main/hooklib/memfile.h @@ -0,0 +1,42 @@ +#ifndef HOOKLIB_MEMFILE_H +#define HOOKLIB_MEMFILE_H + +#include +#include + +#include "hook/iohook.h" + +/** + * memfile allows you to stub read-only files in memory. + */ + +/** + * This hooks required functions and sets up the hook array. + * Please remember to install the irp below. + */ +void memfile_hook_init(void); + +void memfile_hook_fini(void); + +enum memfile_hook_path_mode { + ABSOLUTE_MATCH = 0x1, + ENDING_MATCH = 0x2, +}; + +/** + * Adds the specified path to the list of files to hook + * + * @param path path to hook + * @param path_mode path matching mode (1: abs, 2: ending) + * @param data file contents (buffer must exist until fini is called) + * @param sz size of file + */ +void memfile_hook_add_fd( + const char *path, enum memfile_hook_path_mode path_mode, const void *data, uint32_t sz); + +/** + * iohook dispatch function. Needs to be installed. + */ +HRESULT memfile_hook_dispatch_irp(struct irp *irp); + +#endif diff --git a/src/main/sdvxhook2/config-io.c b/src/main/sdvxhook2/config-io.c index 5e0a7ba..81be301 100644 --- a/src/main/sdvxhook2/config-io.c +++ b/src/main/sdvxhook2/config-io.c @@ -9,11 +9,13 @@ #define SDVXHOOK2_CONFIG_IO_DISABLE_BIO2_EMU_KEY "io.disable_bio2_emu" #define SDVXHOOK2_CONFIG_IO_DISABLE_POLL_LIMITER_KEY "io.disable_poll_limiter" #define SDVXHOOK2_CONFIG_IO_FORCE_HEADPHONES_KEY "io.force_headphones" +#define SDVXHOOK2_CONFIG_IO_DISABLE_FILE_HOOKS_KEY "io.disable_file_hooks" #define SDVXHOOK2_CONFIG_IO_DEFAULT_DISABLE_CARD_READER_EMU_VALUE false #define SDVXHOOK2_CONFIG_IO_DEFAULT_DISABLE_BIO2_EMU_VALUE false #define SDVXHOOK2_CONFIG_IO_DEFAULT_DISABLE_POLL_LIMITER_VALUE false #define SDVXHOOK2_CONFIG_IO_DEFAULT_FORCE_HEADPHONES_VALUE false +#define SDVXHOOK2_CONFIG_IO_DEFAULT_DISABLE_FILE_HOOKS_VALUE false void sdvxhook2_config_io_init(struct cconfig *config) { @@ -41,6 +43,12 @@ void sdvxhook2_config_io_init(struct cconfig *config) SDVXHOOK2_CONFIG_IO_FORCE_HEADPHONES_KEY, SDVXHOOK2_CONFIG_IO_DEFAULT_FORCE_HEADPHONES_VALUE, "Forces game to think headphones are attached"); + + cconfig_util_set_bool( + config, + SDVXHOOK2_CONFIG_IO_DISABLE_FILE_HOOKS_KEY, + SDVXHOOK2_CONFIG_IO_DEFAULT_DISABLE_FILE_HOOKS_VALUE, + "Disables the built in file hooks, requiring manual file creation (/dev/raw/j.dest)"); } void sdvxhook2_config_io_get( @@ -93,4 +101,16 @@ void sdvxhook2_config_io_get( SDVXHOOK2_CONFIG_IO_FORCE_HEADPHONES_KEY, SDVXHOOK2_CONFIG_IO_DEFAULT_FORCE_HEADPHONES_VALUE); } + + if (!cconfig_util_get_bool( + config, + SDVXHOOK2_CONFIG_IO_DISABLE_FILE_HOOKS_KEY, + &config_io->disable_file_hooks, + SDVXHOOK2_CONFIG_IO_DEFAULT_DISABLE_FILE_HOOKS_VALUE)) { + log_warning( + "Invalid value for key '%s' specified, fallback " + "to default '%d'", + SDVXHOOK2_CONFIG_IO_DISABLE_FILE_HOOKS_KEY, + SDVXHOOK2_CONFIG_IO_DEFAULT_DISABLE_FILE_HOOKS_VALUE); + } } diff --git a/src/main/sdvxhook2/config-io.h b/src/main/sdvxhook2/config-io.h index 6cbd721..670a3ae 100644 --- a/src/main/sdvxhook2/config-io.h +++ b/src/main/sdvxhook2/config-io.h @@ -10,6 +10,7 @@ struct sdvxhook2_config_io { bool disable_bio2_emu; bool disable_poll_limiter; bool force_headphones; + bool disable_file_hooks; }; void sdvxhook2_config_io_init(struct cconfig *config); diff --git a/src/main/sdvxhook2/dllmain.c b/src/main/sdvxhook2/dllmain.c index dbd6061..33b3f8b 100644 --- a/src/main/sdvxhook2/dllmain.c +++ b/src/main/sdvxhook2/dllmain.c @@ -14,6 +14,7 @@ #include "hooklib/adapter.h" #include "hooklib/app.h" #include "hooklib/config-adapter.h" +#include "hooklib/memfile.h" #include "hooklib/rs232.h" #include "bio2emu/emu.h" @@ -51,6 +52,21 @@ static struct bio2emu_port bio2_emu = { .dispatcher = bio2_emu_bi2a_dispatch_request, }; +static void attach_dest_fd_intercept(const char *sidcode) +{ + char region = sidcode[3]; + + if (region == 'X') { + region = 'J'; + } + + char target_file[8] = "\\x.dest"; + target_file[1] = tolower(region); + + // can only capture these by ending path due to /dev/raw being mountable + memfile_hook_add_fd(target_file, ENDING_MATCH, NULL, 0); +} + static bool my_dll_entry_init(char *sidcode, struct property_node *param) { struct cconfig *config; @@ -113,12 +129,21 @@ static bool my_dll_entry_init(char *sidcode, struct property_node *param) iohook_push_handler(ac_io_port_dispatch_irp); iohook_push_handler(bio2emu_port_dispatch_irp); + if (!config_io.disable_file_hooks) { + memfile_hook_init(); + iohook_push_handler(memfile_hook_dispatch_irp); + attach_dest_fd_intercept(sidcode); + } + rs232_hook_init(); rs232_hook_limit_hooks(); if (!config_io.disable_bio2_emu) { bio2emu_init(); - bio2_emu_bi2a_init(&bio2_emu, config_io.disable_poll_limiter, config_io.force_headphones); + bio2_emu_bi2a_init( + &bio2_emu, + config_io.disable_poll_limiter, + config_io.force_headphones); } if (!config_io.disable_card_reader_emu) { @@ -157,6 +182,10 @@ static bool my_dll_entry_main(void) sdvx_io_fini(); } + if (!config_io.disable_file_hooks) { + memfile_hook_fini(); + } + return result; } diff --git a/src/main/util/str.c b/src/main/util/str.c index 8c520d7..7455176 100644 --- a/src/main/util/str.c +++ b/src/main/util/str.c @@ -250,3 +250,12 @@ bool wstr_eq(const wchar_t *lhs, const wchar_t *rhs) return wcscmp(lhs, rhs) == 0; } } + +bool wstr_insensitive_eq(const wchar_t *lhs, const wchar_t *rhs) +{ + if (lhs == NULL || rhs == NULL) { + return lhs == rhs; + } else { + return _wcsicmp(lhs, rhs) == 0; + } +} diff --git a/src/main/util/str.h b/src/main/util/str.h index 49fbbd8..fbfc481 100644 --- a/src/main/util/str.h +++ b/src/main/util/str.h @@ -21,6 +21,7 @@ void wstr_cpy(wchar_t *dest, size_t dnchars, const wchar_t *src); wchar_t *wstr_dup(const wchar_t *src); bool wstr_ends_with(const wchar_t *haystack, const wchar_t *needle); bool wstr_eq(const wchar_t *lhs, const wchar_t *rhs); +bool wstr_insensitive_eq(const wchar_t *lhs, const wchar_t *rhs); size_t wstr_format(wchar_t *buf, size_t nchars, const wchar_t *fmt, ...); bool wstr_narrow(const wchar_t *src, char **dest); size_t