From 6b1f49455906d1bd7724f2ced3f6f241f64be56f Mon Sep 17 00:00:00 2001 From: kyoubate-haruka <46010460+kyoubate-haruka@users.noreply.github.com> Date: Mon, 25 May 2026 11:32:35 +0200 Subject: [PATCH 1/7] path: hook GetDiskFreeSpaceW for amGfetcher --- common/hooklib/path.c | 44 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/common/hooklib/path.c b/common/hooklib/path.c index f480c63..0480e77 100644 --- a/common/hooklib/path.c +++ b/common/hooklib/path.c @@ -200,6 +200,14 @@ static UINT WINAPI hook_GetDriveTypeW( LPCWSTR lpRootPathName ); +static BOOL WINAPI hook_GetDiskFreeSpaceW( + LPCWSTR lpRootPathName, + LPDWORD lpSectorsPerCluster, + LPDWORD lpBytesPerSector, + LPDWORD lpNumberOfFreeClusters, + LPDWORD lpTotalNumberOfClusters +); + /* Link pointers */ static BOOL (WINAPI *next_CreateDirectoryA)( @@ -395,6 +403,14 @@ static UINT (WINAPI *next_GetDriveTypeA)( LPCSTR lpRootPathName ); +static BOOL (WINAPI *next_GetDiskFreeSpaceW)( + LPCWSTR lpRootPathName, + LPDWORD lpSectorsPerCluster, + LPDWORD lpBytesPerSector, + LPDWORD lpNumberOfFreeClusters, + LPDWORD lpTotalNumberOfClusters +); + /* Hook table */ static const struct hook_symbol path_hook_syms[] = { @@ -538,6 +554,10 @@ static const struct hook_symbol path_hook_syms[] = { .name = "GetDriveTypeW", .patch = hook_GetDriveTypeW, .link = (void **) &next_GetDriveTypeW, + }, { + .name = "GetDiskFreeSpaceW", + .patch = hook_GetDiskFreeSpaceW, + .link = (void **) &next_GetDiskFreeSpaceW, } }; @@ -1720,6 +1740,30 @@ static UINT WINAPI hook_GetDriveTypeW( return result; } +static BOOL WINAPI hook_GetDiskFreeSpaceW( + LPCWSTR lpRootPathName, + LPDWORD lpSectorsPerCluster, + LPDWORD lpBytesPerSector, + LPDWORD lpNumberOfFreeClusters, + LPDWORD lpTotalNumberOfClusters +) { + wchar_t *trans; + UINT result; + BOOL ok; + + ok = path_transform_w(&trans, lpRootPathName); + + if (!ok) { + return FALSE; + } + + result = next_GetDiskFreeSpaceW(trans ? trans : lpRootPathName, lpSectorsPerCluster, lpBytesPerSector, lpNumberOfFreeClusters, lpTotalNumberOfClusters); + + free(trans); + + return result; +} + char** str_split_a(char* a_str, const char a_delim) { char** result = 0; size_t count = 0; From 31e5cdf76d4946017db33d1eb789e83d711be9df Mon Sep 17 00:00:00 2001 From: kyoubate-haruka <46010460+kyoubate-haruka@users.noreply.github.com> Date: Wed, 27 May 2026 23:23:35 +0200 Subject: [PATCH 2/7] reg: add various missing registry keys --- common/platform/misc.c | 59 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/common/platform/misc.c b/common/platform/misc.c index 9ede751..fd5721a 100644 --- a/common/platform/misc.c +++ b/common/platform/misc.c @@ -22,6 +22,9 @@ static HRESULT misc_read_cpu_temp_error(void *bytes, uint32_t *nbytes); static HRESULT misc_read_cpu_temp_warning(void *bytes, uint32_t *nbytes); static HRESULT misc_read_platform_id(void *bytes, uint32_t *nbytes); static HRESULT misc_read_platform_name(void *bytes, uint32_t *nbytes); +static HRESULT misc_read_main_nic(void *bytes, uint32_t *nbytes); +static HRESULT misc_read_extend_nic(void *bytes, uint32_t *nbytes); +static HRESULT misc_read_downloadui_done(void *bytes, uint32_t *nbytes); static const struct hook_symbol misc_syms[] = { { @@ -74,6 +77,26 @@ static const struct reg_hook_val misc_static_keys[] = { } }; +static const struct reg_hook_val misc_wireless_keys[] = { + { + .name = L"main_nic", + .read = misc_read_main_nic, + .type = REG_DWORD, + }, { + .name = L"extend_nic", + .read = misc_read_extend_nic, + .type = REG_DWORD, + } +}; + +static const struct reg_hook_val misc_downloadui_keys[] = { + { + .name = L"IsDone", + .read = misc_read_downloadui_done, + .type = REG_DWORD, + }, +}; + static wchar_t misc_platform_id[5]; HRESULT misc_hook_init(const struct misc_config *cfg, const char *platform_id) @@ -126,6 +149,27 @@ HRESULT misc_hook_init(const struct misc_config *cfg, const char *platform_id) _countof(misc_master_keys)); } + if (FAILED(hr)) { + return hr; + } + + hr = reg_hook_push_key( + HKEY_LOCAL_MACHINE, + L"SYSTEM\\SEGA\\SystemProperty\\wirelessNetwork", + misc_wireless_keys, + _countof(misc_wireless_keys)); + + if (FAILED(hr)) { + return hr; + } + + hr = reg_hook_push_key( + HKEY_LOCAL_MACHINE, + L"SYSTEM\\SEGA\\SystemProperty\\downloadui", + misc_downloadui_keys, + _countof(misc_downloadui_keys)); + + if (FAILED(hr)) { return hr; } @@ -175,3 +219,18 @@ static HRESULT misc_read_platform_name(void *bytes, uint32_t *nbytes) { return reg_hook_read_wstr(bytes, nbytes, L"ALLS MX2.1"); // TODO: Dynamic } + +static HRESULT misc_read_main_nic(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_wstr(bytes, nbytes, L""); +} + +static HRESULT misc_read_extend_nic(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_wstr(bytes, nbytes, L""); +} + +static HRESULT misc_read_downloadui_done(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_u32(bytes, nbytes, 1); +} From 2a08e3f59f8b9dcab091c809e31dece9115e0272 Mon Sep 17 00:00:00 2001 From: kyoubate-haruka <46010460+kyoubate-haruka@users.noreply.github.com> Date: Wed, 27 May 2026 23:24:08 +0200 Subject: [PATCH 3/7] dns: forward AbaaS log server to configured server --- common/platform/dns.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/platform/dns.c b/common/platform/dns.c index c10e0d0..3ad0d0a 100644 --- a/common/platform/dns.c +++ b/common/platform/dns.c @@ -177,15 +177,15 @@ HRESULT dns_platform_hook_init(const struct dns_config *cfg) return hr; } - // Disable api/polling to the original servers + // ABaaS log server, receives system and error logs stored in appdata - hr = dns_hook_push(L"*.amlog.sys-all.net", NULL); + hr = dns_hook_push(L"*.amlog.sys-all.net", cfg->startup); if (FAILED(hr)) { return hr; } - hr = dns_hook_push(L"*.d-amlog.sys-all.net", NULL); + hr = dns_hook_push(L"*.d-amlog.sys-all.net", cfg->startup); if (FAILED(hr)) { return hr; From 268f252d3eaf6c5420e142facb2da66a26b20cfe Mon Sep 17 00:00:00 2001 From: kyoubate-haruka <46010460+kyoubate-haruka@users.noreply.github.com> Date: Thu, 28 May 2026 11:00:33 +0200 Subject: [PATCH 4/7] misc: add ability to write NextProcess to file --- common/platform/config.c | 7 ++++++ common/platform/misc.c | 51 ++++++++++++++++++++++++++++++++++++---- common/platform/misc.h | 1 + doc/config/common.md | 10 ++++++++ 4 files changed, 64 insertions(+), 5 deletions(-) diff --git a/common/platform/config.c b/common/platform/config.c index 62cf167..02faeb6 100644 --- a/common/platform/config.c +++ b/common/platform/config.c @@ -170,6 +170,13 @@ void misc_config_load(struct misc_config *cfg, const wchar_t *filename) cfg->enable = GetPrivateProfileIntW(L"misc", L"enable", 1, filename); cfg->allowMasterKeyWrite = GetPrivateProfileIntW(L"misc", L"allowMasterKeyWrite", 0, filename); cfg->allowReboot = GetPrivateProfileIntW(L"misc", L"allowReboot", 0, filename); + GetPrivateProfileStringW( + L"misc", + L"path", + L"DEVICE\\NextProcess.txt", + cfg->nextProcessFile, + _countof(cfg->nextProcessFile), + filename); } void netenv_config_load(struct netenv_config *cfg, const wchar_t *filename) diff --git a/common/platform/misc.c b/common/platform/misc.c index fd5721a..9d447fb 100644 --- a/common/platform/misc.c +++ b/common/platform/misc.c @@ -12,6 +12,8 @@ #include "platform/misc.h" +#include + #include "util/dprintf.h" static BOOL WINAPI misc_ExitWindowsEx(unsigned int flags, uint32_t reason); @@ -25,6 +27,8 @@ static HRESULT misc_read_platform_name(void *bytes, uint32_t *nbytes); static HRESULT misc_read_main_nic(void *bytes, uint32_t *nbytes); static HRESULT misc_read_extend_nic(void *bytes, uint32_t *nbytes); static HRESULT misc_read_downloadui_done(void *bytes, uint32_t *nbytes); +static HRESULT misc_read_next_process(void *bytes, uint32_t *nbytes); +static HRESULT misc_write_next_process(const void *bytes, uint32_t nbytes); static const struct hook_symbol misc_syms[] = { { @@ -47,11 +51,12 @@ static const struct reg_hook_val misc_master_keys[] = { .read = misc_read_app_loader_count, .type = REG_DWORD, }, { - /* Black-hole val, list it here so we don't get a warning msg */ .name = L"NextProcess", + .read = misc_read_next_process, + .write = misc_write_next_process, .type = REG_SZ, }, { - /* ditto */ + /* Black-hole val for reading, list it here so we don't get a warning msg */ .name = L"SystemError", .type = REG_SZ, } @@ -98,6 +103,7 @@ static const struct reg_hook_val misc_downloadui_keys[] = { }; static wchar_t misc_platform_id[5]; +static const struct misc_config *config; HRESULT misc_hook_init(const struct misc_config *cfg, const char *platform_id) { @@ -106,6 +112,8 @@ HRESULT misc_hook_init(const struct misc_config *cfg, const char *platform_id) assert(cfg != NULL); assert(platform_id != NULL && strlen(platform_id) == 4); + config = cfg; + if (!cfg->enable) { return S_FALSE; } @@ -147,10 +155,10 @@ HRESULT misc_hook_init(const struct misc_config *cfg, const char *platform_id) L"SYSTEM\\SEGA\\SystemProperty\\Master", misc_master_keys, _countof(misc_master_keys)); - } - if (FAILED(hr)) { - return hr; + if (FAILED(hr)) { + return hr; + } } hr = reg_hook_push_key( @@ -180,6 +188,10 @@ HRESULT misc_hook_init(const struct misc_config *cfg, const char *platform_id) hook_table_apply(NULL, "user32.dll", misc_syms, _countof(misc_syms)); } + if (PathFileExistsW(cfg->nextProcessFile)) { + DeleteFileW(cfg->nextProcessFile); + } + return S_OK; } @@ -234,3 +246,32 @@ static HRESULT misc_read_downloadui_done(void *bytes, uint32_t *nbytes) { return reg_hook_read_u32(bytes, nbytes, 1); } + +static HRESULT misc_read_next_process(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_wstr(bytes, nbytes, L""); +} + +static HRESULT misc_write_next_process(const void *bytes, uint32_t nbytes) +{ + HRESULT hr; + DWORD dwBytesWritten; + + wchar_t* nextProcess = malloc(nbytes); + memcpy(nextProcess, bytes, nbytes); + + dprintf("Misc: Next Process: %ls\n", nextProcess); + + HANDLE hFile = CreateFileW(config->nextProcessFile, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("Misc: Error opening %ls for writing: %x\n", config->nextProcessFile, (int) hr); + return hr; + } + + WriteFile(hFile, nextProcess, nbytes, &dwBytesWritten, NULL); + + CloseHandle(hFile); + + return S_OK; +} \ No newline at end of file diff --git a/common/platform/misc.h b/common/platform/misc.h index e9695bc..dfc2241 100644 --- a/common/platform/misc.h +++ b/common/platform/misc.h @@ -8,6 +8,7 @@ struct misc_config { bool enable; bool allowReboot; bool allowMasterKeyWrite; + wchar_t nextProcessFile[MAX_PATH]; }; HRESULT misc_hook_init(const struct misc_config *cfg, const char *platform_id); diff --git a/doc/config/common.md b/doc/config/common.md index 3956a65..94cd44f 100644 --- a/doc/config/common.md +++ b/doc/config/common.md @@ -761,3 +761,13 @@ Allows the game to write to specific registry keys relevant for the boot process Default: `0` Allows the game to reboot the computer. Only intended for owners of real hardware. + +### `nextProcessFile` + +Default: `DEVICE\NextProcess.txt` + +This is a file that will be set to the content of what would be written to the `NextProcess` registry key when the game is terminated. + +This allows whatever executed the game process to react what should happen next (System Test Mode selected, network delivery completed, ...) without requiring admin permissions. + +The file is deleted on startup of segatools. \ No newline at end of file From de82acf782e03aead33a1965fe4f7d1e5843137f Mon Sep 17 00:00:00 2001 From: kyoubate-haruka <46010460+kyoubate-haruka@users.noreply.github.com> Date: Fri, 29 May 2026 10:40:27 +0200 Subject: [PATCH 5/7] reg: allow opening keys multiple times --- common/hooklib/reg.c | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/common/hooklib/reg.c b/common/hooklib/reg.c index d4a3bc3..27a6ba0 100644 --- a/common/hooklib/reg.c +++ b/common/hooklib/reg.c @@ -12,12 +12,15 @@ #include "util/dprintf.h" #include "util/str.h" +#define MAX_CONCURRENT_REG_HANDLES 4 + struct reg_hook_key { HKEY root; const wchar_t *name; const struct reg_hook_val *vals; size_t nvals; - HKEY handle; + HKEY handles[MAX_CONCURRENT_REG_HANDLES]; + size_t nhandles; }; /* Helper functions */ @@ -344,7 +347,7 @@ static LRESULT reg_hook_propagate_hr(HRESULT hr) static struct reg_hook_key *reg_hook_match_key_locked(HKEY handle) { struct reg_hook_key *key; - size_t i; + size_t i, j; if (handle == NULL || handle == INVALID_HANDLE_VALUE) { return NULL; @@ -353,8 +356,10 @@ static struct reg_hook_key *reg_hook_match_key_locked(HKEY handle) for (i = 0 ; i < reg_hook_nkeys ; i++) { key = ®_hook_keys[i]; - if (key->handle == handle) { - return key; + for (j = 0; j < key->nhandles; j++) { + if (key->handles[j] == handle) { + return key; + } } } @@ -414,9 +419,9 @@ static LSTATUS reg_hook_open_locked( return ERROR_SUCCESS; } - /* Assume only one handle will be open at a time */ + /* Assume only MAX_CONCURRENT_REG_HANDLES handles will be open at a time */ - if (key->handle != NULL) { + if (key->nhandles > MAX_CONCURRENT_REG_HANDLES) { return ERROR_SHARING_VIOLATION; } @@ -433,7 +438,7 @@ static LSTATUS reg_hook_open_locked( out); if (err == ERROR_SUCCESS) { - key->handle = *out; + key->handles[key->nhandles++] = *out; } return err; @@ -511,16 +516,22 @@ static LSTATUS WINAPI hook_RegCreateKeyExW( static LSTATUS WINAPI hook_RegCloseKey(HKEY handle) { struct reg_hook_key *key; - size_t i; + size_t i, j, k; EnterCriticalSection(®_hook_lock); for (i = 0 ; i < reg_hook_nkeys ; i++) { key = ®_hook_keys[i]; - if (key->handle == handle) { - //dprintf("Registry: Closed virtual key %S\n", key->name); - key->handle = NULL; + for (j = 0; j < key->nhandles; j++) { + if (key->handles[j] == handle) { + for (k = j; k < MAX_CONCURRENT_REG_HANDLES - 1; k++) { + key->handles[k] = key->handles[k + 1]; + } + key->handles[MAX_CONCURRENT_REG_HANDLES - 1] = NULL; + key->nhandles--; + //dprintf("Registry: Closed virtual key %S\n", key->name); + } } } From ad7f6bc8004c5ed273b9843545e060a71db6cb45 Mon Sep 17 00:00:00 2001 From: kyoubate-haruka <46010460+kyoubate-haruka@users.noreply.github.com> Date: Sat, 30 May 2026 10:20:09 +0200 Subject: [PATCH 6/7] misc: rename NextProcess.txt file setting --- common/platform/config.c | 2 +- doc/config/common.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common/platform/config.c b/common/platform/config.c index 02faeb6..e90dd24 100644 --- a/common/platform/config.c +++ b/common/platform/config.c @@ -172,7 +172,7 @@ void misc_config_load(struct misc_config *cfg, const wchar_t *filename) cfg->allowReboot = GetPrivateProfileIntW(L"misc", L"allowReboot", 0, filename); GetPrivateProfileStringW( L"misc", - L"path", + L"nextProcessFilePath", L"DEVICE\\NextProcess.txt", cfg->nextProcessFile, _countof(cfg->nextProcessFile), diff --git a/doc/config/common.md b/doc/config/common.md index 94cd44f..d6a7651 100644 --- a/doc/config/common.md +++ b/doc/config/common.md @@ -762,7 +762,7 @@ Default: `0` Allows the game to reboot the computer. Only intended for owners of real hardware. -### `nextProcessFile` +### `nextProcessFilePath` Default: `DEVICE\NextProcess.txt` From ffe569297ef08f693bee3c6dd04c411d7fc683cd Mon Sep 17 00:00:00 2001 From: kyoubate-haruka <46010460+kyoubate-haruka@users.noreply.github.com> Date: Sat, 30 May 2026 11:30:31 +0200 Subject: [PATCH 7/7] vfs: add toggle to intentionally break download process --- common/platform/config.c | 1 + common/platform/vfs.c | 36 ++++++++++++++++++++++++++++++++++++ common/platform/vfs.h | 1 + doc/config/common.md | 6 ++++++ 4 files changed, 44 insertions(+) diff --git a/common/platform/config.c b/common/platform/config.c index e90dd24..dc64c83 100644 --- a/common/platform/config.c +++ b/common/platform/config.c @@ -348,6 +348,7 @@ void vfs_config_load(struct vfs_config *cfg, const wchar_t *filename) assert(filename != NULL); cfg->enable = GetPrivateProfileIntW(L"vfs", L"enable", 1, filename); + cfg->allowAmfsDownloads = GetPrivateProfileIntW(L"vfs", L"allowAmfsDownloads", 0, filename); GetPrivateProfileStringW( L"vfs", diff --git a/common/platform/vfs.c b/common/platform/vfs.c index ae5953e..1cfc5bd 100644 --- a/common/platform/vfs.c +++ b/common/platform/vfs.c @@ -41,6 +41,10 @@ static HRESULT vfs_custom_path_hook( const wchar_t *src, wchar_t *dest, size_t *count); +static HRESULT vfs_path_hook_tmp_icf( + const wchar_t *src, + wchar_t *dest, + size_t *count); static HRESULT vfs_reg_read_amfs(void *bytes, uint32_t *nbytes); static HRESULT vfs_reg_read_appdata(void *bytes, uint32_t *nbytes); @@ -77,6 +81,9 @@ static const size_t vfs_option_len = _countof(vfs_option) - 1; static const wchar_t vfs_apm3[] = L"C:\\Mount\\Apm"; static const size_t vfs_apm3_len = _countof(vfs_apm3) - 1; +static const wchar_t vfs_tmp_icf[] = L"E:\\tmpIcf.icf"; +static const size_t vfs_tmp_icf_len = _countof(vfs_tmp_icf) - 1; + static const struct reg_hook_val vfs_reg_vals[] = { { .name = L"AMFS", @@ -168,6 +175,12 @@ HRESULT vfs_hook_init(const struct vfs_config *config, const char* game_id) vfs_fixup_path(vfs_config.option, _countof(vfs_config.option), true); } + hr = path_hook_push(vfs_path_hook_tmp_icf); + + if (FAILED(hr)) { + return hr; + } + hr = vfs_mkdir_rec(vfs_config.amfs); if (FAILED(hr)) { @@ -601,6 +614,29 @@ static HRESULT vfs_path_hook_apm( return S_OK; } +// Block writing of E:\tmpIcf.icf to intentionally break the download process if the user has not enabled it +static HRESULT vfs_path_hook_tmp_icf( + const wchar_t *src, + wchar_t *dest, + size_t *count) +{ + assert(src != NULL); + assert(count != NULL); + + /* Case-insensitive check to see if src starts with vfs_tmp_icf */ + + if (path_compare_w(src, vfs_tmp_icf, vfs_tmp_icf_len) != 0) { + return S_FALSE; + } + + if (vfs_config.allowAmfsDownloads) { + return S_FALSE; + } + + dprintf("Vfs: AMFS downloads are blocked\n"); + return E_FAIL; +} + static HRESULT vfs_reg_read_amfs(void *bytes, uint32_t *nbytes) { return reg_hook_read_wstr(bytes, nbytes, L"E:\\"); diff --git a/common/platform/vfs.h b/common/platform/vfs.h index 9ff8111..87a2d3e 100644 --- a/common/platform/vfs.h +++ b/common/platform/vfs.h @@ -15,6 +15,7 @@ struct vfs_config { wchar_t redirections_from[MAX_REDIRECTIONS][MAX_PATH]; int redirections_from_len[MAX_REDIRECTIONS]; wchar_t redirections_to[MAX_REDIRECTIONS][MAX_PATH]; + bool allowAmfsDownloads; }; HRESULT vfs_hook_init(const struct vfs_config *config, const char* game_id); diff --git a/doc/config/common.md b/doc/config/common.md index d6a7651..2fc3176 100644 --- a/doc/config/common.md +++ b/doc/config/common.md @@ -685,6 +685,12 @@ redirection0from=\\.\COM5 redirection0to=\\.\COM10 ``` +### `allowAmfsDownloads` + +Default: `0` + +Allows network services to download arbitrary files to the AMFS directory specified above. This has security implications, do not enable this, unless you trust your server operator. + ## `[epay]` Configure Thinca Payment (E-Money) emulation and hooks.