**THIS IS A HIGHLY WORK/DEVELOPMENT IN PROGRESS VERSION**
**THINGS ARE BROKEN AND EVERYTHING IS SUBJECT TO CHANGE**
First cut after massive refactoring with most critical and fundamental changes implemented.
We need to start somewhere, and this might be as good as anything else, so we can get started
with testing, bug fixing and iterating for the next releases.
The following list is non-exhaustive, does not guarantee anything does work, yet, and is supposed
to give a high level idea of what all of this is about. Updated documentation will reflect all of
this at some later point in time in more detail.
* A common "core" now abstracts logging, thread, property and configuration infrastructure and
provides a common interface. This is used by bemanitools internally as well as all tools, hooks
and APIs provided and don't depend on the game, version of the game or AVS version available
anymore
* New bemanitools (public) API
* Versioned API allowing for handling incremental API changes as well as breaking changes by
providing a new/different version when necessary
* Unified interfaces for bemanitools core API, i.e. logging, threads, configuration
* SDK with examples (TBD)
* Dogfooding approach: Bemanitools uses its own (public) API to implement and provide fundamental
features like configurable keyboard implementations for IO or hooks for different games and
versions
* All bemanitools hooks and IO libraries have been or are about to be re-worked to use the new APIs
* New hook API allows for more fine grained runtime control when stages of the hook are to be
executed, i.e. pre AVS, before main game, iat hooking instead of relying purely on DllMain
(which is still a compatible option though)
* launcher as a replacement for bootstrap: Bring it significantly closer to the original bootstrap
by supporting completely vanilla data and bootstrap.xml configurations to run the games. Note
that bemanitools does not include any code or means to run DRM'd data, only decrypted
* inject is also being reworked to use as much of the same "infrastructure" as launcher to provide
a more seamless bootstrapping process for games that keeps pre-eapki data as vanilla as possible
Summary:
Test Plan:
Summary:
Test Plan:
Summary:
Test Plan:
Summary:
Test Plan:
272 lines
6.5 KiB
C
272 lines
6.5 KiB
C
#include "hooklib/memfile.h"
|
|
|
|
#include <stdbool.h>
|
|
#include <windows.h>
|
|
|
|
#include "iface-core/log.h"
|
|
|
|
#include "hook/hr.h"
|
|
#include "hook/iohook.h"
|
|
#include "hook/table.h"
|
|
|
|
#include "util/array.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);
|
|
}
|