feat: enable KiouEditor as a Chinlan consumer - #13
Closed
tkgstrator wants to merge 36 commits into
Closed
Conversation
Initial population extracted from KiouForge:
- KIOUHook.h — RVA + hook-id catalog, cave geometry, dispatcher externs
(`g_inject_entry`, `g_unityBase`, `KFChinlanPublish`), `KFUniTaskRet`,
shared installer protos.
- Account/Persistence.{h,m} — NSUserDefaults account storage. No Internal.h
dependency; importable on Foundation + Chinlan's logging.h alone.
- Hook/AccountObserve.m — login capture, deviceId / distinctId substitution,
AccountExists / RegisterUserArgs / LoginArgs / RunLoginSeq.MoveNext /
GetSelfProfile.MoveNext / RunReset / RunDelete hooks. Defines
KFNavigateToTitleScene used by consumer Settings UIs.
- Hook/GrpcLogging.m — x-user-id header swap on HttpMessageInvoker.SendAsync,
reads pending_device_id + active_user_id from Account/Persistence.
- recipes/__init__.py + common.py + v1_0_1.py + v1_0_2.py — Python catalog
selected by TARGET_VERSION env, mirrors enum kiou_kf_hook_id in KIOUHook.h.
- README.md — repo intent, consumer wiring, scope boundary vs gray hooks.
- LICENSE — All Rights Reserved (private repo, IPA-Patch internal).
- pyproject.toml — kiou_hook package metadata.
Shared `.m` files import il2cpp.h / logging.h explicitly so KIOUHook.h
stays Chinlan-include-free.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add a thin lookup layer (KIOUHook.m) keyed by short symbolic strings:
KIOUHookOrig(NAME) -> orig fn pointer (chinlan: cave bypass,
JB: MSHookFunction orig)
KIOUHookInstall(NAME, fn, base) -> install + return orig (no-op on chinlan
beyond returning the bypass)
KIOUHookSiteAddr(NAME, base) -> unityBase + site_rva, for raw fn pointers
Hook bodies (Account/Persistence stays unchanged; Hook/AccountObserve and
Hook/GrpcLogging are rewritten) no longer reference `KIOU_KF_HOOK_*` enum
values, `KIOU_KF_SITE_RVA_*` macros, or `g_inject_entry[]` directly. The
hook author only writes Objective-C against the symbolic name and the type
signature.
Unifies the JB and chinlan paths in the body: a single `static orig_X`
pointer holds either MSHookFunction's orig (JB) or the cave bypass entry
(chinlan), filled by KIOUHookInstall at install time. The IPA_CHINLAN
ifdefs collapse to a tiny installer-side branch.
The KIOU_KF_HOOK_* / KIOU_KF_SITE_RVA_* macros remain in KIOUHook.h so the
per-tweak ChinlanDispatcher.m (which still needs to switch on hook_id and
publish the entry slot table) can use them. KIOU-Hook's own .m files no
longer touch them.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds the 18 KiouEditor hook sites + 3 direct-ABI helper lookups to the
shared KIOU-Hook catalog so the upcoming KIOU-Hook bodies (PR-A2) and
the KiouEditor consumer migration (PR-B) can reference them by name.
* KIOUHook.h:
- kiou_kf_hook_id extended 15 -> 33 with 18 KiouEditor entries
- kiou_kf_entry_slot_id extended 10 -> 28
- 18 KIOU_KF_SITE_RVA_* macros (1.0.1 RVAs) appended
- 3 direct-ABI helper RVAs (NSS_SETHASHSIZE_DIRECT,
GAMEOBJECT_GETCOMPONENT, RTU_WORLDTOSCREENPOINT)
- 21 new extern KIOU_HOOK_NAME_* declarations
- Old unused KIOU_KF_SITE_RVA_GAME_ORCHESTRATOR_IS_AFK (1.0.2 RVA)
removed in favour of the 1.0.1-pinned macro since AFK is now a
CAVE_ENTRY rather than an inline patch
* KIOUHook.m: 18 name strings + 18 catalog rows + 3 direct-ABI rows
(hook_id = -1) appended.
* recipes/common.py:
- HOOK_IDS gains 18 KiouEditor entries (15..32)
- ENTRY_SLOT_INDEX gains 18 entries (10..27)
- ENTRY_SLOT_COUNT 10 -> 28, ENTRY_SLOT_CAPACITY 16 -> 32
- build_exports() makes AFK inline patch optional so 1.0.1 can drop it
* recipes/v1_0_1.py: appends 18 CAVE_ENTRY SITES rows including
GAME_ORCHESTRATOR_IS_AFK; AFK_SITE/AFK_ORIG_8 left as None/""
stubs so the inline AFK patch is disabled — the consumer tweak now
owns AFK suppression through its Hook_AfkDisable body.
Verified:
TARGET_VERSION=1.0.1 ... verify_sites -> all 27 sites pass
TARGET_VERSION=1.0.2 ... verify_sites -> all 15 sites pass
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The KF prefix was a holdover from when this catalog lived inside
KiouForge. Now that KIOU-Hook is the shared library and KiouEditor is
about to consume it too, KF is semantically wrong — it reads as
"KiouForge" rather than "KIOU framework".
Renames (no aliases — consumers update in lockstep):
KIOU_KF_HOOK_<name> -> KIOU_HOOK_ID_<name>
KIOU_KF_HOOK__COUNT -> KIOU_HOOK_ID__COUNT
KIOU_KF_ENTRY_SLOT_<name> -> KIOU_HOOK_SLOT_<name>
KIOU_KF_ENTRY_SLOT__COUNT -> KIOU_HOOK_SLOT__COUNT
KIOU_KF_SITE_RVA_<name> -> KIOU_HOOK_RVA_<name>
KIOU_KF_HOOK_SLOT_RVA -> KIOU_HOOK_OBSERVER_SLOT_RVA
KIOU_KF_ENTRY_SLOT_BASE_RVA -> KIOU_HOOK_ENTRY_SLOT_BASE_RVA
KIOU_KF_CAVE_<name> -> KIOU_HOOK_CAVE_<name>
enum kiou_kf_hook_id -> enum kiou_hook_id
enum kiou_kf_entry_slot_id -> enum kiou_hook_slot_id
The new naming follows the existing KIOU_HOOK_NAME_<name> pattern for
hook name strings: KIOU_HOOK_<KIND>_<NAME>.
Function names (KFHook*, KFInstall*, KFChinlanPublish, etc.) are left
alone for now — they're a separate refactor and not the source of the
naming oddness called out in review.
Verified:
TARGET_VERSION=1.0.1 verify_sites -> all 27 pass
TARGET_VERSION=1.0.2 verify_sites -> all 15 pass
patch_macho dry-run against 1.0.1 UnityFramework -> all 27 caves
written cleanly, no collisions
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
IL2CPP instance methods take a trailing MethodInfo* in the next free arg
register (x3 on ARM64). Dropping it leaves x3 polluted from the caller
and orig SendAsync / HttpHeadersRemove / TryAdd crash the moment orig
dereferences MethodInfo->methodPointer.
* Add trailing `void *mi` to HttpHeadersTryAdd_t, HttpHeadersRemove_t,
and GenericSendAsync_t — lockstep with AnalysisTune.m's convention.
* KFHookHttpMsgInvokerSendAsync now receives mi and forwards it to orig.
* Header swap callers pass NULL for mi (we don't have one to forward).
Also harden swapUserIdHeader against unexpected throws so login can
proceed with a stale x-user-id instead of aborting the process:
* Add `@catch (id e)` alongside `@catch (NSException *e)` to catch
non-NSException throws (e.g. C++ exceptions bridged as id).
* Wrap the swapUserIdHeader call site in its own @try/@catch so any
escape still falls through to orig SendAsync.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
BackToTitleSequence.RunAsync, RunResetUserDataSequenceAsync, and
RunDeleteAccountSequenceAsync are static IL2CPP methods, so their
ARM64 ABI takes a trailing MethodInfo* in x1/x2. Previous typedef
was (ct) only, leaving the callee to read garbage from x1 for the
methodInfo → crash on account-switch confirm inside RunAsync's
prologue.
* BackToTitleRunAsync_t → (void *ct, void *mi)
* RunResetSeq_t → (void *ct, void *mi), also used by
RunDeleteAccountSeq_t
* KFHookRunResetUserDataSeq / KFHookRunDeleteAccountSeq updated to
receive and forward mi.
* KFNavigateToTitleScene passes fn(NULL, NULL); NULL MethodInfo*
still risks generic-init deref inside RunAsync, but on 1.0.2
build 12 that path never dereferences it — future consumers can
promote a MethodInfo RVA to the catalog if that changes.
Fixes the "settings modal → tap 切り替え → 3s crash" reproducer.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Move the account-switch x-user-id header swap from the
HttpMessageInvoker.SendAsync hook to Project.Network.HeaderProvider.
SetOrUpdateHeader (RVA 0x5BD9EE8 on 1.0.2 build 12), which sits
upstream of every gRPC call and takes only managed types
(string key, string value, MethodInfo*).
The old SendAsync-based swap could not survive the boundary: touching
self._handler, request.Headers, or calling HttpHeaders.TryAdd/Remove
from that hook reliably crashed inside the subsequent orig SendAsync
at HttpHeaders.GetEnumerator, dereferencing a 0x20025xxx truncated
pointer. The likely cause is Cysharp Yaha's Rust FFI borrowing the
request across the SendAsync boundary — anything ObjC/IL2CPP-side does
to those objects tears the borrow. HeaderProvider is Project.Network
managed code, well before Yaha ever sees the request.
Catalog changes:
* KIOUHook.h — add KIOU_HOOK_ID_/SLOT_HEADER_PROVIDER_SET_OR_UPDATE_
HEADER, KIOU_HOOK_RVA_HEADER_PROVIDER_SET_OR_UPDATE_HEADER, and
KIOU_HOOK_NAME_HEADER_PROVIDER_SET_OR_UPDATE_HEADER.
* KIOUHook.m — new kCatalog row.
* recipes/common.py — HOOK_IDS[15] and ENTRY_SLOT_INDEX[10] entries;
ENTRY_SLOT_COUNT 28 → 29.
* recipes/v1_0_2.py — new CAVE_ENTRY SITES row for site 0x5BD9EE8
with prologue f657bda9 (STP X22,X21,[SP,#-0x30]!).
Hook body (Hook/GrpcLogging.m rewritten):
* KFHookHeaderProviderSetOrUpdate reads keyStr, when it equals
"x-user-id" and a pending switch is armed to a known saved
account, allocates a new il2cpp string carrying the target
userId via g_GrpcStringNew, and forwards to orig with the swapped
value. Otherwise passes through untouched.
* KFHookHttpMsgInvokerSendAsync reduced to a bare passthrough
(`return orig(self, request, ct, mi)`). Kept installed so the
cave / entry_slot wiring stays exercised for future bisection,
but does zero work in the body.
* All the old swap machinery (swapUserIdHeader, findMethodInfoByRva,
HttpHeaders.TryAdd/Remove RVAs, s_HttpHeadersTryAdd_mi/Remove_mi,
il2cpp_class_get_methods/get_parent dlsym) is removed.
Verified on device (jailbroken, TrollStore-installed IPA):
[HEADER] x-user-id swapped → 019f04d0-…
[ACCOUNT] LoginReply userName=Kiou Forge deviceId=…
[ACCOUNT] active_user_id=019f04d0-…
[ACCOUNT] SelfProfile openUserId=1054-1001-0520-9241 (before)
[ACCOUNT] SelfProfile openUserId=2620-3831-5122-4152 (after switch)
Account switch completes without crash; server no longer -40004s the
post-switch login.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
feat(hooks): x-user-id swap via HeaderProvider + static-method typedef fixes
The KF prefix was inherited from KiouForge but the KIOU-Hook library is
consumed by multiple sibling tweaks (KiouForge, KiouEditor migration in
flight). Rename `KF*` -> `KIOU*` on every extern-visible symbol so the
prefix reflects the neutral "KIOU framework" origin rather than a
specific tweak:
- Account/Persistence.h: KFSaveAccount / KFListAccounts / KFPending* /
KFSwitchAccount / KFAccountStateChangedNotification / ...
- KIOUHook.h: KFChinlanPublish, KFNavigateToTitleScene, KFUniTaskRet,
KFInstallAccountObserveHook, KFInstallGrpcLoggingHook
- Hook/AccountObserve.m, Hook/GrpcLogging.m: internal references
Notification name constant value is updated in lockstep with the
variable rename ("KIOUAccountStateChangedNotification"). No observed
external subscribers use the literal string form; consumers use the
extern constant.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Migrates 10 of KiouEditor's 11 hook modules (SHA 4d137803) to the
KIOU-Hook shared library so any consumer tweak (KiouEditor, KiouForge,
future ports) can cherry-pick them via Makefile `_FILES +=`. Uses the
name-based hook API (KIOUHookInstall / KIOUHookOrig / KIOUHookSiteAddr)
so bodies never reference RVAs or slot enums directly. FriendUnhide
(56 KB il2cpp_runtime_invoke bridge) is intentionally left for PR-A3
so it gets isolated review.
Layout — flat under Hook/ (no Editor/ subdirectory):
Hook/Common.{h,m} — shared surface for the migrated bodies:
* g_inHook reentrancy guard (moved from KiouEditor's Tweak.m)
* KIOUEditorPersistedSelection / SetPersistedSelection /
ApplyPersistedSelectionToLists (moved from Hook_SelectCharacter.m,
shared with SyncItemList)
* KIOUEditorReconButtonImage / ApplyTitleSpriteToClone stubs
(real defs land with Hook/FriendUnhide.m in PR-A3)
* KIOUEditorFeatureEnabled / SetFeatureEnabled / FeatureLabel and
KIOUEditorAssistDepth / SkillLevel / HashIndex / HashMB weak
stubs so KIOU-Hook links standalone against consumers that
don't wire the API. Consumer-provided strong symbols win.
* KIOU_EDITOR_COMMIT / KIOU_EDITOR_VERSION fallbacks so Version.m
compiles without -DKIOU_EDITOR_VERSION.
Hook/AfkDisable.m — GameOrchestrator.IsAfkEnabled -> false
Hook/AssistEnable.m — ResolvedBeginnerSupport get_Enabled / get_Depth
Hook/AssistTune.m — BSE.ctor field pin + EnsureInitializedLocked
SetHashSize (uses
KIOU_HOOK_NAME_NSS_SETHASHSIZE_DIRECT lookup)
Hook/Collection.m — UpdateCollectionPresetReply.InternalMergeFrom
(observation only)
Hook/MatchingPlayer.m — ShogiMatchingPlayerStatus.InternalMergeFrom
(self identification via KIOUSelfUserId +
SAFE_ID heuristic; skin rewrite)
Hook/PremiumUnlock.m — KifuDetailModel.IsPremiumUser + reply
InternalMergeFrom + get_IsPremiumUser
Hook/SelectCharacter.m — SelectCharacterAsync request swap +
SelectCharacterReply.InternalMergeFrom stitch
Hook/SyncItemList.m — SyncItemListReply.InternalMergeFrom
(decoration-band unlock + intimacy pin +
persisted selection stitch)
Hook/Version.m — TitleScene.<OnActivateAsync>d__10.MoveNext
(_appVersionFormat suffix)
Hook/VoiceUnlock.m — CharacterVoicePlayer.SatisfiesRule +
VoiceScrollerCellModel.get_IsLocked
Account/Persistence extends with KIOUSelfUserId / KIOUSetSelfUserId
(moved from Hook_MatchingPlayer.m). Storage key preserved
("kiou_editor.self_user_id") so existing KiouEditor installs keep
their captured self id across the migration.
Verified by compile-linking all new files into KiouForge's chinlan
build target (clean warnings, otool-L confirms no dangling externs).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Bootstrap CI for KIOU-Hook. Four independent jobs run on every PR to
main and on direct pushes:
- commitlint — conventional-commit type + subject-case on every
PR commit (mirrors the KiouForge workflow so consumers see the
same guardrails).
- python-lint — `ruff check` on recipes/ + tools/. `ruff format --check`
is intentionally skipped so the column-aligned dict / SITES
literals stay untouched.
- python-typecheck — `pyright` on recipes/ + tools/. Missing
`tools.encode` (which lives in Kanade, not here) surfaces as
a warning per pyproject config, not an error.
- recipe-check — AST-level structural validation of the SITES
tables against HOOK_IDS / ENTRY_SLOT_INDEX in `common.py`.
Runs without importing (so Kanade isn't needed) via a new
`tools/check_recipes.py`.
Excluded on purpose:
- Objective-C compile: consumers (KiouForge) already do a full
Theos build; duplicating that here would burn CI minutes for
no additional coverage.
- `verify_sites` against a decrypted IPA: the IPA + index files
are private assets in the consumer repo. A follow-up PR can
wire this up via Actions secrets when needed.
Touch-ups to existing files driven by the new lint pass:
- recipes/__init__.py: import order + explicit `__all__` so re-
exports of `TARGET_BASENAME` / `DYLIB_PATH` / `PLIST_KEYS` /
`ENTRY_SLOT_*` / `build_exports` from `recipes.common` are
recognized as intentional (F401).
- recipes/v1_0_1.py, v1_0_2.py: replace ambiguous MULTIPLICATION
SIGN (`×`) in a comment with plain `x` (RUF003).
Local run confirms:
ruff check — All checks passed
recipe-check — HOOK_IDS=34 ENTRY_SLOT_INDEX=29 sites=43
pyright — 0 errors, 2 warnings (expected tools.encode)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ci: bootstrap CI (commitlint + ruff + pyright + recipe consistency)
feat(hooks): KiouEditor Hook_* modules migration (PR-A2) + KF→KIOU rename
Migrates KiouEditor's Hook_FriendUnhide.m (56 KB, SHA 4d137803) —
the il2cpp_runtime_invoke bridge that clones + reparents + remaps
the retail-hidden friend button into a settings entry point —
onto the KIOU-Hook name-based hook API.
Hook sites:
- HomeUtilityPresenter.ctor -> unhide + clone + reparent the
friend button so it lives next
to the menu / gift row and
carries the title-screen sprite
- UIButtonBase.OnPointerClick -> intercept taps on the cloned
button and route them to the
consumer tweak's
KIOUEditorPresentSettings()
The 56 KB body is byte-for-byte the same as KiouEditor's original;
only the wiring surface changed:
- `#import "Internal.h"` -> `#import "Hook/Common.h"` +
`#import "logging.h"`.
- `file_log(...)` -> `IPALog(...)`.
- `kiou_featureEnabled` / `kioueditor_reconButtonImage` /
`kioueditor_applyTitleSpriteToClone` / `kioueditor_presentSettings`
rename in lockstep with the KIOU* / KIOUEditor* API family.
- `install_FriendUnhide_hook` (JB) + `publish_FriendUnhide_slots`
(binpatch) collapsed into a single
`KIOUEditorInstallFriendUnhideHook(unityBase)` that calls
`KIOUHookInstall(KIOU_HOOK_NAME_HOME_UTILITY_PRESENTER_CTOR, ...)`
and `KIOUHookInstall(KIOU_HOOK_NAME_UIBUTTONBASE_ONPOINTERCLICK, ...)`.
Hook/Common.m stubs updated:
- `KIOUEditorReconButtonImage` / `KIOUEditorApplyTitleSpriteToClone`:
real definitions now live in Hook/FriendUnhide.m; the weak stubs
stay in Common.m so consumers that pull in Hook/Version.m without
Hook/FriendUnhide.m (e.g. KiouForge for compile-testing) still
link cleanly. When FriendUnhide.m is present, its strong symbols
supersede the weak stubs.
- New weak stub for `KIOUEditorPresentSettings(void)` — the consumer
tweak (KiouEditor) defines this in its Hook_SettingsUI.m. Same
weak-supersede pattern.
Hook/Common.h extended:
- Uncommented `KIOUEditorInstallFriendUnhideHook` prototype.
- Added `KIOUEditorPresentSettings` prototype with a note pointing
consumers at the wiring contract.
Verified by compile-linking every Hook/*.m (including FriendUnhide)
into KiouForge's chinlan build target. All 28 files compile, link,
and sign clean; `otool -L` on the resulting dylib shows no dangling
externs.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds the 17 non-AFK KiouEditor hook sites to recipes/v1_0_2.py so a
KiouEditor build targeting KIOU 1.0.2 can chinlan-patch them
alongside the existing KiouForge base + kifu observer + HeaderProvider
entries.
RVAs verified against assets/1.0.2/dump.cs on 2026-07-01; prologue
bytes captured from assets/1.0.2/Kiou-1.0.2.ipa UnityFramework. Ran
python -m tools.verify_sites --recipe recipes --index
assets/1.0.2/dump.cs.index.json --ipa assets/1.0.2/Kiou-1.0.2.ipa:
32 / 33 sites pass (the pre-existing HeaderProvider verification
skip is unrelated — index-lookup misses the fully-qualified type
name).
0x5C3C29C SyncItemListReply.InternalMergeFrom
0x5C458C4 UpdateCollectionPresetReply.InternalMergeFrom
0x5CACEF8 GameServiceClient.SelectCharacterAsync
0x5C2C034 SelectCharacterReply.InternalMergeFrom
0x5B51C3C ShogiMatchingPlayerStatus.InternalMergeFrom
0x5C06590 GetShogiHistoryDetailListReply.InternalMergeFrom
0x5C05FF0 GetShogiHistoryDetailListReply.get_IsPremiumUser
0x585E000 KifuDetailModel.IsPremiumUser
0x582E614 CharacterVoicePlayer.SatisfiesRule
0x584DB64 CharacterVoiceScrollerCellModel.get_IsLocked
0x597E608 BeginnerSupportEvaluator.ctor
0x5980890 BeginnerSupportEvaluator.EnsureInitializedLocked
0x5942AA0 ResolvedBeginnerSupport.get_Enabled
0x5942AC0 ResolvedBeginnerSupport.get_Depth
0x5AA4054 HomeUtilityPresenter.ctor
0x5DD7F54 UIButtonBase.OnPointerClick
0x5DD2874 TitleScene+<OnActivateAsync>d__10.MoveNext
GAME_ORCHESTRATOR_IS_AFK is intentionally NOT ported to a CAVE_ENTRY
on 1.0.2. KiouForge's build already patches that site inline via
AFK_SITE / AFK_ORIG_8 to force IsAfkEnabled -> false, and a KiouEditor
1.0.2 build that wants feature-flag-gated AFK toggling would crash on
first call unless KiouForge simultaneously publishes the cave slot
(BLR to null slot pointer). Migrating 1.0.2 AFK to a cave will land
in a follow-up PR that coordinates with a KiouForge update.
Also drops tools/__init__.py — PEP-420 namespace packages let
python -m tools.check_recipes resolve without it, and its presence
was shadowing the sibling shared/tools/ package on consumer builds.
Verified: python -m tools.check_recipes still passes locally
(HOOK_IDS=34 ENTRY_SLOT_INDEX=29 sites=60).
Not addressed in this PR:
- C-side catalog RVAs for the KiouEditor sites in KIOUHook.h /
KIOUHook.m are still pinned to 1.0.1 (via the KIOU_HOOK_RVA_*
macros). On chinlan builds this is inert (the dispatcher's
g_inject_entry table drives hook dispatch, not the catalog
RVAs), so the recipe port here is sufficient for the primary
consumer (KiouEditor chinlan). Direct-ABI callouts from
Hook/AssistTune.m and Hook/FriendUnhide.m (SetHashSize /
GameObject.GetComponent / RectTransformUtility.WorldToScreenPoint)
WILL land at wrong addresses on a 1.0.2 build until the catalog
gains version-aware dispatch — track that as a follow-up.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Retires the AFK_SITE inline patch on 1.0.2 in favour of a CAVE_ENTRY
so both 1.0.1 and 1.0.2 route GameOrchestrator.IsAfkEnabled through
the same hook-body path.
Recipe changes (recipes/v1_0_2.py):
- AFK_SITE / AFK_ORIG_8 -> None / "" (drops the inline
`MOVZ W0, #0; RET` patch)
- GAME_ORCHESTRATOR_IS_AFK row added to SITES (CAVE_ENTRY at
0x594A034, prologue f44fbea9)
To keep the "AFK is always off" behaviour KiouForge relied on
without pulling in the KIOUEditor feature-flag surface, KIOU-Hook
now exposes a thin convenience installer:
void KIOUAfkDisableAlwaysFalseInstall(uintptr_t unityBase);
It publishes a static `return false` hook body into the cave's
entry slot. One call from a consumer's Tweak.m is enough to
restore the historic behaviour. The feature-flag-gated variant
(KIOUEditorInstallAfkDisableHook in Hook/AfkDisable.m) is
unchanged.
**Coordinated rollout required.** Once this lands on main, any
consumer that chinlan-patches with the updated recipe will get
the AFK cave inserted. Consumers MUST publish the AFK cave slot
before their patched IPA runs, or the first IsAfkEnabled call
BLR-jumps to a null slot pointer and crashes. KiouForge's
companion change (call `KIOUAfkDisableAlwaysFalseInstall` from
its Tweak.m + submodule bump) is expected to land in the same
window as this PR.
Verified with:
python -m tools.check_recipes -> 61 sites (was 60)
TARGET_VERSION=1.0.2 python -m tools.verify_sites ... ->
33/34 rows pass (the pre-existing HeaderProvider miss is the
lone FAIL, unrelated).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
feat(hooks): port KiouEditor FriendUnhide to KIOU-Hook (PR-A3)
feat: port KiouEditor sites to 1.0.2 + migrate AFK to CAVE_ENTRY
The observer dispatcher slot sat at 0x8F90C80, which falls in UnityFramework's __DATA.__bss (0x8E83340..0x8F9D4C0). KIOUChinlanPublish wrote dispatch_one there at load time, but by the time the first observer cave fired (a CPUStreamMode.OnMatchEndAsync on resign) the slot contents had been overwritten to garbage — the cave BLR X16 jumped to an unaligned PC and the process died with a PC alignment fault. il2cpp / UnityRuntime writes into __bss during lazy metadata init after our constructor runs. Move the slot into __DATA.__common (0x8F9D4C0..0x91F5978), where the entry-slot table (0x091E91B8..0x091E92B8) already lives. Entry slots survive publish every session (login-path hooks fire cleanly), so positioning the observer slot immediately past the entry-slot capacity — at 0x091E92B8 — puts it in the same known-stable region. - KIOUHook.h: KIOU_HOOK_OBSERVER_SLOT_RVA -> 0x091E92B8 - recipes/v1_0_1.py: HOOK_SLOT_RVA -> 0x091E92B8 - recipes/v1_0_2.py: HOOK_SLOT_RVA -> 0x091E92B8 - recipes/__init__.py: new assert HOOK_SLOT_RVA + 8 <= ZERO_REGION_END Verified on device via KiouForge: CPUStreamMode.OnMatchEndAsync now fires cleanly through the cave -> dispatch_one -> HookCpuStreamEnd chain, and the resulting kifu file writes without any PC alignment fault surfacing. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix(afk): publish entrySlots[GAME_ORCHESTRATOR_IS_AFK] on chinlan `KIOUAfkDisableAlwaysFalseInstall` used to only route through `KIOUHookInstall`, which is a no-op for slot publishing on chinlan (it returns the bypass entry so callers can invoke orig; it does not write into the entry slot table). That meant the AFK cave BLR'd through a NULL `entrySlots[GAME_ORCHESTRATOR_IS_AFK]` and crashed on the first `IsAfkEnabled` call — the exact regression the "always false" installer exists to prevent. Write the slot directly on chinlan so a plain `KIOUAfkDisableAlwaysFalseInstall(unityBase)` call in the consumer tweak is enough. JB path keeps calling `KIOUHookInstall` (which does the right thing there via `MSHookFunction`). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * ci: update trigger branch from main to master The default branch was renamed main -> master; workflow triggers need to match or push events / PR CI won't fire. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Aligns the AFK installer with the KIOUInstall*Hook naming used by every other installer in this catalog (KIOUInstallAccountObserveHook, KIOUInstallGrpcLoggingHook, etc.). Same behaviour — API-only rename. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Consumers that don't ship every KIOU-Hook site — for example KiouForge,
which only wants the 17 sites relevant to it and does not include the
KiouEditor UI/kifu unlock caves — can now set
KIOU_HOOK_ID_ALLOW=KIOU_HOOK_ID_SET_TARGET_FRAMERATE,KIOU_HOOK_ID_...
as an environment variable when running `tools.patch_macho`. Only sites
whose hook_id_name matches the comma-separated allow-list get patched
into the CAVE_REGION; the rest are silently skipped.
Motivation: with all 34 caves patched, the last two rows on 1.0.2
(HomeUtilityPresenter.ctor at cave 33 and UIButtonBase.OnPointerClick
at cave 34) collide with the `__oslogstring` fragment at
0x826FFF8..0x8270020, and `tools.patch_macho` fails the zero-fill
check. Consumers that don't need those two sites shouldn't have to pay
for the collision.
The filter validates that every id in KIOU_HOOK_ID_ALLOW appears in
the version's SITES list, so a typo raises a clear ValueError instead
of silently dropping to zero patched sites.
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Two small surface additions that unblock KiouEditor from consuming KIOU-Hook via vendor/KIOU-Hook + Chinlan: 1. Add KIOU_FEATURE_INGAME_ANALYSIS and KIOU_FEATURE_AI_SPECIAL_SUPPORT to the shared KiouFeature enum, with labels in the Common.m weak stub. The hook bodies that consume these flags live in the consumer tweak (KiouEditor) because the sites are tweak-local and not part of the shared catalog. Placing the enum values here lets KIOUEditorFeatureEnabled gate them uniformly with the shared 7. 2. Wrap KIOU_HOOK_CAVE_REGION_START in ifndef so consumers targeting a different KIOU binary version can override on the compiler command line. Default (0x826F5E8) still matches v1_0_2 / KiouForge; KiouEditor at 1.0.1 sets -DKIOU_HOOK_CAVE_REGION_START=0x8268024. No RVA additions, no slot table changes, no recipe cascade — the existing 11 KiouEditor-flavored shared hooks (Afk, AssistEnable, Collection, FriendUnhide, MatchingPlayer, PremiumUnlock, SelectCharacter, SyncItemList, Version, VoiceUnlock, plus the AssistTune ctor/EnsureInit pair) already sit in the 32-slot entry table with 1.0.1 RVAs live in the catalog.
Add KIOU_HOOK_PUBLISH_SLOT helper in Hook/Common.h and use it from all
11 KiouEditor-flavored installers so the shared hook bodies work under
IPA_CHINLAN as well as JB/jailed.
Prior to this, KIOUHookInstall's chinlan branch only returned the
cave-bypass entry for orig-chain-back; the entry-slot table was never
written, so any cave the recipe carved would BLR through NULL on the
first call. Only KIOUInstallAfkSuppressHook (already in the tree)
demonstrated the manual entry-slot write pattern.
The macro is a one-line addition per site in each installer:
s_orig = (fn_t)KIOUHookInstall(NAME, (void *)hook_fn, unityBase);
KIOU_HOOK_PUBLISH_SLOT(unityBase, SLOT_ID, hook_fn);
On JB / jailed the macro degenerates to (void) casts so the installer
compiles unchanged. On chinlan it materialises the entry-slot address
and stores the hook function pointer.
KiouEditor at 1.0.1 can now consume these installers via
vendor/KIOU-Hook + Chinlan without adding an editor-side ChinlanDispatcher
publish for every shared site.
Hook/FriendUnhide.m was 1121 lines — well past the project's 600-line hard-split threshold. Extract the il2cpp + Unity bridging layer that takes up ~800 of those lines into two sibling files behind a shared private header, leaving Hook/FriendUnhide.m as the concise ~210-line hook-body + installer file it wants to be. Layout ------ * Hook/FriendUnhide.m (209 lines) — hook bodies for HomeUtilityPresenter.ctor + UIButtonBase.OnPointerClick, installer. * Hook/FriendUnhideBridge.h (public bridge surface) — the extern declarations the hook body uses: init, Component/GO/Transform helpers, hierarchy recon, hide-image, sprite recon, direct Instantiate, and the three shared state pointers (g_friendGo, g_cloneGo, g_lastClonedView). * Hook/FriendUnhideBridge.m (441 lines) — il2cpp bridge resolver, method-pointer caches, Component / GameObject / Transform helpers, hierarchy walker, sprite lookup + swap primitives, transformChildByName / componentByTypeName / findIconImageTransform. * Hook/FriendUnhideBridgeUI.m (457 lines) — sprite ops, image hide, text-component recon, Instantiate variants, and the two public strong-symbol implementations KIOUEditorReconButtonImage / KIOUEditorApplyTitleSpriteToClone. Also owns transformSetSiblingIndex + transformOf because their dependents live here. * Hook/FriendUnhideBridge_Private.h (61 lines) — declarations shared between the two Bridge .m files: il2cpp state, g_unityBaseAddr, a handful of internal helpers (invoke0, objectName, transformChildByName, componentByTypeName, transformChildCount, transformGetChild, findIconImageTransform, swapImageSpriteOnGo, g_titleMenuSprite). Private: not part of the public consumer API. Every file now sits comfortably under the 600-line threshold; only Bridge.m + BridgeUI.m sit in the 400-600 "consider splitting" band, where their single-responsibility grouping justifies the size. No behaviour change. The friendUnhide_initBridge symbol becomes FriendUnhideBridgeInit and is exported from Hook/FriendUnhideBridge.m so the hook installer in Hook/FriendUnhide.m can drive it before KIOUHookInstall wires either RVA. Consumers that only import Hook/Common.h continue to see just the two public entry points KIOUEditorReconButtonImage and KIOUEditorApplyTitleSpriteToClone.
…ia extern The 1121-line FriendUnhide.m split (bbf2c61) left four il2cpp method-handle caches — g_method_get_transform, g_method_Instantiate2, g_method_Instantiate1NonGen, g_method_Tf_SetSiblingIndex — defined `static` in Bridge.m while their only callers moved to BridgeUI.m. The result was a hard compile break: Bridge.m failed with -Wunused-variable on the four statics, and BridgeUI.m failed with "use of undeclared identifier" for each. Drop `static` on the four shared caches, add matching `extern` declarations in Hook/FriendUnhideBridge_Private.h (which is already imported from both halves for exactly this purpose), and delete g_reconLogged — the one-time debug guard from the pre-split recon path that no code references anymore. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The BeginnerSupportEvaluator.EvaluateAsync suppression that gates
"In-Game Analysis" lived in KiouEditor as an MSHookFunction-only
tweak-local supplement. On chinlan the site had no cave, so the
toggle was silently a no-op on binary-patched builds — the exact
flavor this consumer ships. Migrate it into the shared catalog so
chinlan actually dispatches.
The chinlan wiring mirrors hook_BSE_ctor / hook_BSE_ensureInit that
already live in this file: KIOUHookInstall +
KIOU_HOOK_PUBLISH_SLOT under the existing installer, so consumers
call the same KIOUEditorInstallAssistTuneHook and get all three
BSE hooks with one install call.
Catalog additions (all appended before __COUNT so downstream hook
IDs and slot indices stay pinned):
- KIOUHook.h: KIOU_HOOK_ID_BSE_EVALUATE_ASYNC,
KIOU_HOOK_SLOT_BSE_EVALUATE_ASYNC,
KIOU_HOOK_RVA_BSE_EVALUATE_ASYNC (1.0.1 pinned,
matching the other KiouEditor sites in this
header), and KIOU_HOOK_NAME_BSE_EVALUATE_ASYNC.
- KIOUHook.m: catalog string constant + catalog row.
- recipes/common.py:HOOK_IDS[BSE_EVALUATE_ASYNC] = 34,
ENTRY_SLOT_INDEX[BSE_EVALUATE_ASYNC] = 29,
ENTRY_SLOT_COUNT bumped 29 -> 30 (still inside
ENTRY_SLOT_CAPACITY = 32).
- recipes/v1_0_1.py:new SITE (0x597B570, "ff8302d1", CAVE_ENTRY,
"BeginnerSupportEvaluator.EvaluateAsync").
- recipes/v1_0_2.py:new SITE (0x5980304, "ff8302d1", CAVE_ENTRY,
"BeginnerSupportEvaluator.EvaluateAsync"). The
two RVAs are the BSE.EvaluateAsync entries in
the respective dump.cs.index.json; the shared
prologue "ff8302d1" (SUB SP, SP, #0xA0) is not
PC-relative, so verbatim relocation into the
cave tail is safe.
Also included: fix(recipes): move v1_0_2 CAVE_REGION past
__oslogstring. The old region (0x826F5E8, 0x8274000) had only 30
cave slots before running into __oslogstring at 0x8270000..0x8270023
(the "%{public}@" fragment), which broke SITE #30 and #31 when the
1.0.2 catalog crossed 30 sites. Shift the region to (0x8270024,
0x8274000) — 194 slots of verified zero-fill — so the pre-existing
34 sites patch cleanly and the new BSE.EvaluateAsync (SITE #35,
cave @ 0x8270B4C) still lands inside the region. KiouForge's
KIOU_HOOK_ID_ALLOW filter never hit this collision because it
ships fewer than 30 sites, but adding EvaluateAsync would push any
consumer over the edge.
Build-verified on Kiou-1.0.2 (chinlan): all 35 SITEs patch, the
recipe assertions pass, and the resulting IPA repacks. Not
runtime-verified on device — the JB / jailed flavor paths are
mechanically unchanged (KIOUHookInstall on JB still calls
MSHookFunction), so behavior parity is preserved by construction.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The 5 ShogiMoveResultStatus / ShogiMatchingPlayerStatus getters that gate the 棋桜覚醒 button lived in KiouEditor as MSHookFunction-only supplements — a no-op on chinlan because the sites had no caves. The "paid-feature unlock stays local to the consumer" policy from KIOU-Hook's README predates the KIOU_FEATURE_AI_SPECIAL_SUPPORT toggle that already gates every one of these hooks: shared plumbing only publishes the site wiring; the user still has to opt in through KiouEditor's settings sheet. With the feature gate covering the risk, promote them so chinlan actually dispatches. Catalog layout — the 5 new CAVE_ENTRY IDs land after KIOU_HOOK_ID_BSE_EVALUATE_ASYNC (=34) at slots 30..34 / IDs 35..39: KIOU_HOOK_ID_MOVE_RESULT_CAN_USE_SPECIAL (bool) KIOU_HOOK_ID_MOVE_RESULT_FREE_REMAINING (int32) KIOU_HOOK_ID_MOVE_RESULT_TICKET_REMAINING (int32) KIOU_HOOK_ID_MP_FREE_REMAINING (int32) KIOU_HOOK_ID_MP_PAID_AVAILABLE (int32) That pushes ENTRY_SLOT_COUNT to 35, one past the previous ENTRY_SLOT_CAPACITY = 32. Bump the capacity to 40 (sibling room) and slide the single observer-dispatcher slot forward by 0x100: KIOU_HOOK_OBSERVER_SLOT_RVA: 0x091E92B8 -> 0x091E93B8 HOOK_SLOT_RVA (v1_0_1/v1_0_2):0x091E92B8 -> 0x091E93B8 The new observer slot sits inside __DATA.__common on both binaries (1.0.2 __common: 0x8F9D4C0..0x91F5978, headroom 0xC5B8 past the new slot), and both recipe assertions (ENTRY_SLOT_BASE + CAPACITY*8 <= ZERO_REGION_END, HOOK_SLOT_RVA + 8 <= ZERO_REGION_END) still pass. RVA / prologue capture: Site | 1.0.1 RVA | 1.0.2 RVA | prologue ------------------------------------------|------------|------------|--------- MoveResult.get_CanUseAiSpecialSupport | 0x5B4FE18 | 0x5B54F68 | 00204339 MoveResult.get_...RemainingFreeCount | 0x5B4FDE8 | 0x5B54F38 | 00bc40b9 MoveResult.get_...RemainingTicketCount | 0x5B4FDF8 | 0x5B54F48 | 00c040b9 MatchingPlayer.get_...FreeRemainingCount | 0x5B4BC54 | 0x5B50DA4 | 006040b9 MatchingPlayer.get_...PaidAvailableCount | 0x5B4BC64 | 0x5B50DB4 | 006440b9 All five prologues are pure LDR / LDRB immediate loads (no ADR / ADRP / B / BL), so verbatim relocation into the cave tail is safe. Verified against both assets/1.0.1/dump.cs.index.json and assets/1.0.2/dump.cs.index.json. New shared body — Hook/AiSpecialSupport.m — mirrors the KiouEditor original: feature-off => forward to orig, feature-on => CanUse -> true and the 4 counts pinned to KIOU_AI_SPECIAL_SUPPORT_MAX_COUNT (255). The single KIOUEditorInstallAiSpecialSupportHook installer wires all 5 sites through KIOUHookInstall + KIOU_HOOK_PUBLISH_SLOT. Build-verified for chinlan 1.0.2: all 40 SITEs patch, the assertions pass, the IPA repacks. Runtime blast radius worth flagging: the observer-slot move relocates the slot the 5 CAVE_OBSERVER kifu-end hooks ride. The prior 0x091E92B8 placement was device-tested; the new 0x091E93B8 is an in-section slide (same __common, same zero-fill, inside the declared boundary), so the extrapolation is sound — but a device re-verification of the kifu-autosave flow (AI / CPUStream / LocalPvP / OnlinePvP / RecordReplay OnMatchEndAsync) would be prudent before signing off. KiouForge picks up the new slot automatically on its next build (recipe-regenerated). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…PATH env The shared recipe hard-codes DYLIB_PATH to @executable_path/Frameworks/KiouForge.dylib because KiouForge is the primary consumer. But every other consumer that reuses this recipe (KiouEditor being the current live example) ships its own dylib basename, and the fixed LC_LOAD_DYLIB target would fail to resolve at runtime — the injected load command would look for KiouForge.dylib in Frameworks while the tweak dropped e.g. KiouEditor.dylib there. Read KIOU_HOOK_DYLIB_PATH from the environment when finalizing the recipe. A value starting with @executable_path / @loader_path / @rpath is used verbatim; a bare filename gets the standard @executable_path/Frameworks/ prefix so consumers can't accidentally inject a path with no dyld resolver directive. KiouForge stays on the KiouForge.dylib default (unchanged behaviour); KiouEditor now sets KIOU_HOOK_DYLIB_PATH=@executable_path/Frameworks/KiouEditor.dylib in its Makefile before invoking build_patched_ipa.sh. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The KiouEditor-block RVA macros in KIOUHook.h were pinned to 1.0.1
because the tweak historically shipped only for 1.0.1. The refactor
branch retargeted KiouEditor at 1.0.2 (recipes/v1_0_2.py is now the
authoritative site table), but the RVA macros were left at their
1.0.1 values — the header comment even called this out ("KiouEditor
is 1.0.1-only"). Nobody caught it while wiring the chinlan pipeline
because chinlan reads sites from the recipe, not from the macros.
On JB / jailed, KIOUHookInstall goes through
MSHookFunction(unityBase + KIOU_HOOK_RVA_*, replacement) — so the
1.0.1 addresses applied to a 1.0.2 UnityFramework land on unrelated
methods. Verified on-device: with the 1.0.1
KIOU_HOOK_RVA_TITLE_SCENE_MOVENEXT (0x5DCC728), MSHookFunction
overwrote the first bytes of
TitleMenuPopupPresenter.<>c.<<ShowTitleMenuPopupAsync>b__0_7 on
1.0.2 (which lives at 0x5DCC728 +0x20 on that binary). The next
time the game showed a title-menu popup, control ran off into the
corrupted region and died with EXC_BAD_ACCESS at a wild PC. Two
crash reports (KIOU-2026-07-10-033358.ips and -033521.ips) captured
this exact stack — TitleMenuPopupPresenter.MoveNext +0x140 crashing
into imageIndex=? / imageOffset=0.
Repin every KiouEditor entry from recipes/v1_0_2.py SITES:
SYNC_ITEM_LIST_MERGE 0x5C37034 -> 0x5C3C29C
COLLECTION_PRESET_MERGE 0x5C4065C -> 0x5C458C4
SELECT_CHAR_ASYNC 0x5CA7C90 -> 0x5CACEF8
SELECT_CHAR_REPLY_MERGE 0x5C26DCC -> 0x5C2C034
MATCHING_PLAYER_MERGE 0x5B4CAEC -> 0x5B51C3C
HISTORY_DETAIL_MERGE 0x5C01328 -> 0x5C06590
HISTORY_GET_PREMIUM 0x5C00D88 -> 0x5C05FF0
KIFU_DETAIL_IS_PREMIUM 0x585B25C -> 0x585E000
VOICE_PLAYER_SATISFIES 0x582B88C -> 0x582E614
VOICE_CELL_GET_IS_LOCKED 0x584ADC0 -> 0x584DB64
BSE_CTOR 0x597A448 -> 0x597E608
BSE_ENSURE_INITIALIZED 0x597BAFC -> 0x5980890
RBSUPPORT_GET_ENABLED 0x593E630 -> 0x5942AA0
RBSUPPORT_GET_DEPTH 0x593E650 -> 0x5942AC0
HOME_UTILITY_PRESENTER_CTOR 0x5A9F298 -> 0x5AA4054
UIBUTTONBASE_ONPOINTERCLICK 0x5DD1E08 -> 0x5DD7F54
TITLE_SCENE_MOVENEXT 0x5DCC728 -> 0x5DD2874
GAME_ORCHESTRATOR_IS_AFK 0x59455D4 -> 0x594A034
BSE_EVALUATE_ASYNC 0x597B570 -> 0x5980304
MOVE_RESULT_CAN_USE_SPECIAL 0x5B4FE18 -> 0x5B54F68
MOVE_RESULT_FREE_REMAINING 0x5B4FDE8 -> 0x5B54F38
MOVE_RESULT_TICKET_REMAINING 0x5B4FDF8 -> 0x5B54F48
MP_FREE_REMAINING 0x5B4BC54 -> 0x5B50DA4
MP_PAID_AVAILABLE 0x5B4BC64 -> 0x5B50DB4
Also repin the direct-ABI helper block that Hook/AssistTune.m and
Hook/FriendUnhideBridge.m look up via KIOUHookSiteAddr for verbatim
direct calls (not hooks). Wrong RVAs here corrupt at call time in
both chinlan and JB builds because the helpers are used from the
shared hook bodies:
NSS_SETHASHSIZE_DIRECT 0x5D320E0 -> 0x5D379DC
GAMEOBJECT_GETCOMPONENT 0x6BCA6AC -> 0x6BD07F8
RTU_WORLDTOSCREENPOINT 0x6F20040 -> 0x6F2628C
The NSS_SETHASHSIZE_DIRECT and NSS_SETHASHSIZE now resolve to the
same underlying method on 1.0.2 (0x5D379DC). Keeping both catalog
entries: NSS_SETHASHSIZE stays hook_id 1 (currently uninstalled by
KiouEditor; still catalogued for KiouForge and future work), while
NSS_SETHASHSIZE_DIRECT stays hook_id -1 so KIOUHookInstall on the
DIRECT name is a no-op even under the JB pipeline.
Update the comment header to reflect the 1.0.2 target and to record
the on-device failure mode so the next person who touches this
block doesn't repeat the drift.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…t literals Two direct-ABI calls in the friend-unhide bridge — GameObject.GetComponent (string) and RectTransformUtility.WorldToScreenPoint — were computed as g_unityBaseAddr + <hard-coded RVA literal>. Both literals were 1.0.1 values (0x6BCA6AC / 0x6F20040), so on 1.0.2 they land inside unrelated methods. GetComponent(string) fell into what looks like a blocking runtime path (deterministic-hang on the first title-menu sprite recon, per on-device IPALog tail: the last line before freeze is always "[SPRITE-RECON title-menu] imageTf=%p imageGo=%p", i.e. the log immediately preceding the componentByTypeName call), and the app would spin forever without producing a crash report. Route both direct calls through KIOUHookSiteAddr against the version-appropriate catalog names (KIOU_HOOK_NAME_GAMEOBJECT_GETCOMPONENT and KIOU_HOOK_NAME_RTU_WORLDTOSCREENPOINT) so they pick up the same 1.0.2 RVAs KIOUHook.h just repinned. Belt-and-braces null-check on the resolved address before the cast — if the catalog name ever goes stale we degrade to a no-op instead of jumping to unityBase+0. Also drop the two long-dead RVA_HOME_UTILITY_PRESENTER_CTOR / RVA_UIBUTTONBASE_ONPOINTERCLICK #defines at the top of Hook/FriendUnhide.m — both were only kept as reference constants, both were 1.0.1 values, both never expanded anywhere. Removing them prevents the next audit from asking whether they were the freeze cause. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
KiouEditor is porting KiouForge's Kif/ pipeline. Both consumers use the same KIOUEditorFeatureEnabled surface, so the master toggle (the one users see labelled "Kifu Autosave" in the settings sheet) needs a KiouFeature slot. Slot it between AI_SPECIAL_SUPPORT and KIOU_FEATURE_COUNT so existing feature IDs stay pinned. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Ported from KiouEngineBridge Hook_MatchingFilterObserve.m. The
"First Player Only" filter lets KiouEditor reject a MatchFound
whose IsFirstPlayer disagrees with the user's picked seat and ask
the matching server to re-queue via ConnectionFailed. Three
CAVE_ENTRY sites cover the flow:
MatchingHandler.GetValidMatchFoundStatus (0x5D0A78C)
Called once per MatchFound. The hook body reads
IsFirstPlayer, compares to the seat pref, and — on mismatch —
dispatches an IShogiMatchStreamArgs.Create + SendAsync pair.
MatchingHandler+<ReceiveWithTimeoutAsync>d__6.MoveNext
(0x5D0C408)
Fires as the async state machine resumes. The hook body reads
the boxed state machine's stream field (approximate offset
0x40 = struct 0x30 + 0x10 il2cpp header) so hook 1's re-queue
path has a live IStreamHandler to send on.
IShogiMatchStreamArgs.Create (0x5BCF8CC)
Observes every JoinQueue / LeaveQueue / ConnectionFailed the
game sends. On JoinQueue the hook body caches matchType /
rankRule / eventRule / mstEventMatchId / enableBeginnerSupport
so the re-queue path can rebuild an args object with the same
settings the user originally picked.
All three prologues are non-PC-relative on 1.0.2 (SUB SP / STP)
so verbatim relocation into the cave tail is safe. The three
hooks are appended before KIOU_HOOK_ID__COUNT so all downstream
IDs stay pinned.
ENTRY_SLOT_COUNT bumps 35 -> 38; ENTRY_SLOT_CAPACITY bumps 40 -> 48
so the entry-slot table has sibling room for future matching /
observer hooks without another observer-slot slide. The observer
slot (0x091E93B8) stays where it is — 48 × 8 = 0x180 past
ENTRY_SLOT_BASE = 0x091E9338 << 0x091E93B8 (0xC0 headroom).
KIOU_FEATURE_SEAT_FILTER is appended to the shared feature enum
so KiouEditor's Hook/MatchingFilter.m body can gate itself on the
same KIOUEditorFeatureEnabled surface every other consumer uses.
Default off — the filter is a competitive-match tampering surface
and users should opt in explicitly.
The v1_0_1 recipe intentionally does NOT get these sites — the
KiouEngineBridge port assumes 1.0.2 addressing and KiouEditor no
longer ships 1.0.1 by default. When someone actually needs the
1.0.1 path we can back-port the RVAs then.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
tkgstrator
force-pushed
the
feat/kioueditor-alignment
branch
from
July 27, 2026 17:50
e5eafd7 to
69c5b02
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Small surface additions to KIOU-Hook that unblock KiouEditor from consuming this library via
vendor/KIOU-Hook+ Chinlan (mirroring KiouForge's pattern).Changes
1. Extend
KiouFeatureenum with two consumer-owned flagsKIOU_FEATURE_INGAME_ANALYSIS— for KiouEditor'sHook_AssistTuneBSE.EvaluateAsync suppression (CPU/heat control).KIOU_FEATURE_AI_SPECIAL_SUPPORT— for KiouEditor'sHook_AiSpecialSupport棋桜覚醒 unlock (default off).The hook bodies that consume these flags live in the consumer tweak (KiouEditor) because the sites are tweak-local and not part of the shared catalog. Placing the enum values here lets
KIOUEditorFeatureEnabledgate them uniformly with the shared 7.Hook/Common.m's weakKIOUEditorFeatureLabelstub gains matching cases.2. Make
KIOU_HOOK_CAVE_REGION_STARToverridableWrap the macro in
#ifndefso consumers targeting a different KIOU binary version can override on the compiler command line. Default (0x826F5E8) still matchesv1_0_2/ KiouForge; KiouEditor at 1.0.1 will set-DKIOU_HOOK_CAVE_REGION_START=0x8268024in its Makefile (matchingrecipes/v1_0_1.py'sCAVE_REGION[0]).What this does NOT do
No RVA additions, no slot table changes, no recipe cascade. The existing 11 KiouEditor-flavored shared hooks (Afk, AssistEnable, Collection, FriendUnhide, MatchingPlayer, PremiumUnlock, SelectCharacter, SyncItemList, Version, VoiceUnlock, plus the AssistTune ctor/EnsureInit pair) already sit in the 32-slot entry table with 1.0.1 RVAs live in the catalog — this PR does not touch that surface.
Test plan
KIOUEditorFeatureLabelweak stub without functional change (it doesn't consume Hook/Common.h's enum in its own code).🤖 Generated with Claude Code