feat(recipes): add KIOU 1.1.0 target and per-version RVA headers - #15
Merged
Conversation
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>
…stallers Both installers wired KIOUHookInstall for their hook names but never called KIOU_HOOK_PUBLISH_SLOT on chinlan, leaving cave entries for HTTPMSGINVOKER_ SEND_ASYNC, HEADER_PROVIDER_SET_OR_UPDATE_HEADER, ACCOUNT_EXISTS, LOGIN_ ARGS_CREATE, REGISTER_USER_ARGS_CREATE, RUN_LOGIN_SEQ_MOVENEXT, and GET_SELF_PROFILE_MOVENEXT pointing at a NULL slot. First call into any of them (HTTPMSGINVOKER fires at Unity boot; ACCOUNT_EXISTS during Steam token verify on the account-switch path) BLR X16=0 -> PC=0 -> EXC_BAD_ACCESS. Add the seven PUBLISH_SLOT calls right after each KIOUHookInstall, matching the pattern the KiouEditor-flavored installers already use. JB / jailed builds ignore the macro; only chinlan materialises the slot address. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Every hook body now IPALogs on entry with the state that gates its rewrite
path, so the first line in the device log tells us whether the site is
being hit and why the body took its branch:
* MatchingPlayer.merge — fire log with userId + skin/char + persisted
even before the cpu-sentinel / non-self early returns, so silent
skips are visible.
* SelectCharacter.Async / .ReplyMerge — fire log with feature-gate
state + requested/persisted so pass-through cases are visible.
* SyncItemListReply.merge — fire log with all three feature gates +
persisted + reentrant guard state, so re-entrant skips are visible.
Bodies are unchanged; this is diagnostic-only.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
hook_MatchingPlayer_merge locked in self_user_id via a one-shot heuristic
(carry-the-SAFE_ID → this is us) and stored it in NSUserDefaults. Once
captured, the value survived reinstalls and account switches, so a device
that saw account A first would keep checking every subsequent match's
userId against A even after the user signed in as B. Every incoming
ShogiMatchingPlayerStatus for B → self_user_id mismatch → "[MATCH] skip
non-self" → no beginner-support flip and no character rewrite.
Reconcile against KIOUActiveAccountUserId (already refreshed on each
AccountExists observation via AccountObserve.m):
* active vs locked-in disagree → adopt active as the new self
([MATCH] self_user_id stale (X) -> refresh to active (Y))
* no locked-in but active known → adopt active as first-time capture
([MATCH] self_user_id adopted from active: X)
* neither known → fall through to the existing skin==SAFE_ID heuristic
Verified on iPad: previously every match log line was skip non-self for
a stale 019f4b17-... UUID; after the fix the first match logs the stale
→ refresh transition, and subsequent matches take the self-rewrite path.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two orthogonal changes bundled because both touch Account/Persistence.m:
* refactor(log): rewrite every IPALog format string to conform to a
single grammar — `[TAG] verb: k=v k=v ...`. Verbs drawn from a
small vocabulary (fire, installed, resolved, applied, saved,
deleted, refresh, cached, published, skipped, evicted, updated,
hooked, exception, swapped, armed, disarmed). Tags normalised to
ALL-CAPS, hyphen-separated; nested `[TAG][SUB]` merged into
`[TAG-SUB]`. Boot markers (`=== KiouEditor loaded ===` etc.) kept
as-is.
* fix(account): guard KIOUSaveAccount from re-emitting the
`[ACCOUNT] saved: ...` line when the merged record is
byte-identical to what's already stored. RunLoginSequence and
AccountExists both fire for the same account within ~60 ms on
boot; without the guard the log showed the same "saved" event
twice back-to-back.
Bodies unchanged apart from the noop-merge guard and the format
strings. Zero behaviour change for the log format edits.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds three accessors so the KiouEditor-flavored FrameworkPassthrough hook
body can read the user-picked NativeSyncSession NodesLimit preset without
reaching directly into the Persistence NSUserDefaults key:
* KIOUEditorAssistNodesIndex() — current preset index
* KIOUEditorSetAssistNodesIndex(idx)
* KIOUEditorAssistNodesLimit() — 0 when the preset is Off, otherwise
the raw node ceiling
The Persistence side (Sources/KiouEditor/Persistence.m) already ships the
seven-slot preset table (Off / 1M / 2M / 3M / 5M / 7M / 10M) and the
default (idx 4 → 5M) that matches the previously hardcoded clamp.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds KIOU_HOOK_ID_MATCH_STREAM_HANDLER_SEND_ASYNC (id 44, entry slot 39) so consumer tweaks can hook the game's every-3s Heartbeat / JoinQueue / ConnectionFailed / LeaveQueue writes on the matching stream. The immediate consumer is KiouEditor's seat filter, which uses the hook to snapshot the MethodInfo pointer il2cpp passes as arg 3 — its own reject branch calls SendAsync directly and needs a valid MethodInfo to avoid crashing inside the il2cpp method body. - Prologue bytes at 0x5BD00E0: f657bda9 (STP X22,X21,[SP,#-0x30]!). - Bumps ENTRY_SLOT_COUNT 39 -> 40 (well under ENTRY_SLOT_CAPACITY = 48).
Add four Google.Protobuf serialize/parse bottlenecks so consumer tweaks
can hook every gRPC request and reply from a single installer. Sites
verified against IngameService.__Helper_SerializeMessage /
DeserializeMessage BL disassembly (1.0.2):
outbound: ToByteArray (0x52C071C) or WriteTo(IBufferWriter) (0x52C0DB8)
inbound : MergeFrom(msg, ROSeq, bool, reg) (0x52C042C);
MergeFrom(CIS) (0x52C1B18) as a residual-path backstop
Adds IDs 45-48 + entry slots 40-43 (ENTRY_SLOT_COUNT 40->44), the four
KIOU_HOOK_NAME_MSG_* string constants, RVA macros, and catalog rows.
No hook body — consumers publish their own.
BSE.EvaluateAsync fans out over legal candidates via SearchMulti / SearchMultiWithPV rather than the single-position SearchFull that FrameworkPassthrough already covers, so engine invocations were invisible to the logger. Adds 6 CAVE_ENTRY sites (Search, SearchMulti, SearchMultiPV, SearchMultiWithPV, SearchMultiPVWithPV, SetOption) plus ShogiMatchStreamHandler.DisposeAsync, with matching enum/name/catalog rows and recipe entries for 1.0.1 and 1.0.2. Also caches the (session, hashMB) tuple in hook_BSE_ensureInit so a 256 MB transposition table is not re-zeroed on every move, exposes a MultiPV cap accessor, and forwards the BSE evalPath to the consumer for USI option dumps. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds recipes/v1_1_0.py (56 sites, build 15) and moves the RVA catalog out of KIOUHook.h into generated rva/kiou_rva_<ver>.h headers, picked at compile time by KIOU_HOOK_TARGET_BUILD. gen_rva_header.py emits them from a recipe so the two can no longer drift apart by hand. 1.1.0 dropped BeginnerSupportEvaluator.EnsureInitializedLocked, TryBorrowSession and the NativeSyncSession field, and with them the hash-size knob AssistTune applied there. Its row stays as a site = None placeholder so every later hook keeps its cave index, and the hook body compiles out on build >= 15. The BSE ctor also reshaped: <= 1.0.2 carries _analysisDepth / _engineSkillLevel, while >= 1.1.0 carries _normalHintTopN / _firstHintTopN, so AssistTune now writes whichever pair the target build actually has. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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.
What
Brings the shared hook catalog up to KIOU 1.1.0 (build 15) and stops the
RVA tables from being maintained by hand.
recipes/v1_1_0.py— 56 sites for build 15.rva/kiou_rva_<ver>.h— the RVA catalog moves out ofKIOUHook.hintogenerated per-version headers, selected at compile time by
KIOU_HOOK_TARGET_BUILD.tools/gen_rva_header.py— emits those headers from a recipe, so the twotables can no longer drift apart.
Search*/SetOptionsites, the gRPCwire-log catalog, and the matching-filter
SendAsyncsite.Why
Every RVA moves on a new build, and the catalog previously lived in two
places that had to be edited in lock-step. Generating one from the other
removes that class of mistake.
Two shapes actually changed in 1.1.0, not just addresses:
BeginnerSupportEvaluator.EnsureInitializedLocked,TryBorrowSessionandthe
NativeSyncSessionfield are gone, and with them the hash-sizeknob
AssistTuneapplied there. The row stays as asite = Noneplaceholder so every later hook keeps its cave index, and the hook body
compiles out on build >= 15.
<= 1.0.2carries_analysisDepth/_engineSkillLevel,>= 1.1.0carries_normalHintTopN/_firstHintTopN.AssistTunenow writes whichever pair the target has.Test plan
ruff check recipes/ tools/— cleanpyright recipes/ tools/— 0 errors (3 pre-existing cross-repo import warnings)python3 -m tools.check_recipes—HOOK_IDS=56 ENTRY_SLOT_INDEX=51 ENTRY_SLOT_COUNT=51 recipes=3 sites=151🤖 Generated with Claude Code