Skip to content

feat(recipes): add KIOU 1.1.0 target and per-version RVA headers - #15

Merged
tkgstrator merged 20 commits into
masterfrom
feat/kioueditor-alignment
Jul 31, 2026
Merged

feat(recipes): add KIOU 1.1.0 target and per-version RVA headers#15
tkgstrator merged 20 commits into
masterfrom
feat/kioueditor-alignment

Conversation

@tkgstrator

Copy link
Copy Markdown
Contributor

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 of KIOUHook.h into
    generated 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 two
    tables can no longer drift apart.
  • Earlier commits on this branch: NSS Search* / SetOption sites, the gRPC
    wire-log catalog, and the matching-filter SendAsync site.

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, TryBorrowSession and
    the NativeSyncSession field are gone, and with them the hash-size
    knob AssistTune applied there. The 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 reshaped: <= 1.0.2 carries _analysisDepth /
    _engineSkillLevel, >= 1.1.0 carries _normalHintTopN /
    _firstHintTopN. AssistTune now writes whichever pair the target has.

Test plan

  • ruff check recipes/ tools/ — clean
  • pyright recipes/ tools/ — 0 errors (3 pre-existing cross-repo import warnings)
  • python3 -m tools.check_recipesHOOK_IDS=56 ENTRY_SLOT_INDEX=51 ENTRY_SLOT_COUNT=51 recipes=3 sites=151
  • Patched IPA built against 1.1.0 and verified on device by the author

🤖 Generated with Claude Code

tkgstrator and others added 20 commits July 9, 2026 02:22
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>
@tkgstrator
tkgstrator merged commit 8ab5868 into master Jul 31, 2026
4 checks passed
@tkgstrator
tkgstrator deleted the feat/kioueditor-alignment branch July 31, 2026 23:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant