From d5bde32df71c0dde3b16894e84a3706d57e5b1c4 Mon Sep 17 00:00:00 2001 From: kyoubate-haruka <46010460+kyoubate-haruka@users.noreply.github.com> Date: Wed, 24 Dec 2025 23:02:42 +0000 Subject: [PATCH] ewf: implement EWF virtualization (#84) (Yeah, I know the OS may cache disk reads, but that wasn't the concern) The ALLS series of cabs runs EWF to prevent anything from being written to C:\. This PR adds another layer to iohook to redirect specific open/read/write/close operations to a virtual file table to prevent disk activity, and thus simulate the presence of EWF. This was mainly aimed at ALPB's billing files. Seriously, look at this - especially the timestamps, Sega wtf are you doing. This does not affect other path hooks. It's turned off by default and will throw an error if the current executable resides on C:\. Tested with FGO and APMv3. ![22bf755754[1].png](/attachments/31771aee-2e4d-4e4f-85e3-c5eec63c6f78) Reviewed-on: https://gitea.tendokyu.moe/TeamTofuShop/segatools/pulls/84 Co-authored-by: kyoubate-haruka <46010460+kyoubate-haruka@users.noreply.github.com> Co-committed-by: kyoubate-haruka <46010460+kyoubate-haruka@users.noreply.github.com> --- common/platform/config.c | 9 + common/platform/config.h | 1 + common/platform/ewf.c | 558 ++++++++++++++++++++++++++++++++++++ common/platform/ewf.h | 41 +++ common/platform/meson.build | 3 + common/platform/platform.c | 6 + common/platform/platform.h | 2 + common/platform/vfs.c | 1 + doc/config/common.md | 28 ++ meson.build | 3 + meson_options.txt | 5 + 11 files changed, 657 insertions(+) create mode 100644 common/platform/ewf.c create mode 100644 common/platform/ewf.h diff --git a/common/platform/config.c b/common/platform/config.c index 7461430..4f948d0 100644 --- a/common/platform/config.c +++ b/common/platform/config.c @@ -44,6 +44,7 @@ void platform_config_load(struct platform_config *cfg, const wchar_t *filename) vfs_config_load(&cfg->vfs, filename); system_config_load(&cfg->system, filename); openssl_config_load(&cfg->openssl, filename); + ewf_config_load(&cfg->ewf, filename); } void amvideo_config_load(struct amvideo_config *cfg, const wchar_t *filename) @@ -374,3 +375,11 @@ void openssl_config_load(struct openssl_config *cfg, const wchar_t *filename) cfg->enable = GetPrivateProfileIntW(L"openssl", L"enable", 1, filename); cfg->override = GetPrivateProfileIntW(L"openssl", L"override", 0, filename); } + +void ewf_config_load(struct ewf_config* cfg, const wchar_t* filename) { + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"ewf", L"enable", 0, filename); + cfg->full = GetPrivateProfileIntW(L"ewf", L"full", 0, filename); +} diff --git a/common/platform/config.h b/common/platform/config.h index efad5a3..db6aad8 100644 --- a/common/platform/config.h +++ b/common/platform/config.h @@ -38,3 +38,4 @@ void pcbid_config_load(struct pcbid_config *cfg, const wchar_t *filename); void vfs_config_load(struct vfs_config *cfg, const wchar_t *filename); void system_config_load(struct system_config *cfg, const wchar_t *filename); void openssl_config_load(struct openssl_config *cfg, const wchar_t *filename); +void ewf_config_load(struct ewf_config *cfg, const wchar_t *filename); diff --git a/common/platform/ewf.c b/common/platform/ewf.c new file mode 100644 index 0000000..800e3cd --- /dev/null +++ b/common/platform/ewf.c @@ -0,0 +1,558 @@ +#include +#include + +#include +#include +#include +#include +#include + +#include "hook/iohook.h" +#include "hook/procaddr.h" + +#include "platform/ewf.h" + +#include + +#include "hooklib/path.h" +#include "util/dprintf.h" +#include "util/dump.h" + +/* EWF hook */ + +const static struct ewf_config* ewf_config; +static struct ewf_virtual_file virtual_file_table[EWF_MAX_VIRTUAL_FILES] = {0}; +static struct ewf_real_handle handle_table[EWF_MAX_HANDLES] = {0}; +static CRITICAL_SECTION file_table_lock; +static CRITICAL_SECTION handle_table_lock; +static wchar_t windows_directory[MAX_PATH]; + +static const wchar_t* default_drive = L"C:\\"; +static const wchar_t default_paths[][MAX_PATH] = { + L"alib.conf", + L"cacert.pem", + L"first_ar.conf", + L"last_pras.log", + L"last_shime.log", + L"play_history.csv" +}; + +/* Helper functions */ + +static HRESULT atow(const char* string, wchar_t** result) { + if (string == NULL) { + *result = NULL; + return S_FALSE; + } + const size_t n = strlen(string); + wchar_t* widestring = malloc((n + 1) * sizeof(wchar_t)); + if (widestring == NULL) { + return E_OUTOFMEMORY; + } + mbstowcs_s(NULL, widestring, n + 1, string, n); + + *result = widestring; + return S_OK; +} + +static inline bool wprefix(const wchar_t* pre, const wchar_t* str) { + return wcsncmp(pre, str, wcslen(pre)) == 0; +} + +static inline bool wsuffix(const wchar_t* suffix, const wchar_t* str) { + if (str == NULL || suffix == NULL) { + return false; + } + const size_t str_len = wcslen(str); + const size_t suf_len = wcslen(suffix); + if (suf_len > str_len) { + return false; + } + return wcsncmp(str + str_len - suf_len, suffix, suf_len) == 0; +} + + +static BOOL ewf_needs_virtualization(const wchar_t* path) { + if (path == NULL) { + return FALSE; + } + if (ewf_config->full) { + if (wprefix(default_drive, path) && !wprefix(windows_directory, path)) { + return TRUE; + } + } else { + for (int i = 0; i < _countof(default_paths); i++) { + if (wsuffix(default_paths[i], path)) { + return TRUE; + } + } + } + return FALSE; +} + +static struct ewf_virtual_file* ewf_get_virtual_file(HANDLE handle) { + struct ewf_virtual_file* ret = NULL; + EnterCriticalSection(&file_table_lock); + for (int i = 0; i < EWF_MAX_VIRTUAL_FILES; i++) { + if (virtual_file_table[i].virtual_handle == handle) { + ret = &virtual_file_table[i]; + break; + } + } + LeaveCriticalSection(&file_table_lock); + return ret; +} + +static struct ewf_virtual_file* ewf_find_virtual_file(const wchar_t* path) { + if (path == NULL) { + return NULL; + } + HANDLE ret = NULL; + EnterCriticalSection(&file_table_lock); + for (int i = 0; i < EWF_MAX_VIRTUAL_FILES; i++) { + if (virtual_file_table[i].virtual_handle != NULL && wcscmp(virtual_file_table[i].path, path) == 0) { + ret = &virtual_file_table[i]; + break; + } + } + LeaveCriticalSection(&file_table_lock); + return ret; +} + +static struct ewf_virtual_file* ewf_create_virtual_file(const wchar_t* path) { + assert(path != NULL); + struct ewf_virtual_file* ret = NULL; + EnterCriticalSection(&file_table_lock); + for (int i = 0; i < EWF_MAX_VIRTUAL_FILES; i++) { + if (virtual_file_table[i].virtual_handle == NULL) { + struct ewf_virtual_file* h = &virtual_file_table[i]; + HRESULT hr = iohook_open_nul_fd(&h->virtual_handle); + if (!SUCCEEDED(hr)) { + dprintf("EWF: Could not create handle: Failed to get NUL handle: %lx", hr); + break; + } + dprintf("EWF: Created virtual file: %ls\n", path); + wcscpy_s(h->path, MAX_PATH, path); + h->length = 0; + h->data = NULL; + ret = h; + break; + } + } + if (ret == NULL) { + dprintf("EWF: Could not create handle: Too many virtualized files\n"); + } + LeaveCriticalSection(&file_table_lock); + return ret; +} + +static BOOL ewf_delete_virtual_file(HANDLE virtual_handle) { + if (virtual_handle == NULL) { + return TRUE; + } + struct ewf_virtual_file* match = NULL; + EnterCriticalSection(&file_table_lock); + for (int i = 0; i < EWF_MAX_VIRTUAL_FILES; i++) { + if (virtual_file_table[i].virtual_handle == virtual_handle) { + match = &virtual_file_table[i]; + break; + } + } + LeaveCriticalSection(&file_table_lock); + if (match) { + match->virtual_handle = NULL; +#if LOG_EWF + dprintf("EWF: Deleted file: %ls\n", match->path); +#endif + return CloseHandle(virtual_handle); + } else { + SetLastError(ERROR_INVALID_HANDLE); + return FALSE; + } +} + +static struct ewf_real_handle* ewf_open_virtual_file(HANDLE virtual_handle) { + struct ewf_virtual_file* file = ewf_get_virtual_file(virtual_handle); + struct ewf_real_handle* h = NULL; + if (file == NULL) { + dprintf("EWF: open failed: invalid handle\n"); + return NULL; + } + EnterCriticalSection(&handle_table_lock); + for (int i = 0; i < EWF_MAX_HANDLES; i++) { + if (handle_table[i].real_handle == NULL) { + h = &handle_table[i]; + HRESULT hr = iohook_open_nul_fd(&h->real_handle); + if (!SUCCEEDED(hr)) { + dprintf("EWF: Could not create handle: Failed to get NUL handle: %lx", hr); + break; + } + h->virtual_file = file; + h->offset = 0; +#if LOG_EWF + dprintf("EWF: Virtual file opened: %ls\n", file->path); +#endif + break; + } + } + LeaveCriticalSection(&handle_table_lock); + if (h == NULL) { + dprintf("EWF: Could not create handle: Too many open files\n"); + } + return h; +} + +static struct ewf_real_handle* ewf_get_real_handle(HANDLE real_handle) { + if (real_handle == NULL) { + return NULL; + } + struct ewf_real_handle* match = NULL; + EnterCriticalSection(&handle_table_lock); + for (int i = 0; i < EWF_MAX_HANDLES; i++) { + if (handle_table[i].real_handle == real_handle) { + match = &handle_table[i]; + break; + } + } + LeaveCriticalSection(&handle_table_lock); + return match; +} + +static BOOL ewf_close_virtual_file(HANDLE real_handle) { + const struct ewf_virtual_file* match = NULL; + EnterCriticalSection(&handle_table_lock); + for (int i = 0; i < EWF_MAX_HANDLES; i++) { + if (handle_table[i].real_handle == real_handle) { + match = handle_table[i].virtual_file; + handle_table[i].real_handle = NULL; + break; + } + } + LeaveCriticalSection(&handle_table_lock); + if (match != NULL) { +#if LOG_EWF + dprintf("EWF: Virtual file closed: %ls\n", match->path); +#endif + return CloseHandle(real_handle); + } else { + SetLastError(ERROR_INVALID_HANDLE); + return FALSE; + } +} + +/* Hooks */ + +static int __cdecl hook_stat64i32( + const char* path, + struct _stat64i32* buffer +); + +static int (__cdecl *next_stat64i32)( + const char* path, + struct _stat64i32* buffer); + +static const struct hook_symbol ewf_hook_syms[] = { + { + .name = "__imp__stat64i32", + .patch = hook_stat64i32, + .link = (void **) &next_stat64i32, + }, + { + .name = "_stat64i32", + .patch = hook_stat64i32, + .link = (void **) &next_stat64i32, + } +}; + +/* EWF hook main functions */ + +static HRESULT ewf_handle_irp(struct irp* irp); + +static HRESULT ewf_handle_open(struct irp* irp); + +static HRESULT ewf_handle_close(const struct irp* irp, const struct ewf_real_handle* file); + +static HRESULT ewf_handle_read(struct irp* irp, struct ewf_real_handle* handle); + +static HRESULT ewf_handle_write(const struct irp* irp, const struct ewf_real_handle* file); + +void ewf_hook_insert_hooks(HMODULE target) { + hook_table_apply( + target, + "msvcr110d.dll", + ewf_hook_syms, + _countof(ewf_hook_syms)); + hook_table_apply( + target, + "msvcr110.dll", + ewf_hook_syms, + _countof(ewf_hook_syms)); +} + +HRESULT ewf_hook_init(const struct ewf_config* config) { + assert(config != NULL); + + ewf_config = config; + + if (!config->enable) { + return S_FALSE; + } + + GetWindowsDirectoryW(windows_directory, MAX_PATH); + + if (config->full) { + wchar_t executable_path[MAX_PATH]; + GetModuleFileNameW(NULL, executable_path, MAX_PATH); + if (wprefix(default_drive, executable_path)) { + dprintf("FATAL: EWF full virtualization cannot be enabled with the game executable being located on %ls\n", + default_drive); + return E_FAIL; + } + } + + dprintf("EWF: Virtualizing disk I/O to %s\n", + config->full ? "the entirety of the C:\\ drive" : "the ALPB billing directory"); + + InitializeCriticalSection(&handle_table_lock); + InitializeCriticalSection(&file_table_lock); + + ewf_hook_insert_hooks(NULL); + + return iohook_push_handler(ewf_handle_irp); +} + +static HRESULT ewf_handle_irp(struct irp* irp) { + assert(irp != NULL); + + struct ewf_real_handle* h = NULL; + + if (irp->op == IRP_OP_OPEN) { + if (!ewf_needs_virtualization(irp->open_filename)) { + return iohook_invoke_next(irp); + } + } else { + h = ewf_get_real_handle(irp->fd); + if (h == NULL) { + return iohook_invoke_next(irp); + } + } + + + switch (irp->op) { + case IRP_OP_OPEN: return ewf_handle_open(irp); + case IRP_OP_WRITE: return ewf_handle_write(irp, h); + case IRP_OP_READ: return ewf_handle_read(irp, h); + case IRP_OP_CLOSE: return ewf_handle_close(irp, h); + default: return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION); + } +} + +static HRESULT ewf_handle_open(struct irp* irp) { + assert(irp != NULL); + + if (irp->ovl != NULL) { + dprintf("EWF: async file operations not supported\n"); + return E_NOTIMPL; + } + + struct ewf_virtual_file* f = ewf_find_virtual_file(irp->open_filename); + + if (irp->open_creation == CREATE_NEW) { + if (f != NULL) { + return HRESULT_FROM_WIN32(ERROR_FILE_EXISTS); + } else { + f = ewf_create_virtual_file(irp->open_filename); + } + } else if (irp->open_creation == CREATE_ALWAYS) { + if (f != NULL) { + SetLastError(ERROR_ALREADY_EXISTS); +#if LOG_EWF + dprintf("EWF: File was truncated\n"); +#endif + f->length = 0; + free(f->data); + } else { + f = ewf_create_virtual_file(irp->open_filename); + } + } else if (irp->open_creation == OPEN_EXISTING) { + if (f == NULL) { + return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + } + } else if (irp->open_creation == OPEN_ALWAYS) { + if (f != NULL) { + SetLastError(ERROR_ALREADY_EXISTS); + } else { + f = ewf_create_virtual_file(irp->open_filename); + } + } else if (irp->open_creation == TRUNCATE_EXISTING) { + if (f != NULL) { +#if LOG_EWF + dprintf("EWF: File was truncated\n"); +#endif + f->length = 0; + free(f->data); + } else { + return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + } + } else { + return E_INVALIDARG; + } + + const struct ewf_real_handle* h = ewf_open_virtual_file(f->virtual_handle); + irp->fd = h->real_handle; + return S_OK; +} + +static HRESULT ewf_handle_close(const struct irp* irp, const struct ewf_real_handle* file) { + assert(irp != NULL); + assert(file != NULL); + + return ewf_close_virtual_file(file->real_handle) ? S_OK : E_FAIL; +} + +static HRESULT ewf_handle_read(struct irp* irp, struct ewf_real_handle* handle) { + assert(irp != NULL); + assert(handle != NULL); + + const struct ewf_virtual_file* f = handle->virtual_file; + size_t pos = handle->offset; + size_t to_read = irp->read.nbytes; + size_t max = f->length; + + if (pos > max || pos < 0) { + dprintf("EWF: Out-of-bounds read from %ls\n", f->path); + return HRESULT_FROM_WIN32(ERROR_HANDLE_EOF); + } + if (max == 0 || pos >= max || to_read == 0) { +#if LOG_EWF + dprintf("EWF: Zero read of %d at %d/%d from %ls\n", (int) to_read, (int) pos, (int) max, f->path); +#endif + irp->read.pos = 0; + return S_FALSE; + } + + if (pos + to_read > max) { + to_read = max - pos; + } + + memcpy(irp->read.bytes, f->data, to_read); + irp->read.pos = to_read; + handle->offset += to_read; + +#if LOG_EWF + dprintf("EWF: Read %d/%d bytes (offset %d, max %d) from %ls\n", (int) to_read, (int) irp->read.nbytes, (int) pos, + (int) f->length, f->path); + dump_iobuf(&irp->read); +#endif + + return S_OK; +} + +static HRESULT ewf_handle_write(const struct irp* irp, const struct ewf_real_handle* file) { + assert(irp != NULL); + assert(file != NULL); + + struct ewf_virtual_file* f = file->virtual_file; + const size_t n = irp->write.nbytes; + const void* data = irp->write.bytes; + + if (f->length == 0) { + const size_t initial_buf = max(n, EWF_DEFAULT_FILE_BUFFER_SIZE); + f->data = malloc(initial_buf); + if (f->data == NULL) { + return E_OUTOFMEMORY; + } + f->alloc_length = initial_buf; + } else if (f->length + n > f->alloc_length) { + void* ptr = realloc(f->data, f->alloc_length + n); + if (ptr == NULL) { + return E_OUTOFMEMORY; + } + f->data = ptr; + f->alloc_length = f->alloc_length + n; + } + + if (memcpy_s((char *) f->data + f->length, f->alloc_length - f->length, data, n) != 0) { + dprintf("EWF: Failed to copy %d bytes at offset %d (max %d)\n", (int) n, (int) f->length, + (int) f->alloc_length); + return E_NOT_SUFFICIENT_BUFFER; + } + f->length += n; + +#if LOG_EWF + dprintf("EWF: Write %d bytes to %ls\n", (int) n, f->path); + dump_const_iobuf(&irp->write); + dprintf("EWF: File content (%d, allocated %d)\n", (int) f->length, (int) f->alloc_length); + dump(f->data, f->length); +#endif + + return S_OK; +} + +// MSVC implementation detail. amdaemon depends on this succeeding, or you will be spammed with "cannot get accounting info" +static int __cdecl hook_stat64i32( + const char* path, + struct _stat64i32* buffer +) { + if (buffer == NULL || path == NULL) { + _set_errno(EINVAL); + return -1; + } + + wchar_t* wpath = NULL; + wchar_t* trans; + BOOL ok; + + HRESULT result = atow(path, &wpath); + if (!SUCCEEDED(result)) { + _set_errno(ENOMEM); + return -1; + } + + ok = path_transform_w(&trans, wpath); + + if (!ok) { + _set_errno(EINVAL); + return -1; + } + + wchar_t* target_path = trans ? trans : wpath; + + if (!ewf_needs_virtualization(target_path)) { + free(trans); + free(wpath); +#if LOG_EWF + dprintf("EWF: stat64i32: ignore: %s\n", path); +#endif + return next_stat64i32(path, buffer); + } + + const struct ewf_virtual_file* f = ewf_find_virtual_file(target_path); + if (f == NULL) { + free(trans); + free(wpath); + dprintf("EWF: stat64i32: File not found: %s\n", path); + _set_errno(ENOENT); + return -1; + } + + buffer->st_gid = 0; + buffer->st_atime = time(NULL); + buffer->st_ctime = time(NULL); + buffer->st_dev = 0; + buffer->st_ino = 0; + buffer->st_mode = _S_IFREG; + buffer->st_mtime = time(NULL); + buffer->st_nlink = 1; + buffer->st_rdev = 0; + buffer->st_size = (_off_t) f->length; + buffer->st_uid = 0; + +#if LOG_EWF + dprintf("EWF: stat64i32: file length of %s: %d\n", path, (int) f->length); +#endif + free(trans); + free(wpath); + + return 0; +} diff --git a/common/platform/ewf.h b/common/platform/ewf.h new file mode 100644 index 0000000..2d7fc63 --- /dev/null +++ b/common/platform/ewf.h @@ -0,0 +1,41 @@ +#pragma once + +#include + +#include +#include + +#define EWF_MAX_VIRTUAL_FILES 255 +#define EWF_MAX_HANDLES 50000 +#define EWF_DEFAULT_FILE_BUFFER_SIZE 1024 + +struct ewf_config { + bool enable; + bool full; +}; + +// A virtual file that we are storing in memory. +struct ewf_virtual_file { + // The handle that we use internally. This is NULL for a handle that doesn't exist and non-NULL for a handle that maps to a virtual file. + HANDLE virtual_handle; + // The path that is being virtualized (ex. C:\sample.txt). + wchar_t path[MAX_PATH]; + // The current length of the virtual file. + size_t length; + // The current length of the allocated data buffer. + size_t alloc_length; + // Pointer to some buffer holding file data. This is guaranteed to be at least length bytes in size. If length is zero, this may be NULL. + void* data; +}; + +// An open handle to a ewf_virtual_file. +struct ewf_real_handle { + // The "real" handle passed to the application as a result of CreateFile, etc. + HANDLE real_handle; + // The virtual file the real handle points to. + struct ewf_virtual_file* virtual_file; + // The current read offset. + size_t offset; +}; + +HRESULT ewf_hook_init(const struct ewf_config* config); diff --git a/common/platform/meson.build b/common/platform/meson.build index 0e51ddd..8f90208 100644 --- a/common/platform/meson.build +++ b/common/platform/meson.build @@ -4,6 +4,7 @@ platform_lib = static_library( implicit_include_directories : false, dependencies : [ capnhook.get_variable('hook_dep'), + capnhook.get_variable('hooklib_dep'), shlwapi_lib, bcrypt_lib, ], @@ -18,6 +19,8 @@ platform_lib = static_library( 'dns.h', 'epay.c', 'epay.h', + 'ewf.c', + 'ewf.h', 'hwmon.c', 'hwmon.h', 'hwreset.c', diff --git a/common/platform/platform.c b/common/platform/platform.c index d1f380d..2558809 100644 --- a/common/platform/platform.c +++ b/common/platform/platform.c @@ -101,5 +101,11 @@ HRESULT platform_hook_init( return hr; } + hr = ewf_hook_init(&cfg->ewf); + + if (FAILED(hr)) { + return hr; + } + return S_OK; } diff --git a/common/platform/platform.h b/common/platform/platform.h index dc8dfc2..0f01eaf 100644 --- a/common/platform/platform.h +++ b/common/platform/platform.h @@ -2,6 +2,7 @@ #include +#include "ewf.h" #include "platform/amvideo.h" #include "platform/clock.h" #include "platform/dns.h" @@ -30,6 +31,7 @@ struct platform_config { struct vfs_config vfs; struct system_config system; struct openssl_config openssl; + struct ewf_config ewf; }; HRESULT platform_hook_init( diff --git a/common/platform/vfs.c b/common/platform/vfs.c index 372add1..22f0583 100644 --- a/common/platform/vfs.c +++ b/common/platform/vfs.c @@ -14,6 +14,7 @@ #include "platform/vfs.h" +#include "ewf.h" #include "util/dprintf.h" static void vfs_fixup_path(wchar_t *path, size_t max_count); diff --git a/doc/config/common.md b/doc/config/common.md index 1be9b7c..3e9361e 100644 --- a/doc/config/common.md +++ b/doc/config/common.md @@ -694,3 +694,31 @@ Default: `0` Enables the override to always hook the OpenSSL env variable. By default the hook is only applied to Intel CPUs with the SHA extension present. + +## `[ewf]` + +Configure Enhanced Write Filter / Unified Write Filter emulation. + +### `enable` + +Default: `0` + +Enables the EWF/UWF hook. This will cause anything the game writes to the following paths to become virtualized to +reduce disk I/O: + +* C:\Users\AppUser\temp\alib.conf +* C:\Users\AppUser\temp\cacert.pem +* C:\Users\AppUser\temp\first_ar.conf +* C:\Users\AppUser\temp\last_pras.log +* C:\Users\AppUser\temp\last_shime.log +* C:\Users\AppUser\temp\play_history.csv +* and their equivalent Windows XP variants + +### `full` + +Default: `0` + +Enabling full virtualization will virtualize ALL writes to the C:\ drive. Using this setting is highly discouraged +unless you know what you're doing! + +The Windows directory is always excluded from virtualization. diff --git a/meson.build b/meson.build index 76e9577..b4376bb 100644 --- a/meson.build +++ b/meson.build @@ -85,6 +85,9 @@ endif if get_option('log_all') or get_option('log_vfs') add_project_arguments('-DLOG_VFS', language: 'c') endif +if get_option('log_all') or get_option('log_ewf') + add_project_arguments('-DLOG_EWF', language : 'c') +endif shlwapi_lib = cc.find_library('shlwapi') dinput8_lib = cc.find_library('dinput8') diff --git a/meson_options.txt b/meson_options.txt index b6989f4..7cce000 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -78,3 +78,8 @@ option('log_vfs', value : false, description : 'Enable debug logging for file system redirections' ) +option('log_ewf', + type : 'boolean', + value : false, + description : 'Enable debug logging for file system virtualization' +)