Skip to content

feat: enable KiouEditor as a Chinlan consumer - #13

Closed
tkgstrator wants to merge 36 commits into
masterfrom
feat/kioueditor-alignment
Closed

feat: enable KiouEditor as a Chinlan consumer#13
tkgstrator wants to merge 36 commits into
masterfrom
feat/kioueditor-alignment

Conversation

@tkgstrator

Copy link
Copy Markdown
Contributor

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 KiouFeature enum with two consumer-owned flags

  • KIOU_FEATURE_INGAME_ANALYSIS — for KiouEditor's Hook_AssistTune BSE.EvaluateAsync suppression (CPU/heat control).
  • KIOU_FEATURE_AI_SPECIAL_SUPPORT — for KiouEditor's Hook_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 KIOUEditorFeatureEnabled gate them uniformly with the shared 7. Hook/Common.m's weak KIOUEditorFeatureLabel stub gains matching cases.

2. Make KIOU_HOOK_CAVE_REGION_START overridable

Wrap the macro 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 will set -DKIOU_HOOK_CAVE_REGION_START=0x8268024 in its Makefile (matching recipes/v1_0_1.py's CAVE_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

  • Local KIOU-Hook builds cleanly (no consumers modified).
  • KiouForge (1.0.2) rebuild after merge: should pick up the new enum values in KIOUEditorFeatureLabel weak stub without functional change (it doesn't consume Hook/Common.h's enum in its own code).
  • KiouEditor follow-up PR consumes this branch to migrate to Chinlan + dedup 11 hooks.

🤖 Generated with Claude Code

tkgstrator and others added 30 commits June 25, 2026 15:47
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>
tkgstrator and others added 6 commits July 9, 2026 16:34
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 tkgstrator closed this Jul 27, 2026
@tkgstrator
tkgstrator force-pushed the feat/kioueditor-alignment branch from e5eafd7 to 69c5b02 Compare July 27, 2026 17:50
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