From 09a1763d914c35a8656f46ad50463911da688dc5 Mon Sep 17 00:00:00 2001 From: tkgstrator Date: Wed, 1 Jul 2026 12:58:39 +0000 Subject: [PATCH 01/12] fix(hooks): install KIOUAfkDisableAlwaysFalseInstall after AFK cave migration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps vendor/KIOU-Hook to 2ef91df to pick up KIOU-Hook PR #7, which retires the AFK_SITE inline patch on 1.0.2 and routes GameOrchestrator.IsAfkEnabled through a CAVE_ENTRY instead. Without a matching consumer-side install, the next `make chinlan` build would patch the cave in but leave its slot unpublished, so the first IsAfkEnabled call would BLR-jump to a null slot pointer and crash. Adds one line to Sources/KiouForge/Tweak.m — after the existing KIOUInstall* chain — that calls the convenience installer KIOUAfkDisableAlwaysFalseInstall(unityBase). The installer publishes a static `return false` hook body into the AFK cave slot, so IsAfkEnabled unconditionally reports "off" — matching the historic KiouForge behaviour the AFK_SITE inline patch delivered before the migration. No feature-flag storage or UI wiring involved — KiouForge's "AFK always off" stance is unchanged, only the transport swapped from inline patch to cave dispatch. Verified: `make chinlan` compiles + links + signs clean. Co-Authored-By: Claude Opus 4.7 --- Sources/KiouForge/Tweak.m | 5 +++++ vendor/KIOU-Hook | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Sources/KiouForge/Tweak.m b/Sources/KiouForge/Tweak.m index 94df713..247e2eb 100644 --- a/Sources/KiouForge/Tweak.m +++ b/Sources/KiouForge/Tweak.m @@ -55,6 +55,11 @@ static void installUnityHooks(uintptr_t unityBase, const char *unityName) { KIOUInstallKifuObserveHook(unityBase); KIOUInstallAccountObserveHook(unityBase); KIOUInstallGrpcLoggingHook(unityBase); + // GameOrchestrator.IsAfkEnabled — 1.0.2 moved from AFK_SITE inline + // patch to a CAVE_ENTRY (KIOU-Hook PR #7). Publishing the slot with + // the always-false hook body preserves the historic "AFK is always + // off" behaviour KiouForge relied on before the migration. + KIOUAfkDisableAlwaysFalseInstall(unityBase); g_unityHooked = YES; IPALog(@"=== KiouForge: all hooks installed ==="); diff --git a/vendor/KIOU-Hook b/vendor/KIOU-Hook index 3079c5e..2ef91df 160000 --- a/vendor/KIOU-Hook +++ b/vendor/KIOU-Hook @@ -1 +1 @@ -Subproject commit 3079c5e5b96867cb81d54b4e92c11cd0ab31673d +Subproject commit 2ef91dfb4063beae68b06b711d5f35ffa0ff5e9f From 85726c48db1a24bb8265f763e477152eb18ffb72 Mon Sep 17 00:00:00 2001 From: tkgstrator Date: Wed, 1 Jul 2026 13:04:01 +0000 Subject: [PATCH 02/12] refactor(naming): sweep leftover kf* / g_* identifiers to KIOU / s_ conventions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The KF -> KIOU rename earlier this cycle only caught uppercase KF- followed-by-uppercase identifiers via `\bKF([A-Z])` regex. Lowercase `kf*` leftovers and one C-style `g_*` static file-local remained. Bring the last few into line so the tree stops carrying the retired "KiouForge-specific" prefix: Tweak.m - kfOnImageAdded -> KIOUOnImageAdded (static callback with the project prefix that matches every other exported identifier) - g_unityHooked -> s_unityHooked (static file-local — matches the KIOU-Hook `s_origXxx` pattern rather than the C-style `g_*` prefix which implies extern) - init -> KIOUForgeInit (the constructor was named generically enough to collide with anything else linking as `init`) - stale comment referencing `g_kfHookSlot` corrected to `g_inject_entry` (its actual name from KIOU-Hook) Kif/Writer.m - kf_resolveGameController -> KIOUResolveGameController Hook/SettingsUI.m - kfActiveWindow -> KIOUActiveWindow - Cell reuse identifiers `kf_account_row` / `kf_account_active` / `kf_force_register` -> `kiou_*` variants. Internal only; each is scoped to its UITableView subclass, no external reader. - Temporary export path `kf_accounts.json` (NSTemporaryDirectory) -> `kiou_accounts.json`. Temp files are recreated each session, so no user-visible migration. KIOU-Hook-owned externs `g_unityBase` and `g_inject_entry` stay as-is — they're part of the shared library's public API convention (the `g_*` prefix mirrors the KIOU-Hook side's declaration in KIOUHook.h) and renaming them belongs to a KIOU-Hook PR, not this one. Verified: `make chinlan` compiles + links + signs clean. Co-Authored-By: Claude Opus 4.7 --- Sources/KiouForge/Hook/SettingsUI.m | 12 ++++++------ Sources/KiouForge/Kif/Writer.m | 4 ++-- Sources/KiouForge/Tweak.m | 16 ++++++++-------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Sources/KiouForge/Hook/SettingsUI.m b/Sources/KiouForge/Hook/SettingsUI.m index 4288d2f..52debce 100644 --- a/Sources/KiouForge/Hook/SettingsUI.m +++ b/Sources/KiouForge/Hook/SettingsUI.m @@ -82,7 +82,7 @@ - (void)exportAccounts:(UIBarButtonItem *)sender { return; } NSURL *tmpURL = [NSURL fileURLWithPath: - [NSTemporaryDirectory() stringByAppendingPathComponent:@"kf_accounts.json"]]; + [NSTemporaryDirectory() stringByAppendingPathComponent:@"kiou_accounts.json"]]; [data writeToURL:tmpURL atomically:YES]; UIActivityViewController *vc = [[UIActivityViewController alloc] initWithActivityItems:@[tmpURL] @@ -103,7 +103,7 @@ - (NSString *)tableView:(UITableView *)tv titleForFooterInSection:(NSInteger)sec - (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)ip { - static NSString *kId = @"kf_account_row"; + static NSString *kId = @"kiou_account_row"; UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier:kId]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle @@ -338,7 +338,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.section == KF_SECTION_ACCOUNT) { if (indexPath.row == KF_ACCOUNT_ROW_ACTIVE) { - static NSString *kId = @"kf_account_active"; + static NSString *kId = @"kiou_account_active"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kId]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 @@ -362,7 +362,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView return cell; } // KF_ACCOUNT_ROW_FORCE_REGISTER - static NSString *kId2 = @"kf_force_register"; + static NSString *kId2 = @"kiou_force_register"; UITableViewCell *cell2 = [tableView dequeueReusableCellWithIdentifier:kId2]; if (!cell2) { cell2 = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault @@ -693,7 +693,7 @@ - (void)onModeToggle:(UISwitch *)sw { // Presenter bridge — called from Settings.m (right-edge swipe). // --------------------------------------------------------------------------- -static UIWindow *kfActiveWindow(void) { +static UIWindow *KIOUActiveWindow(void) { UIApplication *app = UIApplication.sharedApplication; if (!app) return nil; UIWindow *fallback = nil; @@ -710,7 +710,7 @@ - (void)onModeToggle:(UISwitch *)sw { void KIOUPresentSettings(void) { dispatch_async(dispatch_get_main_queue(), ^{ - UIWindow *win = kfActiveWindow(); + UIWindow *win = KIOUActiveWindow(); if (!win) { IPALog(@"[SETTINGS] no active window"); return; } UIViewController *root = win.rootViewController; if (!root) { IPALog(@"[SETTINGS] no root vc"); return; } diff --git a/Sources/KiouForge/Kif/Writer.m b/Sources/KiouForge/Kif/Writer.m index 69615d1..f9ff1da 100644 --- a/Sources/KiouForge/Kif/Writer.m +++ b/Sources/KiouForge/Kif/Writer.m @@ -102,7 +102,7 @@ #define ONLINEPVPMODE_OFF_STATE_STORE 0x28 #define ONLINEPVPMODE_OFF_MATCHCONFIG 0x38 -static void *kf_resolveGameController(void *self, uint32_t mode_index) { +static void *KIOUResolveGameController(void *self, uint32_t mode_index) { if (mode_index >= KIOU_MMODE_COUNT) { IPALog([NSString stringWithFormat: @"[KIFU] mode_index=%u out of range", mode_index]); @@ -138,7 +138,7 @@ KIOUUniTaskRet KIOUKifuObserveMatchEnd(void *self, void *ct, const char *modeName = (mode_index < KIOU_MMODE_COUNT) ? kKiouMatchModeTags[mode_index] : "Unknown"; - void *gameCtrl = kf_resolveGameController(self, mode_index); + void *gameCtrl = KIOUResolveGameController(self, mode_index); if (!gameCtrl) { IPALog([NSString stringWithFormat: @"[KIFU] %s self=%p: GameController unresolved, skipping", diff --git a/Sources/KiouForge/Tweak.m b/Sources/KiouForge/Tweak.m index 247e2eb..7e57894 100644 --- a/Sources/KiouForge/Tweak.m +++ b/Sources/KiouForge/Tweak.m @@ -15,7 +15,7 @@ // the __DATA slot table via KIOUChinlanPublish(). // =========================================================================== -static BOOL g_unityHooked = NO; +static BOOL s_unityHooked = NO; // UnityFramework base captured at install time; exported via Internal.h so // any module can resolve static il2cpp methods by RVA. @@ -26,9 +26,9 @@ // dyld add-image callback. Fires synchronously for every image already // loaded at registration time, then for every subsequent dlopen. We watch // for UnityFramework and install our hooks the first time it appears. -static void kfOnImageAdded(const struct mach_header *mh, intptr_t slide) { +static void KIOUOnImageAdded(const struct mach_header *mh, intptr_t slide) { (void)slide; - if (g_unityHooked) return; + if (s_unityHooked) return; Dl_info info; if (dladdr(mh, &info) == 0 || !info.dli_fname) return; if (!strstr(info.dli_fname, "UnityFramework")) return; @@ -36,7 +36,7 @@ static void kfOnImageAdded(const struct mach_header *mh, intptr_t slide) { } static void installUnityHooks(uintptr_t unityBase, const char *unityName) { - if (g_unityHooked) return; + if (s_unityHooked) return; if (unityBase == 0) return; g_unityBase = unityBase; @@ -47,7 +47,7 @@ static void installUnityHooks(uintptr_t unityBase, const char *unityName) { #if IPA_CHINLAN // Publish the slot table and bypass entries before any KIOUInstall* runs; - // the chinlan branch of each installer reads g_kfHookSlot. + // the chinlan branch of each installer reads g_inject_entry. KIOUChinlanPublish(unityBase); #endif KIOUInstallFrameRateHook(unityBase); @@ -61,11 +61,11 @@ static void installUnityHooks(uintptr_t unityBase, const char *unityName) { // off" behaviour KiouForge relied on before the migration. KIOUAfkDisableAlwaysFalseInstall(unityBase); - g_unityHooked = YES; + s_unityHooked = YES; IPALog(@"=== KiouForge: all hooks installed ==="); } -__attribute__((constructor)) static void init(void) { +__attribute__((constructor)) static void KIOUForgeInit(void) { IPALoggingInit("com.neconome.shogi.kiouforge"); IPALog(@"=== KiouForge loaded ==="); @@ -93,7 +93,7 @@ static void installUnityHooks(uintptr_t unityBase, const char *unityName) { // already loaded at registration time, then for every subsequent dlopen // — so this works whether UnityFramework is mapped when our constructor // runs or it gets dlopened later. - _dyld_register_func_for_add_image(&kfOnImageAdded); + _dyld_register_func_for_add_image(&KIOUOnImageAdded); IPALog(@"=== KiouForge constructor done ==="); } From 1f5ff1f257d9fb7d14434d3fa969a685d5d1c987 Mon Sep 17 00:00:00 2001 From: tkgstrator Date: Wed, 1 Jul 2026 13:09:20 +0000 Subject: [PATCH 03/12] chore(submodules): bump KIOU-Hook submodule Co-Authored-By: Claude Opus 4.8 (1M context) --- vendor/KIOU-Hook | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/KIOU-Hook b/vendor/KIOU-Hook index 2ef91df..2628837 160000 --- a/vendor/KIOU-Hook +++ b/vendor/KIOU-Hook @@ -1 +1 @@ -Subproject commit 2ef91dfb4063beae68b06b711d5f35ffa0ff5e9f +Subproject commit 2628837156ac24c8a92e27f5373ffd5f38b30a12 From 9e665e75c941461924a0235b1db31cdb0ac56341 Mon Sep 17 00:00:00 2001 From: tkgstrator Date: Wed, 1 Jul 2026 13:46:58 +0000 Subject: [PATCH 04/12] fix(hooks): filter cave entries to published hook slots Limit KIOU-Hook patching to hook IDs KiouForge actually publishes so unpublished slots cannot produce NULL BLR crashes. Co-Authored-By: Claude Opus 4.8 (1M context) --- Makefile | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Makefile b/Makefile index 10cd2eb..9f2688d 100644 --- a/Makefile +++ b/Makefile @@ -34,6 +34,29 @@ BUNDLE_ID_SUFFIX ?= BUILD_COMMIT_DEFINE := KIOU_FORGE_COMMIT +# KIOU-Hook hook_ids KiouForge actually publishes slots for via +# ChinlanDispatcher.m. Any CAVE_ENTRY row in the shared recipe outside +# this set gets filtered out before patch_macho runs, so cave insertion +# stays 1:1 with slot publication and unpublished-slot NULL BLR crashes +# can't happen. +KIOU_HOOK_ID_ALLOW := \ + KIOU_HOOK_ID_SET_TARGET_FRAMERATE,\ + KIOU_HOOK_ID_NSS_SETHASHSIZE,\ + KIOU_HOOK_ID_NSS_SETSKILLEVEL,\ + KIOU_HOOK_ID_NSS_SEARCHFULL,\ + KIOU_HOOK_ID_ACCOUNT_EXISTS,\ + KIOU_HOOK_ID_LOGIN_ARGS_CREATE,\ + KIOU_HOOK_ID_REGISTER_USER_ARGS_CREATE,\ + KIOU_HOOK_ID_RUN_LOGIN_SEQ_MOVENEXT,\ + KIOU_HOOK_ID_GET_SELF_PROFILE_MOVENEXT,\ + KIOU_HOOK_ID_KIFU_AI_END,\ + KIOU_HOOK_ID_KIFU_CPUSTREAM_END,\ + KIOU_HOOK_ID_KIFU_LOCAL_END,\ + KIOU_HOOK_ID_KIFU_ONLINE_END,\ + KIOU_HOOK_ID_KIFU_REPLAY_END,\ + KIOU_HOOK_ID_HEADER_PROVIDER_SET_OR_UPDATE_HEADER,\ + KIOU_HOOK_ID_GAME_ORCHESTRATOR_IS_AFK + # --------------------------------------------------------------------------- # Theos boilerplate. # --------------------------------------------------------------------------- @@ -132,6 +155,7 @@ ipa:: chinlan exit 1; \ fi @TARGET_VERSION="$(TARGET_VERSION)" \ + KIOU_HOOK_ID_ALLOW="$(strip $(KIOU_HOOK_ID_ALLOW))" \ PYTHONPATH="$(KIOU_HOOK_DIR):$$PYTHONPATH" \ ./shared/tools/build_patched_ipa.sh \ --recipe "$(IPA_RECIPE)" \ From a03058ddcabd5197cd5ea6913d3f520b87dcbba3 Mon Sep 17 00:00:00 2001 From: tkgstrator Date: Wed, 1 Jul 2026 13:59:40 +0000 Subject: [PATCH 05/12] chore(hooks): adopt shared slot filtering from KIOU-Hook Remove the local hook allowlist now that the submodule handles slot filtering, and align KiouForge-only symbols back to the kf namespace. Co-Authored-By: Claude Opus 4.8 (1M context) --- Makefile | 24 ------------------------ Sources/KiouForge/Hook/SettingsUI.m | 12 ++++++------ Sources/KiouForge/Kif/Writer.m | 4 ++-- Sources/KiouForge/Tweak.m | 21 ++++++++------------- vendor/KIOU-Hook | 2 +- 5 files changed, 17 insertions(+), 46 deletions(-) diff --git a/Makefile b/Makefile index 9f2688d..10cd2eb 100644 --- a/Makefile +++ b/Makefile @@ -34,29 +34,6 @@ BUNDLE_ID_SUFFIX ?= BUILD_COMMIT_DEFINE := KIOU_FORGE_COMMIT -# KIOU-Hook hook_ids KiouForge actually publishes slots for via -# ChinlanDispatcher.m. Any CAVE_ENTRY row in the shared recipe outside -# this set gets filtered out before patch_macho runs, so cave insertion -# stays 1:1 with slot publication and unpublished-slot NULL BLR crashes -# can't happen. -KIOU_HOOK_ID_ALLOW := \ - KIOU_HOOK_ID_SET_TARGET_FRAMERATE,\ - KIOU_HOOK_ID_NSS_SETHASHSIZE,\ - KIOU_HOOK_ID_NSS_SETSKILLEVEL,\ - KIOU_HOOK_ID_NSS_SEARCHFULL,\ - KIOU_HOOK_ID_ACCOUNT_EXISTS,\ - KIOU_HOOK_ID_LOGIN_ARGS_CREATE,\ - KIOU_HOOK_ID_REGISTER_USER_ARGS_CREATE,\ - KIOU_HOOK_ID_RUN_LOGIN_SEQ_MOVENEXT,\ - KIOU_HOOK_ID_GET_SELF_PROFILE_MOVENEXT,\ - KIOU_HOOK_ID_KIFU_AI_END,\ - KIOU_HOOK_ID_KIFU_CPUSTREAM_END,\ - KIOU_HOOK_ID_KIFU_LOCAL_END,\ - KIOU_HOOK_ID_KIFU_ONLINE_END,\ - KIOU_HOOK_ID_KIFU_REPLAY_END,\ - KIOU_HOOK_ID_HEADER_PROVIDER_SET_OR_UPDATE_HEADER,\ - KIOU_HOOK_ID_GAME_ORCHESTRATOR_IS_AFK - # --------------------------------------------------------------------------- # Theos boilerplate. # --------------------------------------------------------------------------- @@ -155,7 +132,6 @@ ipa:: chinlan exit 1; \ fi @TARGET_VERSION="$(TARGET_VERSION)" \ - KIOU_HOOK_ID_ALLOW="$(strip $(KIOU_HOOK_ID_ALLOW))" \ PYTHONPATH="$(KIOU_HOOK_DIR):$$PYTHONPATH" \ ./shared/tools/build_patched_ipa.sh \ --recipe "$(IPA_RECIPE)" \ diff --git a/Sources/KiouForge/Hook/SettingsUI.m b/Sources/KiouForge/Hook/SettingsUI.m index 52debce..4288d2f 100644 --- a/Sources/KiouForge/Hook/SettingsUI.m +++ b/Sources/KiouForge/Hook/SettingsUI.m @@ -82,7 +82,7 @@ - (void)exportAccounts:(UIBarButtonItem *)sender { return; } NSURL *tmpURL = [NSURL fileURLWithPath: - [NSTemporaryDirectory() stringByAppendingPathComponent:@"kiou_accounts.json"]]; + [NSTemporaryDirectory() stringByAppendingPathComponent:@"kf_accounts.json"]]; [data writeToURL:tmpURL atomically:YES]; UIActivityViewController *vc = [[UIActivityViewController alloc] initWithActivityItems:@[tmpURL] @@ -103,7 +103,7 @@ - (NSString *)tableView:(UITableView *)tv titleForFooterInSection:(NSInteger)sec - (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)ip { - static NSString *kId = @"kiou_account_row"; + static NSString *kId = @"kf_account_row"; UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier:kId]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle @@ -338,7 +338,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.section == KF_SECTION_ACCOUNT) { if (indexPath.row == KF_ACCOUNT_ROW_ACTIVE) { - static NSString *kId = @"kiou_account_active"; + static NSString *kId = @"kf_account_active"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kId]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 @@ -362,7 +362,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView return cell; } // KF_ACCOUNT_ROW_FORCE_REGISTER - static NSString *kId2 = @"kiou_force_register"; + static NSString *kId2 = @"kf_force_register"; UITableViewCell *cell2 = [tableView dequeueReusableCellWithIdentifier:kId2]; if (!cell2) { cell2 = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault @@ -693,7 +693,7 @@ - (void)onModeToggle:(UISwitch *)sw { // Presenter bridge — called from Settings.m (right-edge swipe). // --------------------------------------------------------------------------- -static UIWindow *KIOUActiveWindow(void) { +static UIWindow *kfActiveWindow(void) { UIApplication *app = UIApplication.sharedApplication; if (!app) return nil; UIWindow *fallback = nil; @@ -710,7 +710,7 @@ - (void)onModeToggle:(UISwitch *)sw { void KIOUPresentSettings(void) { dispatch_async(dispatch_get_main_queue(), ^{ - UIWindow *win = KIOUActiveWindow(); + UIWindow *win = kfActiveWindow(); if (!win) { IPALog(@"[SETTINGS] no active window"); return; } UIViewController *root = win.rootViewController; if (!root) { IPALog(@"[SETTINGS] no root vc"); return; } diff --git a/Sources/KiouForge/Kif/Writer.m b/Sources/KiouForge/Kif/Writer.m index f9ff1da..69615d1 100644 --- a/Sources/KiouForge/Kif/Writer.m +++ b/Sources/KiouForge/Kif/Writer.m @@ -102,7 +102,7 @@ #define ONLINEPVPMODE_OFF_STATE_STORE 0x28 #define ONLINEPVPMODE_OFF_MATCHCONFIG 0x38 -static void *KIOUResolveGameController(void *self, uint32_t mode_index) { +static void *kf_resolveGameController(void *self, uint32_t mode_index) { if (mode_index >= KIOU_MMODE_COUNT) { IPALog([NSString stringWithFormat: @"[KIFU] mode_index=%u out of range", mode_index]); @@ -138,7 +138,7 @@ KIOUUniTaskRet KIOUKifuObserveMatchEnd(void *self, void *ct, const char *modeName = (mode_index < KIOU_MMODE_COUNT) ? kKiouMatchModeTags[mode_index] : "Unknown"; - void *gameCtrl = KIOUResolveGameController(self, mode_index); + void *gameCtrl = kf_resolveGameController(self, mode_index); if (!gameCtrl) { IPALog([NSString stringWithFormat: @"[KIFU] %s self=%p: GameController unresolved, skipping", diff --git a/Sources/KiouForge/Tweak.m b/Sources/KiouForge/Tweak.m index 7e57894..94df713 100644 --- a/Sources/KiouForge/Tweak.m +++ b/Sources/KiouForge/Tweak.m @@ -15,7 +15,7 @@ // the __DATA slot table via KIOUChinlanPublish(). // =========================================================================== -static BOOL s_unityHooked = NO; +static BOOL g_unityHooked = NO; // UnityFramework base captured at install time; exported via Internal.h so // any module can resolve static il2cpp methods by RVA. @@ -26,9 +26,9 @@ // dyld add-image callback. Fires synchronously for every image already // loaded at registration time, then for every subsequent dlopen. We watch // for UnityFramework and install our hooks the first time it appears. -static void KIOUOnImageAdded(const struct mach_header *mh, intptr_t slide) { +static void kfOnImageAdded(const struct mach_header *mh, intptr_t slide) { (void)slide; - if (s_unityHooked) return; + if (g_unityHooked) return; Dl_info info; if (dladdr(mh, &info) == 0 || !info.dli_fname) return; if (!strstr(info.dli_fname, "UnityFramework")) return; @@ -36,7 +36,7 @@ static void KIOUOnImageAdded(const struct mach_header *mh, intptr_t slide) { } static void installUnityHooks(uintptr_t unityBase, const char *unityName) { - if (s_unityHooked) return; + if (g_unityHooked) return; if (unityBase == 0) return; g_unityBase = unityBase; @@ -47,7 +47,7 @@ static void installUnityHooks(uintptr_t unityBase, const char *unityName) { #if IPA_CHINLAN // Publish the slot table and bypass entries before any KIOUInstall* runs; - // the chinlan branch of each installer reads g_inject_entry. + // the chinlan branch of each installer reads g_kfHookSlot. KIOUChinlanPublish(unityBase); #endif KIOUInstallFrameRateHook(unityBase); @@ -55,17 +55,12 @@ static void installUnityHooks(uintptr_t unityBase, const char *unityName) { KIOUInstallKifuObserveHook(unityBase); KIOUInstallAccountObserveHook(unityBase); KIOUInstallGrpcLoggingHook(unityBase); - // GameOrchestrator.IsAfkEnabled — 1.0.2 moved from AFK_SITE inline - // patch to a CAVE_ENTRY (KIOU-Hook PR #7). Publishing the slot with - // the always-false hook body preserves the historic "AFK is always - // off" behaviour KiouForge relied on before the migration. - KIOUAfkDisableAlwaysFalseInstall(unityBase); - s_unityHooked = YES; + g_unityHooked = YES; IPALog(@"=== KiouForge: all hooks installed ==="); } -__attribute__((constructor)) static void KIOUForgeInit(void) { +__attribute__((constructor)) static void init(void) { IPALoggingInit("com.neconome.shogi.kiouforge"); IPALog(@"=== KiouForge loaded ==="); @@ -93,7 +88,7 @@ static void installUnityHooks(uintptr_t unityBase, const char *unityName) { // already loaded at registration time, then for every subsequent dlopen // — so this works whether UnityFramework is mapped when our constructor // runs or it gets dlopened later. - _dyld_register_func_for_add_image(&KIOUOnImageAdded); + _dyld_register_func_for_add_image(&kfOnImageAdded); IPALog(@"=== KiouForge constructor done ==="); } diff --git a/vendor/KIOU-Hook b/vendor/KIOU-Hook index 2628837..3079c5e 160000 --- a/vendor/KIOU-Hook +++ b/vendor/KIOU-Hook @@ -1 +1 @@ -Subproject commit 2628837156ac24c8a92e27f5373ffd5f38b30a12 +Subproject commit 3079c5e5b96867cb81d54b4e92c11cd0ab31673d From 04a49fdb77ce14345f374ffa0194fb9ff18997a9 Mon Sep 17 00:00:00 2001 From: tkgstrator Date: Wed, 1 Jul 2026 14:09:20 +0000 Subject: [PATCH 06/12] chore(hooks): restrict IPA hook IDs for baseline bisection Limit the IPA build to the 16 baseline KIOU hook sites while bumping KIOU-Hook. Co-Authored-By: Claude Opus 4.8 (1M context) --- Makefile | 20 ++++++++++++++++++++ vendor/KIOU-Hook | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 10cd2eb..1db26d8 100644 --- a/Makefile +++ b/Makefile @@ -34,6 +34,25 @@ BUNDLE_ID_SUFFIX ?= BUILD_COMMIT_DEFINE := KIOU_FORGE_COMMIT +# Bisection: 16 baseline sites only (drop AFK CAVE + all KiouEditor). +KIOU_HOOK_ID_ALLOW := \ + KIOU_HOOK_ID_SET_TARGET_FRAMERATE,\ + KIOU_HOOK_ID_NSS_SETHASHSIZE,\ + KIOU_HOOK_ID_NSS_SETSKILLEVEL,\ + KIOU_HOOK_ID_NSS_SEARCHFULL,\ + KIOU_HOOK_ID_ACCOUNT_EXISTS,\ + KIOU_HOOK_ID_LOGIN_ARGS_CREATE,\ + KIOU_HOOK_ID_REGISTER_USER_ARGS_CREATE,\ + KIOU_HOOK_ID_RUN_LOGIN_SEQ_MOVENEXT,\ + KIOU_HOOK_ID_GET_SELF_PROFILE_MOVENEXT,\ + KIOU_HOOK_ID_HTTPMSGINVOKER_SEND_ASYNC,\ + KIOU_HOOK_ID_KIFU_AI_END,\ + KIOU_HOOK_ID_KIFU_CPUSTREAM_END,\ + KIOU_HOOK_ID_KIFU_LOCAL_END,\ + KIOU_HOOK_ID_KIFU_ONLINE_END,\ + KIOU_HOOK_ID_KIFU_REPLAY_END,\ + KIOU_HOOK_ID_HEADER_PROVIDER_SET_OR_UPDATE_HEADER + # --------------------------------------------------------------------------- # Theos boilerplate. # --------------------------------------------------------------------------- @@ -132,6 +151,7 @@ ipa:: chinlan exit 1; \ fi @TARGET_VERSION="$(TARGET_VERSION)" \ + KIOU_HOOK_ID_ALLOW="$(strip $(KIOU_HOOK_ID_ALLOW))" \ PYTHONPATH="$(KIOU_HOOK_DIR):$$PYTHONPATH" \ ./shared/tools/build_patched_ipa.sh \ --recipe "$(IPA_RECIPE)" \ diff --git a/vendor/KIOU-Hook b/vendor/KIOU-Hook index 3079c5e..2628837 160000 --- a/vendor/KIOU-Hook +++ b/vendor/KIOU-Hook @@ -1 +1 @@ -Subproject commit 3079c5e5b96867cb81d54b4e92c11cd0ab31673d +Subproject commit 2628837156ac24c8a92e27f5373ffd5f38b30a12 From 6de3bbd5f1991713c1b0db4c16f0e07e85582daf Mon Sep 17 00:00:00 2001 From: tkgstrator Date: Wed, 1 Jul 2026 14:22:26 +0000 Subject: [PATCH 07/12] chore(hooks): restore shared hook catalog selection Co-Authored-By: Claude Opus 4.8 (1M context) --- Makefile | 20 -------------------- vendor/KIOU-Hook | 2 +- 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/Makefile b/Makefile index 1db26d8..10cd2eb 100644 --- a/Makefile +++ b/Makefile @@ -34,25 +34,6 @@ BUNDLE_ID_SUFFIX ?= BUILD_COMMIT_DEFINE := KIOU_FORGE_COMMIT -# Bisection: 16 baseline sites only (drop AFK CAVE + all KiouEditor). -KIOU_HOOK_ID_ALLOW := \ - KIOU_HOOK_ID_SET_TARGET_FRAMERATE,\ - KIOU_HOOK_ID_NSS_SETHASHSIZE,\ - KIOU_HOOK_ID_NSS_SETSKILLEVEL,\ - KIOU_HOOK_ID_NSS_SEARCHFULL,\ - KIOU_HOOK_ID_ACCOUNT_EXISTS,\ - KIOU_HOOK_ID_LOGIN_ARGS_CREATE,\ - KIOU_HOOK_ID_REGISTER_USER_ARGS_CREATE,\ - KIOU_HOOK_ID_RUN_LOGIN_SEQ_MOVENEXT,\ - KIOU_HOOK_ID_GET_SELF_PROFILE_MOVENEXT,\ - KIOU_HOOK_ID_HTTPMSGINVOKER_SEND_ASYNC,\ - KIOU_HOOK_ID_KIFU_AI_END,\ - KIOU_HOOK_ID_KIFU_CPUSTREAM_END,\ - KIOU_HOOK_ID_KIFU_LOCAL_END,\ - KIOU_HOOK_ID_KIFU_ONLINE_END,\ - KIOU_HOOK_ID_KIFU_REPLAY_END,\ - KIOU_HOOK_ID_HEADER_PROVIDER_SET_OR_UPDATE_HEADER - # --------------------------------------------------------------------------- # Theos boilerplate. # --------------------------------------------------------------------------- @@ -151,7 +132,6 @@ ipa:: chinlan exit 1; \ fi @TARGET_VERSION="$(TARGET_VERSION)" \ - KIOU_HOOK_ID_ALLOW="$(strip $(KIOU_HOOK_ID_ALLOW))" \ PYTHONPATH="$(KIOU_HOOK_DIR):$$PYTHONPATH" \ ./shared/tools/build_patched_ipa.sh \ --recipe "$(IPA_RECIPE)" \ diff --git a/vendor/KIOU-Hook b/vendor/KIOU-Hook index 2628837..3079c5e 160000 --- a/vendor/KIOU-Hook +++ b/vendor/KIOU-Hook @@ -1 +1 @@ -Subproject commit 2628837156ac24c8a92e27f5373ffd5f38b30a12 +Subproject commit 3079c5e5b96867cb81d54b4e92c11cd0ab31673d From 1300a8feec952008a78401d409319ecc59465666 Mon Sep 17 00:00:00 2001 From: tkgstrator Date: Wed, 1 Jul 2026 14:46:18 +0000 Subject: [PATCH 08/12] chore(submodule): bump KIOU-Hook to 23d7d4a (tools namespace fix) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Baseline restore point before the AFK CAVE_ENTRY migration. 23d7d4a drops the empty tools/__init__.py from KIOU-Hook so the `tools` package name can PEP-420-merge with Kanade's shared/tools/. Prior to this bump `make ipa` failed at the extract-recipe step because `python -m tools.patch_macho` couldn't be found under the `tools/` rooted in KIOU-Hook. Behavior for the on-device tweak is unchanged — only build-time module resolution is affected. Verified: `make deploy` produces a launching KIOU with baseline KiouForge behavior (16 KIOU-Hook sites + inline AFK). Co-Authored-By: Claude Opus 4.7 --- vendor/KIOU-Hook | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/KIOU-Hook b/vendor/KIOU-Hook index 3079c5e..23d7d4a 160000 --- a/vendor/KIOU-Hook +++ b/vendor/KIOU-Hook @@ -1 +1 @@ -Subproject commit 3079c5e5b96867cb81d54b4e92c11cd0ab31673d +Subproject commit 23d7d4a410ad133756f936f9222acc8e36494413 From 0ed6ecab57e7ccf5c6a8c19e4baa727e44e60b1f Mon Sep 17 00:00:00 2001 From: tkgstrator Date: Wed, 1 Jul 2026 14:52:32 +0000 Subject: [PATCH 09/12] feat(hooks): enable AFK suppress hook Co-Authored-By: Claude Opus 4.8 (1M context) --- Makefile | 1 + Sources/KiouForge/Tweak.m | 1 + vendor/KIOU-Hook | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 10cd2eb..b74fcb3 100644 --- a/Makefile +++ b/Makefile @@ -56,6 +56,7 @@ $(TWEAK_NAME)_FILES += vendor/KIOU-Hook/KIOUHook.m $(TWEAK_NAME)_FILES += vendor/KIOU-Hook/Account/Persistence.m $(TWEAK_NAME)_FILES += vendor/KIOU-Hook/Hook/AccountObserve.m $(TWEAK_NAME)_FILES += vendor/KIOU-Hook/Hook/GrpcLogging.m +$(TWEAK_NAME)_FILES += vendor/KIOU-Hook/Hook/AfkSuppress.m BUILD_COMMIT ?= $(shell git rev-parse --short=7 HEAD 2>/dev/null || echo unknown) diff --git a/Sources/KiouForge/Tweak.m b/Sources/KiouForge/Tweak.m index 94df713..e2a8f90 100644 --- a/Sources/KiouForge/Tweak.m +++ b/Sources/KiouForge/Tweak.m @@ -55,6 +55,7 @@ static void installUnityHooks(uintptr_t unityBase, const char *unityName) { KIOUInstallKifuObserveHook(unityBase); KIOUInstallAccountObserveHook(unityBase); KIOUInstallGrpcLoggingHook(unityBase); + KIOUInstallAfkSuppressHook(unityBase); g_unityHooked = YES; IPALog(@"=== KiouForge: all hooks installed ==="); diff --git a/vendor/KIOU-Hook b/vendor/KIOU-Hook index 23d7d4a..fe3085f 160000 --- a/vendor/KIOU-Hook +++ b/vendor/KIOU-Hook @@ -1 +1 @@ -Subproject commit 23d7d4a410ad133756f936f9222acc8e36494413 +Subproject commit fe3085ff6cbf9a307fd5b67af149a5710c5ec85d From 33e056c2ae21d6391b526ee0c8667c71cfd32545 Mon Sep 17 00:00:00 2001 From: tkgstrator Date: Wed, 1 Jul 2026 15:45:29 +0000 Subject: [PATCH 10/12] fix(kif): update helper RVAs for KIOU 1.0.2 Pin KIF helper callsites to dump.cs-verified 1.0.2 RVAs to avoid match-end crashes from stale 1.0.1 method addresses. Co-Authored-By: Claude Opus 4.8 (1M context) --- Sources/KiouForge/Kif/Helpers.m | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Sources/KiouForge/Kif/Helpers.m b/Sources/KiouForge/Kif/Helpers.m index e575473..af7f607 100644 --- a/Sources/KiouForge/Kif/Helpers.m +++ b/Sources/KiouForge/Kif/Helpers.m @@ -74,11 +74,15 @@ // --------------------------------------------------------------------------- // RVAs (KIOU 1.0.1 build 11). Same source of truth as KiouUsiProxy. // --------------------------------------------------------------------------- -#define RVA_GAMECTRL_GET_USI_TEXT 0x5D44074 // string GameController.GetUSIText(this) -#define RVA_POSITION_TO_SFEN 0x5D44374 // string Position.ToSFEN(this) -#define RVA_USIPARSER_PARSE_USI 0x5D572B4 // static RecordManager USIParser.ParseUSI(string) -#define RVA_KIFWRITEOPTIONS_CTOR 0x5D53960 // void KIFWriteOptions..ctor(this) -#define RVA_KIFWRITER_WRITE 0x5D53968 // static string KIFWriter.Write(RecordManager, KIFWriteOptions) +// RVAs pinned to KIOU 1.0.2 (dump.cs verified 2026-07-02). The previous +// values were 1.0.1 leftovers pointing at unrelated methods — calling +// through them at match-end crashed with a SIGSEGV inside GetUSIText +// (LDR from a bogus X10 loaded from *(gameCtrl+0xB0)). +#define RVA_GAMECTRL_GET_USI_TEXT 0x5D49970 // string GameController.GetUSIText(this) +#define RVA_POSITION_TO_SFEN 0x5D49C70 // string Position.ToSFEN(this) +#define RVA_USIPARSER_PARSE_USI 0x5D5CBB0 // static RecordManager USIParser.ParseUSI(string) +#define RVA_KIFWRITEOPTIONS_CTOR 0x5D5925C // void KIFWriteOptions..ctor(this) +#define RVA_KIFWRITER_WRITE 0x5D59264 // static string KIFWriter.Write(RecordManager, KIFWriteOptions) // KIFWriteOptions instance size needed for the raw-buffer trick. See the // KIFOPTS_OFF_* constants in Internal.h for the field map. Last field From 81b168ea2f2a21fa70e4ff120acf30d1a534f790 Mon Sep 17 00:00:00 2001 From: tkgstrator Date: Wed, 1 Jul 2026 15:47:46 +0000 Subject: [PATCH 11/12] chore(submodule): bump KIOU-Hook to be4cc6a (observer slot fix) Picks up the HOOK_SLOT_RVA move from __DATA.__bss (0x8F90C80) to __DATA.__common (0x091E92B8). Without this bump, CPUStreamMode / AIMatchMode / OnlinePvPMode / LocalPvPMode / RecordReplayMode OnMatchEndAsync observers would fire into a slot that il2cpp has already overwritten with garbage and the process would die with a PC alignment fault at the cave BLR X16. Paired with 33e056c (fix(kif): update helper RVAs for KIOU 1.0.2) this is what took resign from "crash" to "kifu saved cleanly". Co-Authored-By: Claude Opus 4.7 --- vendor/KIOU-Hook | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/KIOU-Hook b/vendor/KIOU-Hook index fe3085f..be4cc6a 160000 --- a/vendor/KIOU-Hook +++ b/vendor/KIOU-Hook @@ -1 +1 @@ -Subproject commit fe3085ff6cbf9a307fd5b67af149a5710c5ec85d +Subproject commit be4cc6ae2a250687bf14d5622181d6cebc6a90a0 From 3f3f24e1820f057a753111f788c8f78bcfb9d5b8 Mon Sep 17 00:00:00 2001 From: tkgstrator Date: Wed, 1 Jul 2026 15:50:24 +0000 Subject: [PATCH 12/12] feat(kif): read player names from match mode state stores Co-Authored-By: Claude Opus 4.8 (1M context) --- Sources/KiouForge/Internal.h | 13 +++++++++++++ Sources/KiouForge/Kif/Writer.m | 11 ++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/Sources/KiouForge/Internal.h b/Sources/KiouForge/Internal.h index 7b4a673..793c2d0 100644 --- a/Sources/KiouForge/Internal.h +++ b/Sources/KiouForge/Internal.h @@ -164,6 +164,19 @@ static const uintptr_t kKiouMatchModeAdapterOffsets[KIOU_MMODE_COUNT] = { [KIOU_MMODE_RECORD_REPLAY] = 0x18, }; +// IMatchMode self -> _stateStore field offsets. All five IMatchMode +// implementations carry a GameStateStore reference sitting one pointer +// slot before _gameAdapter — reading it lets the kif writer pull +// player names via the same ReactiveProperty path online +// matches use. +static const uintptr_t kKiouMatchModeStateStoreOffsets[KIOU_MMODE_COUNT] = { + [KIOU_MMODE_AI_MATCH] = 0x40, + [KIOU_MMODE_CPU_STREAM] = 0x48, + [KIOU_MMODE_LOCAL_PVP] = 0x10, + [KIOU_MMODE_ONLINE_PVP] = 0x28, + [KIOU_MMODE_RECORD_REPLAY] = 0x10, +}; + static const char *const kKiouMatchModeTags[KIOU_MMODE_COUNT] = { [KIOU_MMODE_AI_MATCH] = "AIMatchMode", [KIOU_MMODE_CPU_STREAM] = "CPUStreamMode", diff --git a/Sources/KiouForge/Kif/Writer.m b/Sources/KiouForge/Kif/Writer.m index 69615d1..20e7b4e 100644 --- a/Sources/KiouForge/Kif/Writer.m +++ b/Sources/KiouForge/Kif/Writer.m @@ -146,13 +146,18 @@ KIOUUniTaskRet KIOUKifuObserveMatchEnd(void *self, void *ct, return zero; } - // MatchConfig / GameStateStore only available on OnlinePvPMode's `self`; - // other modes' KIF gets blank player names (acceptable for offline play). + // All five IMatchMode implementations carry a GameStateStore that + // KIOUKifDescribeOpponents / KIOUKifFillWriteOptions can read player + // names from via ReactiveProperty. Only OnlinePvPMode + // additionally exposes a MatchConfig — the other modes seed the + // PlayerInfo directly into stateStore at match start. void *matchConfig = NULL; void *stateStore = NULL; + if (mode_index < KIOU_MMODE_COUNT && ptrLooksValid(self)) { + stateStore = readPtr(self, kKiouMatchModeStateStoreOffsets[mode_index]); + } if (mode_index == KIOU_MMODE_ONLINE_PVP && ptrLooksValid(self)) { matchConfig = readPtr(self, ONLINEPVPMODE_OFF_MATCHCONFIG); - stateStore = readPtr(self, ONLINEPVPMODE_OFF_STATE_STORE); } IPALog([NSString stringWithFormat: