From 8caa38d362589ea67bd299eae25a818e4cec4e43 Mon Sep 17 00:00:00 2001 From: tkgstrator Date: Wed, 1 Jul 2026 07:59:52 +0000 Subject: [PATCH 1/9] feat(dispatcher): wire HeaderProvider entry slot; bump KIOU-Hook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wire the new HeaderProvider.SetOrUpdateHeader entry slot in KiouForge's chinlan dispatcher and bump the KIOU-Hook submodule to carry the catalog row and hook body. Dispatcher gains one extern (KFHookHeaderProviderSetOrUpdate) and one entry_slot assignment at KIOU_HOOK_SLOT_HEADER_PROVIDER_SET_OR_UPDATE_ HEADER. Nothing else in the dispatcher changes. KIOU-Hook submodule bump brings: * Static-method typedef fix for BackToTitleSequence.RunAsync, RunResetUserDataSequenceAsync, and RunDeleteAccountSequenceAsync (previously (ct)-only, now (ct, mi) with fn(NULL, NULL) at call sites). * Catalog + recipe + Hook/GrpcLogging.m rewrite moving the x-user-id swap from HttpMessageInvoker.SendAsync to Project.Network. HeaderProvider.SetOrUpdateHeader — a managed-only site upstream of the Cysharp Yaha borrow that the SendAsync hook could not survive. Verified end-to-end on 1.0.2 build 12 chinlan (jailbroken, TrollStore- installed IPA): account switch completes without crash, [HEADER] x-user-id swapped fires, and the follow-up LoginReply / SelfProfile return the target user (openUserId changes from 1054-1001-0520-9241 to 2620-3831-5122-4152 across the switch). Co-Authored-By: Claude Opus 4.7 --- Sources/KiouForge/ChinlanDispatcher.m | 22 ++++++++++++---------- vendor/KIOU-Hook | 2 +- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Sources/KiouForge/ChinlanDispatcher.m b/Sources/KiouForge/ChinlanDispatcher.m index 27dceae..af8fda3 100644 --- a/Sources/KiouForge/ChinlanDispatcher.m +++ b/Sources/KiouForge/ChinlanDispatcher.m @@ -40,16 +40,17 @@ // replace orig; the body calls the cave bypass (resolved by KIOUHookOrig) // to invoke the original method when needed. // --------------------------------------------------------------------------- -extern void KFHookSetTargetFrameRateEntry(int32_t value, void *mi); -extern void KFHookNSSSetHashSizeEntry(void *self, int32_t mb, void *mi); -extern void KFHookNSSSetSkillLevelEntry(void *self, int32_t level, void *mi); -extern void *KFHookNSSSearchFullEntry(void *self, void *sfen, int32_t depth, void *mi); -extern bool KFHookAccountExists(void *data); -extern void *KFHookLoginArgsCreate(void *deviceId, void *distinctId); -extern void *KFHookRegisterUserArgsCreate(void *userName, void *distinctId); -extern void KFHookRunLoginSeqMoveNext(void *self); -extern void KFHookGetSelfProfileMoveNext(void *self); -extern void *KFHookHttpMsgInvokerSendAsync(void *self, void *request, void *ct); +extern void KFHookSetTargetFrameRateEntry(int32_t value, void *mi); +extern void KFHookNSSSetHashSizeEntry(void *self, int32_t mb, void *mi); +extern void KFHookNSSSetSkillLevelEntry(void *self, int32_t level, void *mi); +extern KFSyncSearchResult KFHookNSSSearchFullEntry(void *self, void *sfen, int32_t depth, void *mi); +extern bool KFHookAccountExists(void *data, void *mi); +extern void * KFHookLoginArgsCreate(void *deviceId, void *distinctId, void *mi); +extern void * KFHookRegisterUserArgsCreate(void *userName, void *distinctId, void *mi); +extern void KFHookRunLoginSeqMoveNext(void *self, void *mi); +extern void KFHookGetSelfProfileMoveNext(void *self, void *mi); +extern void * KFHookHttpMsgInvokerSendAsync(void *self, void *request, void *ct, void *mi); +extern void KFHookHeaderProviderSetOrUpdate(void *self, void *keyStr, void *valueStr, void *mi); // --------------------------------------------------------------------------- // dispatch_one — single shared observer slot, switches on W6=hook_id. @@ -123,6 +124,7 @@ void KFChinlanPublish(uintptr_t unityBase) { entrySlots[KIOU_HOOK_SLOT_RUN_LOGIN_SEQ_MOVENEXT] = (void *)&KFHookRunLoginSeqMoveNext; entrySlots[KIOU_HOOK_SLOT_GET_SELF_PROFILE_MOVENEXT] = (void *)&KFHookGetSelfProfileMoveNext; entrySlots[KIOU_HOOK_SLOT_HTTPMSGINVOKER_SEND_ASYNC] = (void *)&KFHookHttpMsgInvokerSendAsync; + entrySlots[KIOU_HOOK_SLOT_HEADER_PROVIDER_SET_OR_UPDATE_HEADER] = (void *)&KFHookHeaderProviderSetOrUpdate; IPALog([NSString stringWithFormat: @"[CHINLAN] dispatcher=%p observer slot=%p (unityBase+0x%lx) " diff --git a/vendor/KIOU-Hook b/vendor/KIOU-Hook index a47be0a..41b2a00 160000 --- a/vendor/KIOU-Hook +++ b/vendor/KIOU-Hook @@ -1 +1 @@ -Subproject commit a47be0afcbfa38a98d7096727795004d325b46bc +Subproject commit 41b2a00cbbe8961f49d9c017be13de9eec652369 From e983ebf85b94319c3109444275d6c3ca0f6e8449 Mon Sep 17 00:00:00 2001 From: tkgstrator Date: Wed, 1 Jul 2026 08:00:04 +0000 Subject: [PATCH 2/9] feat(makefile): add make deploy target + BUNDLE_ID_SUFFIX; bump shared MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a make deploy target that builds the patched IPA, scps it to the device, installs via trollstorehelper install force, and launches the app. Kept separate from Theos's install:: so `make deploy` does not also trigger the JB rootless .deb install as a side effect. Introduce BUNDLE_ID_SUFFIX. Empty (default) leaves CFBundleIdentifier untouched — the patched IPA overwrites the original app. Setting e.g. BUNDLE_ID_SUFFIX=chinlan appends ".chinlan" to the bundle id at IPA build time, so the patched IPA installs side-by-side with the original App Store / Sideloadly copy of the same app. INSTALLED_IPA_BUNDLE_ID composes the launch target from TARGET_BUNDLE_ ID + suffix automatically. Related overrides (all .env-friendly): * TROLLSTORE_HELPER — /var/jb/Applications/TrollStorePersistenceHelper. app/trollstorehelper (stable rootless path) * INSTALLED_IPA_BUNDLE_ID — TARGET_BUNDLE_ID + optional suffix * DEVICE_USER — root Requires the shared submodule bump so build_patched_ipa.sh honours --bundle-id-suffix end-to-end. Co-Authored-By: Claude Opus 4.7 --- Makefile | 50 ++++++++++++++++++++++++++++++++++++++++++++------ shared | 2 +- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 88df2d6..10cd2eb 100644 --- a/Makefile +++ b/Makefile @@ -26,6 +26,12 @@ IPA_RECIPE := recipes.__init__ KIOU_HOOK_DIR := $(CURDIR)/vendor/KIOU-Hook IPA_FRAMEWORK := UnityFramework +# Optional CFBundleIdentifier suffix. Empty (default) leaves the bundle +# id alone so the patched IPA overwrites the original app; set e.g. +# BUNDLE_ID_SUFFIX=chinlan in .env or on the command line to append +# ".chinlan" and install alongside the original. +BUNDLE_ID_SUFFIX ?= + BUILD_COMMIT_DEFINE := KIOU_FORGE_COMMIT # --------------------------------------------------------------------------- @@ -36,7 +42,7 @@ INSTALL_TARGET_PROCESSES := $(TARGET_PROCESS) ARCHS := arm64 THEOS_PACKAGE_SCHEME := rootless -include .env -THEOS_DEVICE_IP ?= 192.168.0.49 +THEOS_DEVICE_IP ?= 192.168.0.30 include $(THEOS)/makefiles/common.mk @@ -116,6 +122,8 @@ chinlan:: IPA_DYLIB := $(CURDIR)/packages/chinlan/$(TWEAK_NAME).dylib +IPA_OUT := $(CURDIR)/packages/ipa/$(basename $(notdir $(DECRYPTED_IPA)))-patched.ipa + ipa:: chinlan @echo "==> assembling patched IPA from $(DECRYPTED_IPA) (v$(TARGET_VERSION))" @if [ ! -f "$(DECRYPTED_IPA)" ]; then \ @@ -126,11 +134,41 @@ ipa:: chinlan @TARGET_VERSION="$(TARGET_VERSION)" \ PYTHONPATH="$(KIOU_HOOK_DIR):$$PYTHONPATH" \ ./shared/tools/build_patched_ipa.sh \ - --recipe "$(IPA_RECIPE)" \ - --framework "$(IPA_FRAMEWORK)" \ - --dylib "$(IPA_DYLIB)" \ - --input "$(DECRYPTED_IPA)" \ - --output "$(CURDIR)/packages/ipa/$(basename $(notdir $(DECRYPTED_IPA)))-patched.ipa" + --recipe "$(IPA_RECIPE)" \ + --framework "$(IPA_FRAMEWORK)" \ + --dylib "$(IPA_DYLIB)" \ + --input "$(DECRYPTED_IPA)" \ + --output "$(IPA_OUT)" \ + --bundle-id-suffix "$(BUNDLE_ID_SUFFIX)" + +# --------------------------------------------------------------------------- +# TrollStore-backed IPA deploy on a JB device. +# Ships the patched IPA to the device via SSH, installs it through +# trollstorehelper (force flag, so it overrides an existing Sideloadly / +# AltStore build with the same bundle id), and relaunches the app. +# Kept separate from Theos's own `install::` (JB rootless .deb install) +# so `make deploy` targets only the IPA path and doesn't drag in the +# JB dpkg install as a side effect. +# +# Override on the command line or in .env: +# TROLLSTORE_HELPER — trollstorehelper binary path on the device +# INSTALLED_IPA_BUNDLE_ID — bundle id used to relaunch the app +# DEVICE_USER — SSH user (defaults to root) +# --------------------------------------------------------------------------- +TROLLSTORE_HELPER ?= /var/jb/Applications/TrollStorePersistenceHelper.app/trollstorehelper +INSTALLED_IPA_BUNDLE_ID ?= $(TARGET_BUNDLE_ID)$(if $(BUNDLE_ID_SUFFIX),.$(BUNDLE_ID_SUFFIX),) +DEVICE_USER ?= root + +.PHONY: deploy +deploy: ipa + @echo "==> scp $(notdir $(IPA_OUT)) -> $(DEVICE_USER)@$(THEOS_DEVICE_IP):/tmp/" + @scp -q $(IPA_OUT) $(DEVICE_USER)@$(THEOS_DEVICE_IP):/tmp/$(notdir $(IPA_OUT)) + @echo "==> trollstorehelper install force /tmp/$(notdir $(IPA_OUT))" + @ssh $(DEVICE_USER)@$(THEOS_DEVICE_IP) '$(TROLLSTORE_HELPER) install force /tmp/$(notdir $(IPA_OUT))' + @echo "==> launching $(TARGET_PROCESS) ($(INSTALLED_IPA_BUNDLE_ID))" + @ssh $(DEVICE_USER)@$(THEOS_DEVICE_IP) 'sleep 1; (open $(INSTALLED_IPA_BUNDLE_ID) 2>/dev/null \ + || uiopen $(INSTALLED_IPA_BUNDLE_ID):// 2>/dev/null \ + || echo "no launcher tool; start $(TARGET_PROCESS) manually")' .PHONY: hooks hooks:: diff --git a/shared b/shared index 5ee8890..c29a372 160000 --- a/shared +++ b/shared @@ -1 +1 @@ -Subproject commit 5ee8890cdb8a604b9029b0a4d93d2417a6205261 +Subproject commit c29a372c7cd1d447c1f12a8b580986aba327e9ef From da64dfea2b8cad18e5c7032bdd8eb067ab83576a Mon Sep 17 00:00:00 2001 From: tkgstrator Date: Wed, 1 Jul 2026 08:04:49 +0000 Subject: [PATCH 3/9] feat(claude): add tweak crash repair agents and devcontainer tooling Add specialized Claude agents, a fix-tweak skill, and crash collection script for KIOU-Hook Tweak diagnosis and repair. Update the devcontainer to support the workflow dependencies and runtime setup. Co-Authored-By: Claude Opus 4.8 (1M context) --- .claude/agents/cave-rva-verifier.md | 160 ++++++++++++++++++++ .claude/agents/crash-log-analyzer.md | 128 ++++++++++++++++ .claude/agents/tweak-fixer.md | 104 +++++++++++++ .claude/scripts/pull_crash.sh | 176 ++++++++++++++++++++++ .claude/skills/fix-tweak/SKILL.md | 215 +++++++++++++++++++++++++++ .devcontainer/Dockerfile | 79 ++++++---- .devcontainer/compose.yaml | 12 ++ .devcontainer/devcontainer.json | 4 +- .devcontainer/postCreateCommand.sh | 19 ++- 9 files changed, 865 insertions(+), 32 deletions(-) create mode 100644 .claude/agents/cave-rva-verifier.md create mode 100644 .claude/agents/crash-log-analyzer.md create mode 100644 .claude/agents/tweak-fixer.md create mode 100755 .claude/scripts/pull_crash.sh create mode 100644 .claude/skills/fix-tweak/SKILL.md diff --git a/.claude/agents/cave-rva-verifier.md b/.claude/agents/cave-rva-verifier.md new file mode 100644 index 0000000..9f58099 --- /dev/null +++ b/.claude/agents/cave-rva-verifier.md @@ -0,0 +1,160 @@ +--- +name: cave-rva-verifier +description: | + Verify the integrity of CAVE / RVA / hook-slot wiring for a KIOU-Hook- + based Tweak. Checks the catalog header (KIOUHook.h), the dispatcher + (ChinlanDispatcher.m), the Python recipes (recipes/common.py and + v_*.py), and the per-hook bodies. Cross-references with dump.cs index + files under assets// when available. Read-only; reports + mismatches, never edits. +tools: Read, Grep, Glob, Bash +--- + +# cave-rva-verifier + +You verify that the pieces involved in chinlan-style static cave hooking +are internally consistent. You are read-only. You produce a verdict: +which sites are correctly wired, which look wrong, with concrete +evidence pointing at file:line. + +## Inputs + +The orchestrator (`/fix-tweak`) passes you: + +- `repoRoot`: project root (defaults to current working directory) +- `targetVersion`: KIOU app version (e.g. `1.0.2`) — picks which + `recipes/v_*.py` to cross-check +- Optionally `suspectHooks`: list of hook names (`KIOU_HOOK_NAME_*` or + the corresponding hook id) the upstream analyzer flagged. Focus on + these first, then sweep the rest. + +If `suspectHooks` is empty, do a full sweep. + +## The contract you are verifying + +Four files form a pair-of-truth quartet: + +1. **`vendor/KIOU-Hook/KIOUHook.h`** — `enum kiou_hook_id`, + `enum kiou_hook_slot_id`, `KIOU_HOOK_RVA_*` macros, cave geometry + macros (`KIOU_HOOK_CAVE_*`). +2. **`vendor/KIOU-Hook/KIOUHook.m`** — `kCatalog[]` table mapping + `KIOU_HOOK_NAME_*` strings to (hook_id, site_rva). +3. **`vendor/KIOU-Hook/recipes/common.py`** — `HOOK_IDS`, + `ENTRY_SLOT_INDEX`, `CAVE_PAYLOAD_SIZE`, `build_observer_cave`, + `build_entry_cave`. +4. **`vendor/KIOU-Hook/recipes/v_.py`** — `SITES`, + `CAVE_REGION`, `HOOK_SLOT_RVA`, `ENTRY_SLOT_BASE_RVA`. + +And one consumer: + +5. **`Sources//ChinlanDispatcher.m`** (or equivalent) — `extern` + declarations and the `entrySlots[]` assignments inside + `KFChinlanPublish`. + +A row is *correctly wired* when, for a given hook name: + +- `KIOUHook.h` has both an `enum kiou_hook_id` entry and a + `KIOU_HOOK_RVA_*` macro. +- `KIOUHook.m` has a `kCatalog` row whose `hook_id` matches the enum, + and whose `site_rva` matches the macro by **value**, not just name. +- `recipes/common.py` `HOOK_IDS[]` matches the enum's numerical + value. +- If it's a CAVE_ENTRY hook, `recipes/common.py` `ENTRY_SLOT_INDEX` + has a matching key and the index lies in `[0, ENTRY_SLOT_COUNT)`. +- `recipes/v_.py` `SITES` has a row whose `site_rva` matches the + macro and whose `hook_id_name` matches the enum constant. +- For CAVE_ENTRY hooks, `ChinlanDispatcher.m` `entrySlots[KIOU_HOOK_SLOT_*] + = &` exists, and `` matches the `extern` declared + signature. + +## What you do + +1. Open all five files and load the relevant tables. Use `grep` / + `Grep` for the actual regex extractions: + - `KIOU_HOOK_ID_[A-Z_]+\s*=?\s*\d*` for enum values + - `#define\s+KIOU_HOOK_RVA_[A-Z_]+\s+0x[0-9A-Fa-f]+` for macros + - `KIOU_HOOK_NAME_[A-Z_]+\b` for name constants + - `\"KIOU_HOOK_ID_[A-Z_]+\"\s*:\s*\d+` for Python HOOK_IDS +2. Cross-tabulate. For every hook name that appears in at least one + table, check it appears with consistent values in every place it + should. +3. Sanity-check cave geometry: + - `CAVE_PAYLOAD_SIZE` matches between Python (84) and C + (`KIOU_HOOK_CAVE_SIZE`) + - `KIOU_HOOK_CAVE_REGION_START` matches `recipes/v_*.py CAVE_REGION[0]` + - `KIOU_HOOK_OBSERVER_SLOT_RVA` matches `v_*.py HOOK_SLOT_RVA` + - `KIOU_HOOK_ENTRY_SLOT_BASE_RVA` matches `v_*.py ENTRY_SLOT_BASE_RVA` + - `(CAVE_REGION_START + KIOU_HOOK_ID__COUNT * KIOU_HOOK_CAVE_SIZE)` + fits inside `CAVE_REGION[1]` + - `(ENTRY_SLOT_BASE_RVA + ENTRY_SLOT_CAPACITY * 8)` fits inside + `ZERO_REGION_END_RVA` +4. Sanity-check the dispatcher: + - Every CAVE_ENTRY hook (`HOOK_IDS[name] in range covered by + ENTRY_SLOT_INDEX`) MUST appear on the LHS of an `entrySlots[...]` + assignment in `ChinlanDispatcher.m` + - The number of arguments in each `extern` declaration in + `ChinlanDispatcher.m` SHOULD match the function's actual + definition. The dispatcher's externs are public C declarations + that go into a function pointer; a mismatch is a code smell even + when it links, because it signals the dispatcher hasn't been + updated alongside the hook body (recent example: `MethodInfo*` + was added as a fourth argument to `KFHookHttpMsgInvokerSendAsync` + but the dispatcher's extern still has three). +5. Cross-check with `dump.cs.index.json` when available. If + `assets//dump.cs.index.json` exists, for each RVA in + the catalog, look up the symbol it points at and check it matches + the name the catalog claims (e.g. `KIOU_HOOK_RVA_LOGIN_ARGS_CREATE` + should point at `ILoginArgs.Create` or similar). Surface mismatches. +6. Determine whether each suspect hook is CAVE_ENTRY or CAVE_OBSERVER + by looking at `recipes/v_*.py` SITES — different invariants apply + (observer caves don't need an entry slot table assignment). + +## Output shape + +Return a single JSON object followed by a short prose summary. + +```json +{ + "targetVersion": "1.0.2", + "rowsChecked": 15, + "ok": [ "KIOU_HOOK_NAME_LOGIN_ARGS_CREATE", "..." ], + "issues": [ + { + "hook": "KIOU_HOOK_NAME_HTTPMSGINVOKER_SEND_ASYNC", + "severity": "warning", + "kind": "dispatcher-extern-mismatch", + "where": "Sources/KiouForge/ChinlanDispatcher.m:48", + "expected": "void *KFHookHttpMsgInvokerSendAsync(void *, void *, void *, void *)", + "actual": "void *KFHookHttpMsgInvokerSendAsync(void *, void *, void *)", + "notes": "GrpcLogging.m's definition takes a trailing MethodInfo*; the dispatcher's extern doesn't. Not a linker error but a sign the dispatcher is out of date with the hook body." + } + ], + "geometry": { + "caveRegion": "0x826F5E8..0x8274000 (0x4A18 bytes)", + "caveTotalSpan": "0xB28 bytes (used by 34 hook ids)", + "entrySlotsRegion":"0x91E91B8..0x91F5978 (0xC7C0 bytes)", + "entrySlotsUsed": "0x100 bytes (32 slots × 8B)" + } +} +``` + +Severity levels: +- `error` — guaranteed runtime breakage (e.g. RVA mismatch between + catalog and recipe; an entry slot that's used by a recipe but never + assigned in dispatcher) +- `warning` — likely bug, but might be benign (e.g. extern arity + mismatch where the trailing arg is unused) +- `info` — observation that informs other agents (e.g. unused slot; + hook name present in catalog but absent from recipes) + +## What you must NOT do + +- **Do not Edit any file.** You report. Repairs go to `tweak-fixer`. +- Do not propose specific replacement RVAs. The verifier flags + mismatches; resolving them needs disassembly which is out of scope. +- Do not invent severity ratings. Use the three above only. +- Do not skip cross-checking against `dump.cs.index.json` when the file + exists; it's the most authoritative source for whether an RVA still + points at the symbol you think it does. +- Do not enumerate every passing row in `ok` if the count exceeds 20 — + return `ok: [" rows passed"]` instead. diff --git a/.claude/agents/crash-log-analyzer.md b/.claude/agents/crash-log-analyzer.md new file mode 100644 index 0000000..2f3ab5d --- /dev/null +++ b/.claude/agents/crash-log-analyzer.md @@ -0,0 +1,128 @@ +--- +name: crash-log-analyzer +description: | + Read an iOS crash report (.ips) and the Tweak's in-app log files, then + produce a structured crash diagnosis: signal/code, faulting thread, + symbolicated frame for the Tweak, register state, and the most recent + events from the in-app log right before the crash. Output is hypotheses + + evidence, not patches. Read-only. +tools: Read, Grep, Glob, Bash +--- + +# crash-log-analyzer + +You are a focused crash-report analyst for iOS Tweaks. Your only job is +to read the artifacts already on disk and produce a structured diagnosis. +You do NOT edit code. You do NOT run the device. You do NOT propose +patches — that is the `tweak-fixer` agent's job. + +## Inputs + +The orchestrator (`/fix-tweak`) passes you: + +- `crashDir`: absolute path to a directory under `logs/crashes/`, with: + - `crashreporter/*.ips` — one or more iOS crash reports + - `sandbox//{Documents,Library,tmp}/Logs/*` — + in-app logs the Tweak wrote +- Optionally a hint: `focusReport` = filename of the .ips to prioritize + (defaults to the newest by mtime) + +If the inputs aren't structured this way, ask for the actual paths in +your first response (one short question) rather than guessing. + +## What you do + +1. **Pick the focus crash report**. Newest .ips by mtime unless told + otherwise. Read it. +2. **Parse the .ips**. It is a JSON document with a single-line header + followed by a JSON body. Use `jq` via Bash for fields. Extract: + - `exception.type`, `exception.signal`, `exception.codes`, + `exception.subtype` (the fault address) + - `faultingThread` (index into `threads[]`) + - `threads[].frames[]` for the faulting thread — keep the top + ~25 frames + - `usedImages[]` entries whose `name` matches the Tweak dylib and the + host framework. Record their `base` and `size`. + - `threadState.x[]` if present (general-purpose registers at fault) +3. **Symbolicate per-image offsets**. For each top frame: + - Convert `imageOffset` to hex + - Note which image it lives in + - For Tweak dylib frames, the .ips usually already has `symbol` + filled in (e.g. `KFHookHttpMsgInvokerSendAsync + 88`). Surface that + verbatim + - For host framework frames, you can't symbolicate without dump.cs, + so just record the hex offset and let `cave-rva-verifier` cross- + reference it later +4. **Read in-app logs**. Walk every file under `sandbox/.../Logs/`. For + each file: + - Print the last ~40 lines (tail of the file) verbatim + - Look for the install banner the Tweak emits at constructor time + (e.g. `KiouForge: all hooks installed`) and copy the line so the + orchestrator can confirm hook addresses + - Look for `[ACCOUNT]`, `[GRPC]`, `[CHINLAN]` and similar bracketed + tags and extract the LAST occurrence of each tag +5. **Cross-check signals**. Note specifically: + - Was a hook body the symbolicated top frame? Which hook? + - Did the install log show the bypass / orig pointer for that hook? + What was its value? + - Is the fault address obviously bogus (e.g. < 0x100000, between + image bases, or far outside any region)? +6. **Form hypotheses**. Produce 1-3 ranked hypotheses, each with: + - `mechanism`: 1 sentence on what would cause this crash + - `evidence`: the concrete lines / addresses / register values that + support it (cite file:line or .ips field paths) + - `disconfirming`: what you'd expect to also see but didn't, that + keeps confidence below 100% + +## Output shape + +Return a single JSON object (so the orchestrator can pass it onward +machine-readably) followed by a short prose summary for humans. + +```json +{ + "crashReport": "logs/crashes/.../KIOU-...-ips", + "exception": { "type": "EXC_BAD_ACCESS", "signal": "SIGSEGV", "faultAddr": "0x200258ac" }, + "topFrames": [ + { "image": "UnityFramework", "imageOffset": "0x18494F" }, + { "image": "KiouForge.dylib", "imageOffset": "0x18240", + "symbol": "KFHookHttpMsgInvokerSendAsync", "symbolOffset": 88 } + ], + "registers": { "x0": "...", "x1": "...", "x2": "...", "x3": "..." }, + "imageBases": { "UnityFramework": "0x101000000", "KiouForge.dylib": "0x101087000" }, + "lastTweakLog": ["…", "…"], + "hypotheses": [ + { "rank": 1, "mechanism": "...", "evidence": ["..."], "disconfirming": ["..."] } + ] +} +``` + +The prose summary that follows the JSON should be ~5 lines max. It +restates the leading hypothesis in plain English and points at the +specific evidence by file:line. + +## What you must NOT do + +- **Do not Edit any file**. You don't have Edit/Write — if you find + yourself wanting to, that's the orchestrator's signal to call + `tweak-fixer` instead. Surface what should change in your hypothesis, + not in code. +- Do not run the device or SSH anywhere. All inputs are already on disk + under `logs/crashes/`. +- Do not invent register values, image bases, or symbol names that + aren't literally in the artifacts. If a field isn't present, say so. +- Do not enumerate every frame of every thread. Faulting thread top + ~25 frames only. The orchestrator has a context budget. +- Do not propose patches. Hypotheses, not fixes. + +## Tips + +- `jq -r '.threads[.faultingThread] | .frames[0:25]' body.json` is the + fastest way to get the stack +- iOS crash reports store `threadState.x` as an array of {value: int} + objects, not a plain array of ints — index by position +- `imageOffset` decimal → hex with `printf '0x%x\n' ` +- Image-relative offset = absolute_va - image.base; orchestrators usually + want both forms +- For each hypothesis, "what's the minimum new evidence that would + refute this?" is a good sanity check — write it as `disconfirming` diff --git a/.claude/agents/tweak-fixer.md b/.claude/agents/tweak-fixer.md new file mode 100644 index 0000000..212df4b --- /dev/null +++ b/.claude/agents/tweak-fixer.md @@ -0,0 +1,104 @@ +--- +name: tweak-fixer +description: | + Apply targeted source-level fixes to a KIOU-Hook-based Tweak based on + diagnoses from crash-log-analyzer and cave-rva-verifier. Edits Tweak + hook bodies, dispatcher externs, catalog RVAs, recipe SITES rows. Runs + no device commands. Verifies with the Tweak's build commands when + asked. +tools: Read, Write, Edit, Grep, Glob, Bash +--- + +# tweak-fixer + +You apply minimal, targeted edits to a Tweak's source tree to address +specific problems other agents have already diagnosed. You do NOT +re-diagnose — the orchestrator passes you a finding list. You do NOT +edit speculatively — every change ties back to a specific finding. + +## Inputs + +The orchestrator (`/fix-tweak`) passes you a fix plan: + +```json +{ + "findings": [ + { + "id": "FIX-1", + "where": "Sources/KiouForge/ChinlanDispatcher.m:48", + "what": "extern declaration of KFHookHttpMsgInvokerSendAsync needs trailing MethodInfo* arg", + "rationale": "Matches the GrpcLogging.m definition; needed for IL2CPP instance-method ABI" + }, + { + "id": "FIX-2", + "where": "vendor/KIOU-Hook/Hook/AccountObserve.m", + "what": "Add `void *mi` to KFHookLoginArgsCreate signature and forward it to orig", + "rationale": "..." + } + ], + "buildCheck": "make CHINLAN=1 -j4" // optional; run after edits +} +``` + +## What you do + +1. **Re-read each target file before editing.** Edit will refuse if + you haven't, and stale context produces broken diffs. +2. **Apply edits with the Edit tool**, one finding at a time. Match + the file's existing style (indentation, brace placement, comment + tone). Do not introduce unrelated formatting changes. +3. **For ABI / signature changes** (the common case), update *all* + three places that need to agree: + - The hook body definition (`void *FunctionName(...)`) + - The `typedef` and `static` orig pointer in the same file + - The `extern` declaration in `ChinlanDispatcher.m` + - Any callers (search with `grep -rE 'FunctionName\\s*\\('`) +4. **For RVA changes**, update both the C macro + (`#define KIOU_HOOK_RVA_*`) AND the recipe SITES row + (`recipes/v_*.py`). They are paired-of-truth; touching one without + the other guarantees a build-time / runtime divergence. +5. **For dispatcher slot table changes**, update both the + `entrySlots[KIOU_HOOK_SLOT_*]` line AND the corresponding + `extern` declaration above it. +6. **Build check** (when `buildCheck` is supplied). Run the command, + capture stderr, and report failures. Do not attempt to "fix" build + errors that don't trace back to your edits — report and stop. +7. **Summarize.** For each finding, report: + - Applied / skipped (with reason if skipped) + - Files touched + - Lines changed (just counts, not the patch — orchestrator can + diff) + +## Output shape + +```json +{ + "findings": [ + { "id": "FIX-1", "status": "applied", "files": ["Sources/KiouForge/ChinlanDispatcher.m"], "linesChanged": 1 }, + { "id": "FIX-2", "status": "applied", "files": ["vendor/KIOU-Hook/Hook/AccountObserve.m"], "linesChanged": 4 } + ], + "buildCheck": { "ran": true, "ok": true, "summary": "make CHINLAN=1 -j4 succeeded; 0 warnings" } +} +``` + +If a finding's `where` is ambiguous (file exists but the symbol / +location it names doesn't), set `status: "skipped"` with `reason` +explaining what was missing, and move on. Do not invent a target. + +## What you must NOT do + +- Do not edit files that aren't named in `findings[]`. If you think + one needs a tag-along change, report it as a new finding the + orchestrator can decide on — don't sneak it in. +- Do not change semantics beyond what the finding describes. If a + finding says "add a trailing arg", do not also rename the function + or refactor the body. +- Do not commit or push. Orchestrator owns version control. +- Do not run `make ipa` or `make package` or anything that builds an + IPA / .deb. Build checks are for compile verification only + (`make CHINLAN=1` or the supplied command). Packaging is downstream. +- Do not touch unrelated style (Biome, ARC, comments). The fixes are + surgical. +- Do not silence compiler warnings unrelated to your changes. If new + warnings appear that look caused by your edits, include them in the + build report so the orchestrator can decide. diff --git a/.claude/scripts/pull_crash.sh b/.claude/scripts/pull_crash.sh new file mode 100755 index 0000000..7916c8f --- /dev/null +++ b/.claude/scripts/pull_crash.sh @@ -0,0 +1,176 @@ +#!/usr/bin/env bash +# pull_crash.sh — fetch iOS crash reports + sandbox logs from a JB device. +# +# Deterministic: bundle ID prefix is read from the Tweak filter plist, not +# inferred. Device IP comes from $THEOS_DEVICE_IP, or the project Makefile +# default, or the script's --ip argument. Output is logs/crashes/-/ relative to the repository root. +# +# Usage: +# .claude/scripts/pull_crash.sh [--prefix com.example] [--ip 1.2.3.4] +# [--plist KiouForge.plist] [--root .] +# +# All flags are optional. The script exits non-zero on hard failures +# (no SSH connectivity, no matching device application). Missing pieces +# inside the run (e.g. an app with no sandbox Logs dir) are logged and +# skipped — partial results are still written. + +set -uo pipefail + +PREFIX="" +IP="" +PLIST="" +ROOT="$(git -C "$(dirname "$0")" rev-parse --show-toplevel 2>/dev/null || pwd)" + +while [ $# -gt 0 ]; do + case "$1" in + --prefix) PREFIX="$2"; shift 2 ;; + --ip) IP="$2"; shift 2 ;; + --plist) PLIST="$2"; shift 2 ;; + --root) ROOT="$2"; shift 2 ;; + -h|--help) + sed -n '2,18p' "$0"; exit 0 ;; + *) + echo "pull_crash: unknown flag $1" >&2; exit 2 ;; + esac +done + +cd "$ROOT" || { echo "pull_crash: cannot cd to $ROOT" >&2; exit 2; } + +# --- 1. bundle ID prefix ---------------------------------------------------- +if [ -z "$PREFIX" ]; then + if [ -z "$PLIST" ]; then + PLIST=$(find . -maxdepth 2 -name '*.plist' \ + -not -path './assets/*' \ + -not -path './packages/*' \ + -not -path './.theos/*' \ + -not -path './vendor/*' 2>/dev/null \ + | head -1) + fi + if [ -z "$PLIST" ] || [ ! -f "$PLIST" ]; then + echo "pull_crash: no Tweak plist found; pass --prefix or --plist" >&2 + exit 2 + fi + # Extract first inside the Bundles . XML plist only — + # the Tweak filter plist Theos generates is always XML, never binary. + PREFIX=$(awk ' + /Bundles<\/key>/ { in_b=1; next } + in_b && // { + sub(/.*/,"") + sub(/<\/string>.*/,"") + print + exit + }' "$PLIST" 2>/dev/null) +fi + +# Sanity: prefix must be ASCII identifier-ish (letters, digits, dots, -, _). +if ! printf '%s' "$PREFIX" | grep -qE '^[A-Za-z0-9._-]+$'; then + echo "pull_crash: extracted prefix is not a valid bundle id: '$PREFIX'" >&2 + exit 2 +fi +if [ -z "$PREFIX" ]; then + echo "pull_crash: could not read bundle ID prefix from $PLIST" >&2 + exit 2 +fi + +# --- 2. device IP ----------------------------------------------------------- +if [ -z "$IP" ] && [ -n "${THEOS_DEVICE_IP:-}" ]; then + IP="$THEOS_DEVICE_IP" +fi +if [ -z "$IP" ] && [ -f Makefile ]; then + IP=$(grep -E '^THEOS_DEVICE_IP[[:space:]]*[?]=' Makefile \ + | head -1 \ + | sed -E 's/^THEOS_DEVICE_IP[[:space:]]*[?]=[[:space:]]*//' \ + | tr -d '[:space:]') +fi +if [ -z "$IP" ]; then + echo "pull_crash: device IP not set; pass --ip or export THEOS_DEVICE_IP" >&2 + exit 2 +fi + +SSH_OPTS=(-o ConnectTimeout=5 -o StrictHostKeyChecking=no -o BatchMode=yes) +# -n keeps ssh from swallowing stdin (otherwise it eats lines from while-read loops). +SSH() { ssh -n "${SSH_OPTS[@]}" "root@$IP" "$@"; } +if ! SSH 'echo ok' >/dev/null 2>&1; then + echo "pull_crash: cannot reach root@$IP via SSH" >&2 + exit 1 +fi + +# --- 3. output directory ---------------------------------------------------- +TS=$(date +%Y%m%d-%H%M%S) +SAFE_PREFIX=${PREFIX//./-} +OUT="logs/crashes/${SAFE_PREFIX}-${TS}" +mkdir -p "$OUT/crashreporter" "$OUT/sandbox" + +# --- 4. crash reports ------------------------------------------------------- +# Match by JSON-header bundleID OR by filename prefix (proc_name resource +# diagnostics like KIOU.wakeups_resource-*.ips carry the proc name as the +# filename prefix but a different bundleID in the JSON). +ESCAPED_PREFIX=$(printf '%s' "$PREFIX" | sed 's/[.[\*^$/]/\\&/g') +LIST_FILE="$OUT/crash-list.txt" +SSH "for f in /var/mobile/Library/Logs/CrashReporter/*.ips; do + [ -f \"\$f\" ] || continue + head -1 \"\$f\" | grep -qE '\"bundleID\":\"${ESCAPED_PREFIX}([.][^\"]*)?\"' && echo \"\$f\" + done" > "$LIST_FILE" 2>/dev/null + +CRASH_COUNT=0 +if [ -s "$LIST_FILE" ]; then + while IFS= read -r f; do + [ -n "$f" ] || continue + if scp -q "${SSH_OPTS[@]}" "root@$IP:$f" "$OUT/crashreporter/" 2>/dev/null; then + CRASH_COUNT=$((CRASH_COUNT + 1)) + fi + done < "$LIST_FILE" +fi + +# --- 5. sandbox discovery --------------------------------------------------- +# All applications whose MCMMetadataIdentifier starts with the prefix. +# Binary plists pack strings without surrounding whitespace, so `strings` +# can split the id into multiple chunks. Use `grep -ao` to extract the +# bundle id straight from the raw bytes. +SANDBOX_FILE="$OUT/sandbox-list.txt" +SSH "for d in /var/mobile/Containers/Data/Application/*/; do + p=\"\${d}.com.apple.mobile_container_manager.metadata.plist\" + [ -f \"\$p\" ] || continue + grep -aq '${PREFIX}' \"\$p\" 2>/dev/null || continue + id=\$(grep -aoE '${ESCAPED_PREFIX}([.][A-Za-z0-9_-]+)*' \"\$p\" 2>/dev/null | sort -u | head -1) + [ -n \"\$id\" ] && echo \"\$id|\$d\" + done" > "$SANDBOX_FILE" 2>/dev/null + +SANDBOX_COUNT=0 +LOG_COUNT=0 +if [ -s "$SANDBOX_FILE" ]; then + while IFS='|' read -r bundle dir; do + [ -n "$dir" ] || continue + safe_bundle=${bundle//./-} + dest="$OUT/sandbox/$safe_bundle" + mkdir -p "$dest" + for sub in Documents/Logs Library/Logs tmp/Logs; do + # Probe existence first so missing dirs don't pollute stderr. + if SSH "[ -d \"${dir}${sub}\" ]"; then + mkdir -p "$dest/$sub" + if scp -q -r "${SSH_OPTS[@]}" "root@$IP:${dir}${sub}/." "$dest/$sub/" 2>/dev/null; then + n=$(find "$dest/$sub" -type f 2>/dev/null | wc -l) + LOG_COUNT=$((LOG_COUNT + n)) + fi + fi + done + SANDBOX_COUNT=$((SANDBOX_COUNT + 1)) + done < "$SANDBOX_FILE" +fi + +# --- 6. summary ------------------------------------------------------------- +LATEST_IPS="" +if [ "$CRASH_COUNT" -gt 0 ]; then + LATEST_IPS=$(ls -t "$OUT/crashreporter"/*.ips 2>/dev/null | head -1 | xargs -I{} basename {}) +fi + +cat <\nfocusReport=\nuserContext=" +} +``` + +Conventions for the `prompt`: +- Always use absolute paths. The subagent's working directory is the + repo root. +- `focusReport` is blank unless the user named a specific `.ips`. +- `userContext` carries the Step 0 note (or `""` if none). + +Save the returned JSON as `analysis`. From +`analysis.hypotheses[0].evidence`, extract any hook names of the form +`KIOU_HOOK_NAME_*` or `KFHook[A-Z]\w*` and collect them as the +`suspectHooks` list for Stage 3. If the leading hypothesis names no +hook explicitly, pass `suspectHooks=[]` (Stage 3 will sweep). + +--- + +## Stage 3 — CAVE/RVA verification + +Spawn the `cave-rva-verifier` subagent. + +```json +{ + "subagent_type": "cave-rva-verifier", + "description": "Verify CAVE/RVA wiring around the suspected hook(s)", + "prompt": "repoRoot=\ntargetVersion=\nsuspectHooks=[]" +} +``` + +Resolve `targetVersion`: + +```bash +grep -oE 'TARGET_VERSION\s*\?=\s*\S+' Makefile | awk -F'=' '{gsub(/ /,"",$2); print $2}' +``` + +Default to `1.0.2` if extraction fails. + +Save the returned JSON as `verification`. Build the combined +`findings[]` list for Stage 4 by merging: + +- Every entry of `verification.issues[]` with severity `error` or + `warning`. +- Any actionable mechanism from `analysis.hypotheses[]` that names a + specific file:line and is NOT already covered by a `verification` + issue at the same location. + +Each merged finding MUST have these fields: + +```json +{ + "id": "FIX-", + "where": ":", + "what": "", + "rationale": "" +} +``` + +Number `id`s sequentially starting at `FIX-1`. Do NOT rewrite the +underlying `what` text — copy it from the source subagent so the +audit trail is preserved. + +--- + +## Stage 4 — Patch application (requires user consent) + +Show the merged `findings[]` to the user verbatim, then issue +`AskUserQuestion`: + +- `Apply all` — forward every finding to `tweak-fixer`. +- `Pick which to apply` — follow-up `AskUserQuestion` (multiSelect) + listing each finding by `id` + first 80 chars of `what`. +- `Don't apply` — leave the findings on screen and end the pipeline. + +If the user opts in, spawn `tweak-fixer`: + +```json +{ + "subagent_type": "tweak-fixer", + "description": "Apply Tweak fixes", + "prompt": "findings=\nbuildCheck=make CHINLAN=1 -j4" +} +``` + +`buildCheck` enables compile verification after edits. Do not omit it +unless the user explicitly requests skipping the build. + +When `tweak-fixer` returns, present its result to the user: +- Per-finding `status` (applied / skipped) and `files`. +- `buildCheck.summary` (ok / errors). + +Do NOT proceed to packaging, IPA assembly, git commit, push, or PR +creation. Hand control back to the user. + +--- + +## Pipeline control + +- Maintain four tasks via `TaskCreate` / `TaskUpdate`, one per stage. + Set `in_progress` on stage entry, `completed` on success. +- If any stage fails (non-zero exit, subagent returns empty / error + JSON), stop and report the partial state. Do not skip ahead. +- Never let one subagent's output be paraphrased before reaching the + next subagent. Pass JSON through. + +--- + +## Hard prohibitions + +- Do NOT perform analysis, verification, or code edits yourself. They + belong to the subagents. Your role is dispatch and aggregation. +- Do NOT ask the user for device IP, bundle ID, target files, or + Tweak version. `pull_crash.sh` is deterministic; trust it. +- Do NOT invoke `tweak-fixer` without explicit user consent in + Stage 4. +- Do NOT summarize subagent JSON in a way that drops fields. Pass it + through; summarize once at the end for the human reader. +- Do NOT run `make ipa`, `make package`, `make jailed`, or any + packaging target. `tweak-fixer` stops at `make CHINLAN=1` for + compile verification. +- Do NOT fall back to the global `~/.claude/skills/fix-tweak/SKILL.md` + generic flow. This project-local SKILL.md is authoritative inside + this repository. + +--- + +## Asset locations (reference) + +- `.claude/scripts/pull_crash.sh` — log collection +- `.claude/agents/crash-log-analyzer.md` — crash analysis subagent +- `.claude/agents/cave-rva-verifier.md` — CAVE/RVA verifier subagent +- `.claude/agents/tweak-fixer.md` — code repair subagent + +To change pipeline behavior, edit the asset directly rather than +patching this orchestrator. diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index d8eb66e..3c9c03e 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,3 +1,4 @@ +# syntax=docker/dockerfile:1.7 FROM mcr.microsoft.com/devcontainers/base:ubuntu-24.04 ARG _REMOTE_USER_HOME=/home/vscode @@ -5,6 +6,7 @@ ARG _REMOTE_USER_HOME=/home/vscode RUN \ --mount=type=cache,target=/var/lib/apt,sharing=locked \ --mount=type=cache,target=/var/cache/apt,sharing=locked \ + rm -f /etc/apt/apt.conf.d/docker-clean && \ apt-get update && apt-get install -y --no-install-recommends \ wget \ unzip \ @@ -41,38 +43,47 @@ RUN \ # ── jadx (GitHub Release) ── # Android APK → Java 逆コンパイル (プラットフォーム非依存 JAR、JRE は devcontainer feature の Java で提供) -RUN JADX_URL=$(wget -qO- https://api.github.com/repos/skylot/jadx/releases/latest | jq -r '.assets[] | select(.name | test("^jadx-[0-9].*\\.zip$")) | .browser_download_url') && \ - wget -q "$JADX_URL" -O /tmp/jadx.zip && \ - unzip -q /tmp/jadx.zip -d /opt/jadx && \ +RUN \ + --mount=type=cache,target=/var/cache/dl,sharing=locked,uid=1000,gid=1000 \ + JADX_URL=$(wget -qO- https://api.github.com/repos/skylot/jadx/releases/latest | jq -r '.assets[] | select(.name | test("^jadx-[0-9].*\\.zip$")) | .browser_download_url') && \ + JADX_FILE=/var/cache/dl/$(basename "$JADX_URL") && \ + { [ -f "$JADX_FILE" ] || wget -q "$JADX_URL" -O "$JADX_FILE"; } && \ + unzip -q "$JADX_FILE" -d /opt/jadx && \ ln -s /opt/jadx/bin/jadx /usr/local/bin/jadx && \ - ln -s /opt/jadx/bin/jadx-gui /usr/local/bin/jadx-gui && \ - rm /tmp/jadx.zip + ln -s /opt/jadx/bin/jadx-gui /usr/local/bin/jadx-gui # ── Ghidra (Headless) ── # NSA 製リバースエンジニアリングツール。ヘッドレスモードで C++ 擬似コード生成に使用 # 使い方: analyzeHeadless /tmp/project name -import -RUN GHIDRA_URL=$(wget -qO- https://api.github.com/repos/NationalSecurityAgency/ghidra/releases/latest | jq -r '.assets[] | select(.name | test("PUBLIC.*\\.zip$")) | .browser_download_url') && \ - wget -q "$GHIDRA_URL" -O /tmp/ghidra.zip && \ - unzip -q /tmp/ghidra.zip -d /opt && \ +RUN \ + --mount=type=cache,target=/var/cache/dl,sharing=locked,uid=1000,gid=1000 \ + GHIDRA_URL=$(wget -qO- https://api.github.com/repos/NationalSecurityAgency/ghidra/releases/latest | jq -r '.assets[] | select(.name | test("PUBLIC.*\\.zip$")) | .browser_download_url') && \ + GHIDRA_FILE=/var/cache/dl/$(basename "$GHIDRA_URL") && \ + { [ -f "$GHIDRA_FILE" ] || wget -q "$GHIDRA_URL" -O "$GHIDRA_FILE"; } && \ + unzip -q "$GHIDRA_FILE" -d /opt && \ GHIDRA_DIR=$(find /opt -maxdepth 1 -name 'ghidra_*' -type d) && \ - ln -s "$GHIDRA_DIR" /opt/ghidra && \ - rm /tmp/ghidra.zip + ln -s "$GHIDRA_DIR" /opt/ghidra ENV PATH="/opt/ghidra/support:$PATH" # ── ipsw ── # Go 製 Mach-O 解析ツール。iOS バイナリの ObjC/Swift クラスダンプ、シンボル解析に使用 # 使い方: ipsw macho info --class-dump -RUN IPSW_URL=$(wget -qO- https://api.github.com/repos/blacktop/ipsw/releases/latest | jq -r '.assets[] | select(.name | test("^ipsw_.*linux_arm64\\.tar\\.gz$")) | .browser_download_url') && \ - wget -q "$IPSW_URL" -O /tmp/ipsw.tar.gz && \ - tar xzf /tmp/ipsw.tar.gz -C /usr/local/bin ipsw && \ - rm /tmp/ipsw.tar.gz +RUN \ + --mount=type=cache,target=/var/cache/dl,sharing=locked,uid=1000,gid=1000 \ + IPSW_URL=$(wget -qO- https://api.github.com/repos/blacktop/ipsw/releases/latest | jq -r '.assets[] | select(.name | test("^ipsw_.*linux_arm64\\.tar\\.gz$")) | .browser_download_url') && \ + IPSW_FILE=/var/cache/dl/$(basename "$IPSW_URL") && \ + { [ -f "$IPSW_FILE" ] || wget -q "$IPSW_URL" -O "$IPSW_FILE"; } && \ + tar xzf "$IPSW_FILE" -C /usr/local/bin ipsw # ── yq (mikefarah/yq, Go 製) ── # YAML 操作。agent-iossolve の hook 仕様 YAML を扱う recon/frida エージェントが使う -RUN ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') && \ +RUN \ + --mount=type=cache,target=/var/cache/dl,sharing=locked,uid=1000,gid=1000 \ + ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') && \ YQ_URL=$(wget -qO- https://api.github.com/repos/mikefarah/yq/releases/latest | jq -r --arg arch "$ARCH" '.assets[] | select(.name == "yq_linux_\($arch)") | .browser_download_url') && \ - wget -q "$YQ_URL" -O /usr/local/bin/yq && \ - chmod +x /usr/local/bin/yq + YQ_FILE=/var/cache/dl/yq_linux_${ARCH} && \ + { [ -f "$YQ_FILE" ] || wget -q "$YQ_URL" -O "$YQ_FILE"; } && \ + install -m 0755 "$YQ_FILE" /usr/local/bin/yq # ── Theos 本体 ── USER vscode @@ -80,25 +91,37 @@ ENV THEOS=/home/vscode/theos RUN git clone --recursive https://github.com/theos/theos.git ${THEOS} # ── Toolchain (L1ghtmann, aarch64 対応) ── -RUN ARCH=$(uname -m) && \ - curl -fsSL "https://github.com/L1ghtmann/llvm-project/releases/latest/download/iOSToolchain-${ARCH}.tar.xz" \ - | tar xJ -C ${THEOS}/toolchain +RUN \ + --mount=type=cache,target=/var/cache/dl,sharing=locked,uid=1000,gid=1000 \ + ARCH=$(uname -m) && \ + TC_FILE=/var/cache/dl/iOSToolchain-${ARCH}.tar.xz && \ + { [ -f "$TC_FILE" ] || curl -fsSL -o "$TC_FILE" "https://github.com/L1ghtmann/llvm-project/releases/latest/download/iOSToolchain-${ARCH}.tar.xz"; } && \ + tar xJf "$TC_FILE" -C ${THEOS}/toolchain # ── Swift Toolchain (kabiroberai, iOS クロスコンパイル用) ── -RUN ARCH=$(uname -m) && \ - curl -fsSL "https://github.com/kabiroberai/swift-toolchain-linux/releases/download/v2.3.0/swift-5.8-ubuntu22.04-${ARCH}.tar.xz" \ - | tar xJ -C ${THEOS}/toolchain +RUN \ + --mount=type=cache,target=/var/cache/dl,sharing=locked,uid=1000,gid=1000 \ + ARCH=$(uname -m) && \ + SWIFT_TC_FILE=/var/cache/dl/swift-5.8-ubuntu22.04-${ARCH}.tar.xz && \ + { [ -f "$SWIFT_TC_FILE" ] || curl -fsSL -o "$SWIFT_TC_FILE" "https://github.com/kabiroberai/swift-toolchain-linux/releases/download/v2.3.0/swift-5.8-ubuntu22.04-${ARCH}.tar.xz"; } && \ + tar xJf "$SWIFT_TC_FILE" -C ${THEOS}/toolchain # ── ホスト Swift (SPM ビルド用) ── USER root -RUN ARCH=$(uname -m) && \ - curl -fsSL "https://download.swift.org/swift-5.8.1-release/ubuntu2204-${ARCH}/swift-5.8.1-RELEASE/swift-5.8.1-RELEASE-ubuntu22.04-${ARCH}.tar.gz" \ - | tar xz --strip-components=2 -C /usr +RUN \ + --mount=type=cache,target=/var/cache/dl,sharing=locked,uid=1000,gid=1000 \ + ARCH=$(uname -m) && \ + HOST_SWIFT_FILE=/var/cache/dl/swift-5.8.1-RELEASE-ubuntu22.04-${ARCH}.tar.gz && \ + { [ -f "$HOST_SWIFT_FILE" ] || curl -fsSL -o "$HOST_SWIFT_FILE" "https://download.swift.org/swift-5.8.1-release/ubuntu2204-${ARCH}/swift-5.8.1-RELEASE/swift-5.8.1-RELEASE-ubuntu22.04-${ARCH}.tar.gz"; } && \ + tar xzf "$HOST_SWIFT_FILE" --strip-components=2 -C /usr USER vscode # ── iOS SDKs (15.6 + 16.5) ── -RUN curl -fsSL "https://github.com/theos/sdks/archive/master.tar.gz" \ - | tar xz -C /tmp && \ +RUN \ + --mount=type=cache,target=/var/cache/dl,sharing=locked,uid=1000,gid=1000 \ + SDKS_FILE=/var/cache/dl/theos-sdks-master.tar.gz && \ + { [ -f "$SDKS_FILE" ] || curl -fsSL -o "$SDKS_FILE" "https://github.com/theos/sdks/archive/master.tar.gz"; } && \ + tar xzf "$SDKS_FILE" -C /tmp && \ mv /tmp/sdks-master/iPhoneOS15.6.sdk ${THEOS}/sdks/ && \ mv /tmp/sdks-master/iPhoneOS16.5.sdk ${THEOS}/sdks/ && \ rm -rf /tmp/sdks-master diff --git a/.devcontainer/compose.yaml b/.devcontainer/compose.yaml index 3b84ac6..0927647 100644 --- a/.devcontainer/compose.yaml +++ b/.devcontainer/compose.yaml @@ -6,9 +6,21 @@ services: volumes: - ../:/home/vscode/app:cached - venv:/home/vscode/app/.venv + - uv-cache:/home/vscode/.cache/uv + - bun-cache:/home/vscode/.bun/install/cache + - npm-cache:/home/vscode/.npm + - pip-cache:/home/vscode/.cache/pip tty: true stdin_open: true volumes: venv: driver: local + uv-cache: + driver: local + bun-cache: + driver: local + npm-cache: + driver: local + pip-cache: + driver: local diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index e9b7a5d..bfc57db 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -20,9 +20,7 @@ "source=${env:HOME}/.aws,target=/home/vscode/.aws,type=bind,consistency=cached,readonly", "source=${env:HOME}/.claude,target=/home/vscode/.claude,type=bind,consistency=cached", "source=${env:HOME}/.ssh,target=/home/vscode/.ssh,type=bind,consistency=cached,readonly", - "source=${env:HOME}/.config/gh,target=/home/vscode/.config/gh,type=bind,consistency=cached", - "source=${env:HOME}/.codex,target=/home/vscode/.codex,type=bind,consistency=cached", - "source=${env:HOME}/.gnupg,target=/home/vscode/.gnupg-host,type=bind,consistency=cached,readonly" + "source=${env:HOME}/.config/gh,target=/home/vscode/.config/gh,type=bind,consistency=cached" ], "features": { "ghcr.io/devcontainers/features/common-utils:2": { diff --git a/.devcontainer/postCreateCommand.sh b/.devcontainer/postCreateCommand.sh index 81ca4f8..816aaa1 100644 --- a/.devcontainer/postCreateCommand.sh +++ b/.devcontainer/postCreateCommand.sh @@ -1,5 +1,22 @@ #!/bin/sh +USER_NAME="$(whoami)" +USER_HOME="/home/${USER_NAME}" + git submodule update --init --recursive -sudo chown -R "$(whoami)":"$(whoami)" /home/"$(whoami)"/app/.venv + +sudo mkdir -p \ + "${USER_HOME}/app/.venv" \ + "${USER_HOME}/.cache/uv" \ + "${USER_HOME}/.cache/pip" \ + "${USER_HOME}/.bun/install/cache" \ + "${USER_HOME}/.npm" + +sudo chown -R "${USER_NAME}":"${USER_NAME}" \ + "${USER_HOME}/app/.venv" \ + "${USER_HOME}/.cache/uv" \ + "${USER_HOME}/.cache/pip" \ + "${USER_HOME}/.bun/install/cache" \ + "${USER_HOME}/.npm" + uv sync From 90c3744a01aedbcae66f09b1d06fb92456e04908 Mon Sep 17 00:00:00 2001 From: tkgstrator Date: Wed, 1 Jul 2026 08:39:49 +0000 Subject: [PATCH 4/9] chore(submodules): bump KIOU-Hook vendor revision Co-Authored-By: Claude Opus 4.8 (1M context) --- vendor/KIOU-Hook | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/KIOU-Hook b/vendor/KIOU-Hook index 41b2a00..85f00e1 160000 --- a/vendor/KIOU-Hook +++ b/vendor/KIOU-Hook @@ -1 +1 @@ -Subproject commit 41b2a00cbbe8961f49d9c017be13de9eec652369 +Subproject commit 85f00e19039194fac6b34443e60eb3ad9406d17b From d6418f6aeaaafd8a4e6248af009574358bc27cf4 Mon Sep 17 00:00:00 2001 From: tkgstrator Date: Wed, 1 Jul 2026 09:11:53 +0000 Subject: [PATCH 5/9] refactor(kiouforge): rename internal KF symbols to KIOU prefix Co-Authored-By: Claude Opus 4.8 (1M context) --- Sources/KiouForge/ChinlanDispatcher.m | 50 +++++------ Sources/KiouForge/Hook/AnalysisTune.m | 42 ++++----- Sources/KiouForge/Hook/FrameRate.m | 16 ++-- Sources/KiouForge/Hook/KifuObserve.m | 42 ++++----- Sources/KiouForge/Hook/SettingsUI.m | 122 +++++++++++++------------- Sources/KiouForge/Hook/Version.m | 4 +- Sources/KiouForge/Internal.h | 68 +++++++------- Sources/KiouForge/Kif/Helpers.m | 68 +++++++------- Sources/KiouForge/Kif/Writer.m | 32 +++---- Sources/KiouForge/Persistence.m | 38 ++++---- Sources/KiouForge/Settings.h | 6 +- Sources/KiouForge/Settings.m | 32 +++---- Sources/KiouForge/Tweak.m | 20 ++--- 13 files changed, 270 insertions(+), 270 deletions(-) diff --git a/Sources/KiouForge/ChinlanDispatcher.m b/Sources/KiouForge/ChinlanDispatcher.m index af8fda3..a309f28 100644 --- a/Sources/KiouForge/ChinlanDispatcher.m +++ b/Sources/KiouForge/ChinlanDispatcher.m @@ -13,7 +13,7 @@ // Hook_*.m observer body. // // CAVE_ENTRY caves each load their own slot at unityBase + -// KIOU_HOOK_ENTRY_SLOT_BASE_RVA + slot_index*8 and BLR it. KFChinlanPublish() +// KIOU_HOOK_ENTRY_SLOT_BASE_RVA + slot_index*8 and BLR it. KIOUChinlanPublish() // writes the live hook function pointers into those slots. // // g_inject_entry[i] holds the per-site cave-bypass entry pointer so a @@ -40,17 +40,17 @@ // replace orig; the body calls the cave bypass (resolved by KIOUHookOrig) // to invoke the original method when needed. // --------------------------------------------------------------------------- -extern void KFHookSetTargetFrameRateEntry(int32_t value, void *mi); -extern void KFHookNSSSetHashSizeEntry(void *self, int32_t mb, void *mi); -extern void KFHookNSSSetSkillLevelEntry(void *self, int32_t level, void *mi); -extern KFSyncSearchResult KFHookNSSSearchFullEntry(void *self, void *sfen, int32_t depth, void *mi); -extern bool KFHookAccountExists(void *data, void *mi); -extern void * KFHookLoginArgsCreate(void *deviceId, void *distinctId, void *mi); -extern void * KFHookRegisterUserArgsCreate(void *userName, void *distinctId, void *mi); -extern void KFHookRunLoginSeqMoveNext(void *self, void *mi); -extern void KFHookGetSelfProfileMoveNext(void *self, void *mi); -extern void * KFHookHttpMsgInvokerSendAsync(void *self, void *request, void *ct, void *mi); -extern void KFHookHeaderProviderSetOrUpdate(void *self, void *keyStr, void *valueStr, void *mi); +extern void KIOUHookSetTargetFrameRateEntry(int32_t value, void *mi); +extern void KIOUHookNSSSetHashSizeEntry(void *self, int32_t mb, void *mi); +extern void KIOUHookNSSSetSkillLevelEntry(void *self, int32_t level, void *mi); +extern KIOUSyncSearchResult KIOUHookNSSSearchFullEntry(void *self, void *sfen, int32_t depth, void *mi); +extern bool KIOUHookAccountExists(void *data, void *mi); +extern void * KIOUHookLoginArgsCreate(void *deviceId, void *distinctId, void *mi); +extern void * KIOUHookRegisterUserArgsCreate(void *userName, void *distinctId, void *mi); +extern void KIOUHookRunLoginSeqMoveNext(void *self, void *mi); +extern void KIOUHookGetSelfProfileMoveNext(void *self, void *mi); +extern void * KIOUHookHttpMsgInvokerSendAsync(void *self, void *request, void *ct, void *mi); +extern void KIOUHookHeaderProviderSetOrUpdate(void *self, void *keyStr, void *valueStr, void *mi); // --------------------------------------------------------------------------- // dispatch_one — single shared observer slot, switches on W6=hook_id. @@ -92,10 +92,10 @@ + (uintptr_t)hook_id * KIOU_HOOK_CAVE_SIZE } // --------------------------------------------------------------------------- -// KFChinlanPublish — install the observer dispatcher pointer + entry slot +// KIOUChinlanPublish — install the observer dispatcher pointer + entry slot // table the moment UnityFramework's base address is known. // --------------------------------------------------------------------------- -void KFChinlanPublish(uintptr_t unityBase) { +void KIOUChinlanPublish(uintptr_t unityBase) { if (unityBase == 0) { IPALog(@"[CHINLAN] publish skipped: unityBase is zero"); return; @@ -114,17 +114,17 @@ void KFChinlanPublish(uintptr_t unityBase) { // Entry-slot table — one slot per CAVE_ENTRY hook. void * volatile *entrySlots = (void * volatile *)(unityBase + KIOU_HOOK_ENTRY_SLOT_BASE_RVA); - entrySlots[KIOU_HOOK_SLOT_SET_TARGET_FRAMERATE] = (void *)&KFHookSetTargetFrameRateEntry; - entrySlots[KIOU_HOOK_SLOT_NSS_SETHASHSIZE] = (void *)&KFHookNSSSetHashSizeEntry; - entrySlots[KIOU_HOOK_SLOT_NSS_SETSKILLEVEL] = (void *)&KFHookNSSSetSkillLevelEntry; - entrySlots[KIOU_HOOK_SLOT_NSS_SEARCHFULL] = (void *)&KFHookNSSSearchFullEntry; - entrySlots[KIOU_HOOK_SLOT_ACCOUNT_EXISTS] = (void *)&KFHookAccountExists; - entrySlots[KIOU_HOOK_SLOT_LOGIN_ARGS_CREATE] = (void *)&KFHookLoginArgsCreate; - entrySlots[KIOU_HOOK_SLOT_REGISTER_USER_ARGS_CREATE] = (void *)&KFHookRegisterUserArgsCreate; - entrySlots[KIOU_HOOK_SLOT_RUN_LOGIN_SEQ_MOVENEXT] = (void *)&KFHookRunLoginSeqMoveNext; - entrySlots[KIOU_HOOK_SLOT_GET_SELF_PROFILE_MOVENEXT] = (void *)&KFHookGetSelfProfileMoveNext; - entrySlots[KIOU_HOOK_SLOT_HTTPMSGINVOKER_SEND_ASYNC] = (void *)&KFHookHttpMsgInvokerSendAsync; - entrySlots[KIOU_HOOK_SLOT_HEADER_PROVIDER_SET_OR_UPDATE_HEADER] = (void *)&KFHookHeaderProviderSetOrUpdate; + entrySlots[KIOU_HOOK_SLOT_SET_TARGET_FRAMERATE] = (void *)&KIOUHookSetTargetFrameRateEntry; + entrySlots[KIOU_HOOK_SLOT_NSS_SETHASHSIZE] = (void *)&KIOUHookNSSSetHashSizeEntry; + entrySlots[KIOU_HOOK_SLOT_NSS_SETSKILLEVEL] = (void *)&KIOUHookNSSSetSkillLevelEntry; + entrySlots[KIOU_HOOK_SLOT_NSS_SEARCHFULL] = (void *)&KIOUHookNSSSearchFullEntry; + entrySlots[KIOU_HOOK_SLOT_ACCOUNT_EXISTS] = (void *)&KIOUHookAccountExists; + entrySlots[KIOU_HOOK_SLOT_LOGIN_ARGS_CREATE] = (void *)&KIOUHookLoginArgsCreate; + entrySlots[KIOU_HOOK_SLOT_REGISTER_USER_ARGS_CREATE] = (void *)&KIOUHookRegisterUserArgsCreate; + entrySlots[KIOU_HOOK_SLOT_RUN_LOGIN_SEQ_MOVENEXT] = (void *)&KIOUHookRunLoginSeqMoveNext; + entrySlots[KIOU_HOOK_SLOT_GET_SELF_PROFILE_MOVENEXT] = (void *)&KIOUHookGetSelfProfileMoveNext; + entrySlots[KIOU_HOOK_SLOT_HTTPMSGINVOKER_SEND_ASYNC] = (void *)&KIOUHookHttpMsgInvokerSendAsync; + entrySlots[KIOU_HOOK_SLOT_HEADER_PROVIDER_SET_OR_UPDATE_HEADER] = (void *)&KIOUHookHeaderProviderSetOrUpdate; IPALog([NSString stringWithFormat: @"[CHINLAN] dispatcher=%p observer slot=%p (unityBase+0x%lx) " diff --git a/Sources/KiouForge/Hook/AnalysisTune.m b/Sources/KiouForge/Hook/AnalysisTune.m index 6d5229e..9b840c3 100644 --- a/Sources/KiouForge/Hook/AnalysisTune.m +++ b/Sources/KiouForge/Hook/AnalysisTune.m @@ -13,7 +13,7 @@ typedef void (*NSSSetHashSize_t)(void *self, int32_t mb, void *mi); typedef void (*NSSSetSkillLevel_t)(void *self, int32_t level, void *mi); -typedef KFSyncSearchResult (*NSSSearchFull_t)(void *self, void *sfen, +typedef KIOUSyncSearchResult (*NSSSearchFull_t)(void *self, void *sfen, int32_t depth, void *mi); #if !IPA_CHINLAN @@ -23,8 +23,8 @@ typedef KFSyncSearchResult (*NSSSearchFull_t)(void *self, void *sfen, #endif static int32_t pickHashMB(int32_t mb) { - int32_t target = KFFeatureEnabled(KIOU_FEATURE_ANALYSIS_TUNE) - ? KFAnalysisHashMB() : mb; + int32_t target = KIOUFeatureEnabled(KIOU_FEATURE_ANALYSIS_TUNE) + ? KIOUAnalysisHashMB() : mb; if (target != mb) { IPALog([NSString stringWithFormat: @"[ANALYSIS] SetHashSize %d -> %d MB (override)", mb, target]); @@ -33,8 +33,8 @@ static int32_t pickHashMB(int32_t mb) { } static int32_t pickSkillLevel(int32_t level) { - int32_t target = KFFeatureEnabled(KIOU_FEATURE_ANALYSIS_TUNE) - ? KFAnalysisSkillLevel() : level; + int32_t target = KIOUFeatureEnabled(KIOU_FEATURE_ANALYSIS_TUNE) + ? KIOUAnalysisSkillLevel() : level; if (target != level) { IPALog([NSString stringWithFormat: @"[ANALYSIS] SetSkillLevel %d -> %d (override)", level, target]); @@ -43,8 +43,8 @@ static int32_t pickSkillLevel(int32_t level) { } static int32_t pickDepth(int32_t depth) { - int32_t target = KFFeatureEnabled(KIOU_FEATURE_ANALYSIS_TUNE) - ? KFAnalysisDepth() : depth; + int32_t target = KIOUFeatureEnabled(KIOU_FEATURE_ANALYSIS_TUNE) + ? KIOUAnalysisDepth() : depth; if (target != depth) { IPALog([NSString stringWithFormat: @"[ANALYSIS] SearchFull depth %d -> %d (override)", @@ -54,38 +54,38 @@ static int32_t pickDepth(int32_t depth) { } #if IPA_CHINLAN -void KFHookNSSSetHashSizeEntry(void *self, int32_t mb, void *mi) { +void KIOUHookNSSSetHashSizeEntry(void *self, int32_t mb, void *mi) { int32_t v = pickHashMB(mb); NSSSetHashSize_t bypass = (NSSSetHashSize_t)g_inject_entry[KIOU_HOOK_ID_NSS_SETHASHSIZE]; if (bypass) bypass(self, v, mi); } -void KFHookNSSSetSkillLevelEntry(void *self, int32_t level, void *mi) { +void KIOUHookNSSSetSkillLevelEntry(void *self, int32_t level, void *mi) { int32_t v = pickSkillLevel(level); NSSSetSkillLevel_t bypass = (NSSSetSkillLevel_t)g_inject_entry[KIOU_HOOK_ID_NSS_SETSKILLEVEL]; if (bypass) bypass(self, v, mi); } -KFSyncSearchResult KFHookNSSSearchFullEntry(void *self, void *sfen, +KIOUSyncSearchResult KIOUHookNSSSearchFullEntry(void *self, void *sfen, int32_t depth, void *mi) { int32_t v = pickDepth(depth); NSSSearchFull_t bypass = (NSSSearchFull_t)g_inject_entry[KIOU_HOOK_ID_NSS_SEARCHFULL]; if (bypass) return bypass(self, sfen, v, mi); - KFSyncSearchResult empty = {0}; + KIOUSyncSearchResult empty = {0}; return empty; } -void KFInstallAnalysisTuneHook(uintptr_t unityBase) { +void KIOUInstallAnalysisTuneHook(uintptr_t unityBase) { (void)unityBase; IPALog([NSString stringWithFormat: @"[CHINLAN] AnalysisTune: entry hooks wired " @"(depth=%d hash=%d MB skill=%d)", - (int)KFAnalysisDepth(), - (int)KFAnalysisHashMB(), - (int)KFAnalysisSkillLevel()]); + (int)KIOUAnalysisDepth(), + (int)KIOUAnalysisHashMB(), + (int)KIOUAnalysisSkillLevel()]); } #else static void HookNSSSetHashSize(void *self, int32_t mb, void *mi) { @@ -98,15 +98,15 @@ static void HookNSSSetSkillLevel(void *self, int32_t level, void *mi) { if (orig_NSS_SetSkillLevel) orig_NSS_SetSkillLevel(self, v, mi); } -static KFSyncSearchResult HookNSSSearchFull(void *self, void *sfen, +static KIOUSyncSearchResult HookNSSSearchFull(void *self, void *sfen, int32_t depth, void *mi) { int32_t v = pickDepth(depth); if (orig_NSS_SearchFull) return orig_NSS_SearchFull(self, sfen, v, mi); - KFSyncSearchResult empty = {0}; + KIOUSyncSearchResult empty = {0}; return empty; } -void KFInstallAnalysisTuneHook(uintptr_t unityBase) { +void KIOUInstallAnalysisTuneHook(uintptr_t unityBase) { { uintptr_t addr = unityBase + RVA_NSS_SETHASHSIZE; MSHookFunction((void *)addr, @@ -114,7 +114,7 @@ void KFInstallAnalysisTuneHook(uintptr_t unityBase) { (void **)&orig_NSS_SetHashSize); IPALog([NSString stringWithFormat: @"NativeSyncSession.SetHashSize hooked @0x%lx hash=%d MB", - (unsigned long)addr, (int)KFAnalysisHashMB()]); + (unsigned long)addr, (int)KIOUAnalysisHashMB()]); } { uintptr_t addr = unityBase + RVA_NSS_SETSKILLEVEL; @@ -123,7 +123,7 @@ void KFInstallAnalysisTuneHook(uintptr_t unityBase) { (void **)&orig_NSS_SetSkillLevel); IPALog([NSString stringWithFormat: @"NativeSyncSession.SetSkillLevel hooked @0x%lx skill=%d", - (unsigned long)addr, (int)KFAnalysisSkillLevel()]); + (unsigned long)addr, (int)KIOUAnalysisSkillLevel()]); } { uintptr_t addr = unityBase + RVA_NSS_SEARCHFULL; @@ -132,7 +132,7 @@ void KFInstallAnalysisTuneHook(uintptr_t unityBase) { (void **)&orig_NSS_SearchFull); IPALog([NSString stringWithFormat: @"NativeSyncSession.SearchFull hooked @0x%lx depth=%d", - (unsigned long)addr, (int)KFAnalysisDepth()]); + (unsigned long)addr, (int)KIOUAnalysisDepth()]); } } #endif diff --git a/Sources/KiouForge/Hook/FrameRate.m b/Sources/KiouForge/Hook/FrameRate.m index ae836e4..42353de 100644 --- a/Sources/KiouForge/Hook/FrameRate.m +++ b/Sources/KiouForge/Hook/FrameRate.m @@ -14,8 +14,8 @@ #endif static int32_t pickFPS(int32_t value) { - int32_t v = KFFeatureEnabled(KIOU_FEATURE_FPS_OVERRIDE) - ? KFTargetFPS() : value; + int32_t v = KIOUFeatureEnabled(KIOU_FEATURE_FPS_OVERRIDE) + ? KIOUTargetFPS() : value; if (v != value) { IPALog([NSString stringWithFormat: @"[FPS] set_targetFrameRate %d -> %d (override)", value, v]); @@ -25,7 +25,7 @@ static int32_t pickFPS(int32_t value) { // Direct call from the settings slider callback so the new value takes // effect immediately without waiting for KIOU's next settings apply. -void KFApplyFPS(int32_t fps) { +void KIOUApplyFPS(int32_t fps) { if (g_unityBase == 0) return; SetTargetFrameRate_t fn = (SetTargetFrameRate_t)(g_unityBase + RVA_SET_TARGET_FRAMERATE); @@ -34,18 +34,18 @@ void KFApplyFPS(int32_t fps) { } #if IPA_CHINLAN -void KFHookSetTargetFrameRateEntry(int32_t value, void *mi) { +void KIOUHookSetTargetFrameRateEntry(int32_t value, void *mi) { int32_t v = pickFPS(value); SetTargetFrameRate_t bypass = (SetTargetFrameRate_t)g_inject_entry[KIOU_HOOK_ID_SET_TARGET_FRAMERATE]; if (bypass) bypass(v, mi); } -void KFInstallFrameRateHook(uintptr_t unityBase) { +void KIOUInstallFrameRateHook(uintptr_t unityBase) { (void)unityBase; IPALog([NSString stringWithFormat: @"[CHINLAN] set_targetFrameRate: entry hook wired (fps=%d)", - (int)KFTargetFPS()]); + (int)KIOUTargetFPS()]); } #else static void HookSetTargetFrameRate(int32_t value, void *mi) { @@ -53,13 +53,13 @@ static void HookSetTargetFrameRate(int32_t value, void *mi) { if (orig_set_targetFrameRate) orig_set_targetFrameRate(v, mi); } -void KFInstallFrameRateHook(uintptr_t unityBase) { +void KIOUInstallFrameRateHook(uintptr_t unityBase) { uintptr_t addr = unityBase + RVA_SET_TARGET_FRAMERATE; MSHookFunction((void *)addr, (void *)HookSetTargetFrameRate, (void **)&orig_set_targetFrameRate); IPALog([NSString stringWithFormat: @"Application.set_targetFrameRate hooked @0x%lx (base+0x%x) fps=%d", - (unsigned long)addr, RVA_SET_TARGET_FRAMERATE, (int)KFTargetFPS()]); + (unsigned long)addr, RVA_SET_TARGET_FRAMERATE, (int)KIOUTargetFPS()]); } #endif diff --git a/Sources/KiouForge/Hook/KifuObserve.m b/Sources/KiouForge/Hook/KifuObserve.m index 92f2bc3..9ae13fa 100644 --- a/Sources/KiouForge/Hook/KifuObserve.m +++ b/Sources/KiouForge/Hook/KifuObserve.m @@ -5,7 +5,7 @@ // // Five sites (AI / CPUStream / LocalPvP / OnlinePvP / RecordReplay) all // route into mode-specific HookXxxEnd(self, ct) observer bodies that -// invoke KFKifuObserveMatchEnd with the right KiouMatchMode index. +// invoke KIOUKifuObserveMatchEnd with the right KiouMatchMode index. // // Chinlan path: // Each cave passes its hook_id in W6 to the shared observer slot at @@ -23,8 +23,8 @@ #define RVA_ONLINE_END KIOU_HOOK_RVA_ONLINE_END #define RVA_REPLAY_END KIOU_HOOK_RVA_REPLAY_END -// 16-byte UniTask return (see KFUniTaskRet in Internal.h). -typedef KFUniTaskRet (*OnMatchEndAsync_t)(void *self, void *ct); +// 16-byte UniTask return (see KIOUUniTaskRet in Internal.h). +typedef KIOUUniTaskRet (*OnMatchEndAsync_t)(void *self, void *ct); #if !IPA_CHINLAN static OnMatchEndAsync_t orig_AI_End = NULL; @@ -38,47 +38,47 @@ // Observer bodies — single source of truth, called by both chinlan // dispatch_one and (via the JB trampolines below) MSHookFunction. // --------------------------------------------------------------------------- -void HookAiEnd (void *self, void *ct) { KFKifuObserveMatchEnd(self, ct, KIOU_MMODE_AI_MATCH); } -void HookCpuStreamEnd(void *self, void *ct) { KFKifuObserveMatchEnd(self, ct, KIOU_MMODE_CPU_STREAM); } -void HookLocalEnd (void *self, void *ct) { KFKifuObserveMatchEnd(self, ct, KIOU_MMODE_LOCAL_PVP); } -void HookOnlineEnd (void *self, void *ct) { KFKifuObserveMatchEnd(self, ct, KIOU_MMODE_ONLINE_PVP); } -void HookReplayEnd (void *self, void *ct) { KFKifuObserveMatchEnd(self, ct, KIOU_MMODE_RECORD_REPLAY); } +void HookAiEnd (void *self, void *ct) { KIOUKifuObserveMatchEnd(self, ct, KIOU_MMODE_AI_MATCH); } +void HookCpuStreamEnd(void *self, void *ct) { KIOUKifuObserveMatchEnd(self, ct, KIOU_MMODE_CPU_STREAM); } +void HookLocalEnd (void *self, void *ct) { KIOUKifuObserveMatchEnd(self, ct, KIOU_MMODE_LOCAL_PVP); } +void HookOnlineEnd (void *self, void *ct) { KIOUKifuObserveMatchEnd(self, ct, KIOU_MMODE_ONLINE_PVP); } +void HookReplayEnd (void *self, void *ct) { KIOUKifuObserveMatchEnd(self, ct, KIOU_MMODE_RECORD_REPLAY); } #if !IPA_CHINLAN // JB-flavour trampolines: observer body first, then chain to orig. -static KFUniTaskRet ThunkAIEnd(void *self, void *ct) { +static KIOUUniTaskRet ThunkAIEnd(void *self, void *ct) { HookAiEnd(self, ct); if (orig_AI_End) return orig_AI_End(self, ct); - return (KFUniTaskRet){ NULL, NULL }; + return (KIOUUniTaskRet){ NULL, NULL }; } -static KFUniTaskRet ThunkCPUStreamEnd(void *self, void *ct) { +static KIOUUniTaskRet ThunkCPUStreamEnd(void *self, void *ct) { HookCpuStreamEnd(self, ct); if (orig_CPUStream_End) return orig_CPUStream_End(self, ct); - return (KFUniTaskRet){ NULL, NULL }; + return (KIOUUniTaskRet){ NULL, NULL }; } -static KFUniTaskRet ThunkLocalEnd(void *self, void *ct) { +static KIOUUniTaskRet ThunkLocalEnd(void *self, void *ct) { HookLocalEnd(self, ct); if (orig_Local_End) return orig_Local_End(self, ct); - return (KFUniTaskRet){ NULL, NULL }; + return (KIOUUniTaskRet){ NULL, NULL }; } -static KFUniTaskRet ThunkOnlineEnd(void *self, void *ct) { +static KIOUUniTaskRet ThunkOnlineEnd(void *self, void *ct) { HookOnlineEnd(self, ct); if (orig_Online_End) return orig_Online_End(self, ct); - return (KFUniTaskRet){ NULL, NULL }; + return (KIOUUniTaskRet){ NULL, NULL }; } -static KFUniTaskRet ThunkReplayEnd(void *self, void *ct) { +static KIOUUniTaskRet ThunkReplayEnd(void *self, void *ct) { HookReplayEnd(self, ct); if (orig_RecordReplay_End) return orig_RecordReplay_End(self, ct); - return (KFUniTaskRet){ NULL, NULL }; + return (KIOUUniTaskRet){ NULL, NULL }; } #endif -void KFInstallKifuObserveHook(uintptr_t unityBase) { - NSString *outDir = KFKifEnsureOutputDir(); +void KIOUInstallKifuObserveHook(uintptr_t unityBase) { + NSString *outDir = KIOUKifEnsureOutputDir(); IPALog([NSString stringWithFormat:@"[KIFU] output dir = %@", outDir ?: @"(failed)"]); #if IPA_CHINLAN - // Nothing to do — KFChinlanPublish has already wired dispatch_one + // Nothing to do — KIOUChinlanPublish has already wired dispatch_one // into the observer slot; dispatch_one switches on W6=hook_id and // calls HookXxxEnd directly. (void)unityBase; diff --git a/Sources/KiouForge/Hook/SettingsUI.m b/Sources/KiouForge/Hook/SettingsUI.m index 2134632..4288d2f 100644 --- a/Sources/KiouForge/Hook/SettingsUI.m +++ b/Sources/KiouForge/Hook/SettingsUI.m @@ -15,16 +15,16 @@ // Declare the apply helper from Hook_FrameRate.m so we can call it from // the FPS stepper callback. -extern void KFApplyFPS(int32_t fps); +extern void KIOUApplyFPS(int32_t fps); // --------------------------------------------------------------------------- -// KFAccountsViewController — account list with select / delete / reorder. +// KIOUAccountsViewController — account list with select / delete / reorder. // --------------------------------------------------------------------------- -@interface KFAccountsViewController : UITableViewController +@interface KIOUAccountsViewController : UITableViewController @property (nonatomic, strong) NSMutableArray *accounts; @end -@implementation KFAccountsViewController +@implementation KIOUAccountsViewController - (instancetype)init { self = [super initWithStyle:UITableViewStyleInsetGrouped]; @@ -39,11 +39,11 @@ - (void)viewDidLoad { target:self action:@selector(exportAccounts:)]; self.navigationItem.rightBarButtonItems = @[self.editButtonItem, share]; - self.accounts = [NSMutableArray arrayWithArray:KFListAccounts()]; + self.accounts = [NSMutableArray arrayWithArray:KIOUListAccounts()]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAccountStateChanged:) - name:KFAccountStateChangedNotification + name:KIOUAccountStateChangedNotification object:nil]; } @@ -53,13 +53,13 @@ - (void)dealloc { - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - self.accounts = [NSMutableArray arrayWithArray:KFListAccounts()]; + self.accounts = [NSMutableArray arrayWithArray:KIOUListAccounts()]; [self.tableView reloadData]; } - (void)onAccountStateChanged:(NSNotification *)note { dispatch_async(dispatch_get_main_queue(), ^{ - self.accounts = [NSMutableArray arrayWithArray:KFListAccounts()]; + self.accounts = [NSMutableArray arrayWithArray:KIOUListAccounts()]; [self.tableView reloadData]; }); } @@ -67,7 +67,7 @@ - (void)onAccountStateChanged:(NSNotification *)note { - (void)exportAccounts:(UIBarButtonItem *)sender { NSError *err = nil; NSData *data = [NSJSONSerialization - dataWithJSONObject:KFListAccounts() + dataWithJSONObject:KIOUListAccounts() options:NSJSONWritingPrettyPrinted | NSJSONWritingSortedKeys error:&err]; if (data.length == 0) { @@ -117,7 +117,7 @@ - (UITableViewCell *)tableView:(UITableView *)tv NSString *userId = acc[@"userId"]; cell.textLabel.text = userName.length > 0 ? userName : @"(no name)"; cell.detailTextLabel.text = openId.length > 0 ? openId : @"(no open id)"; - NSString *activeUserId = KFActiveAccountUserId(); + NSString *activeUserId = KIOUActiveAccountUserId(); cell.accessoryType = ([userId isKindOfClass:[NSString class]] && [userId isEqualToString:activeUserId]) ? UITableViewCellAccessoryCheckmark @@ -146,15 +146,15 @@ - (void)tableView:(UITableView *)tv didSelectRowAtIndexPath:(NSIndexPath *)ip { [alert addAction:[UIAlertAction actionWithTitle:@"切り替え" style:UIAlertActionStyleDefault handler:^(UIAlertAction *_) { - if (uuid.length > 0) KFSwitchAccount(uuid); - KFSetActiveAccountUserId(userId); + if (uuid.length > 0) KIOUSwitchAccount(uuid); + KIOUSetActiveAccountUserId(userId); // Close the settings modal then let KIOU navigate itself back to the // title scene — that re-runs AccountExists → LoginAsync with the // pending_device_id substitution in effect, no app relaunch needed. UIViewController *modalRoot = self.navigationController ?: self; UIViewController *presenter = modalRoot.presentingViewController; [presenter dismissViewControllerAnimated:YES completion:^{ - KFNavigateToTitleScene(); + KIOUNavigateToTitleScene(); }]; }]]; [self presentViewController:alert animated:YES completion:nil]; @@ -170,7 +170,7 @@ - (void)tableView:(UITableView *)tv if (ip.row >= (NSInteger)self.accounts.count) return; NSString *userId = self.accounts[ip.row][@"userId"]; [self.accounts removeObjectAtIndex:ip.row]; - if ([userId isKindOfClass:[NSString class]]) KFDeleteAccount(userId); + if ([userId isKindOfClass:[NSString class]]) KIOUDeleteAccount(userId); [tv deleteRowsAtIndexPaths:@[ip] withRowAnimation:UITableViewRowAnimationAutomatic]; } @@ -190,7 +190,7 @@ - (void)tableView:(UITableView *)tv // --------------------------------------------------------------------------- // Root settings view controller. // --------------------------------------------------------------------------- -@interface KFSettingsViewController : UITableViewController +@interface KIOUSettingsViewController : UITableViewController @property (nonatomic, strong) UILabel *fpsValueLabel; @property (nonatomic, strong) UILabel *depthValueLabel; @property (nonatomic, strong) UILabel *hashValueLabel; @@ -198,7 +198,7 @@ @interface KFSettingsViewController : UITableViewController @end // Sub-screen: per-match-mode toggles for kifu autosave. -@interface KFKifuModesViewController : UITableViewController +@interface KIOUKifuModesViewController : UITableViewController @end // Mirror of the preset tables in Persistence.m for local label rendering. @@ -231,7 +231,7 @@ @interface KFKifuModesViewController : UITableViewController static NSString *const kAboutRepoURL = @"https://github.com/IPA-Patch/KiouForge"; static NSString *const kAboutTwitterURL = @"https://x.com/tkgling"; -@implementation KFSettingsViewController +@implementation KIOUSettingsViewController - (instancetype)init { if ((self = [super initWithStyle:UITableViewStyleInsetGrouped])) { @@ -249,7 +249,7 @@ - (void)viewDidLoad { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAccountStateChanged:) - name:KFAccountStateChangedNotification + name:KIOUAccountStateChangedNotification object:nil]; } @@ -347,9 +347,9 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cell.detailTextLabel.textColor = UIColor.secondaryLabelColor; } cell.textLabel.text = @"Active"; - NSString *activeUserId = KFActiveAccountUserId(); + NSString *activeUserId = KIOUActiveAccountUserId(); NSString *activeName = nil; - for (NSDictionary *acc in KFListAccounts()) { + for (NSDictionary *acc in KIOUListAccounts()) { NSString *u = acc[@"userId"]; if ([u isKindOfClass:[NSString class]] && [u isEqualToString:activeUserId]) { NSString *n = acc[@"userName"]; @@ -374,7 +374,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cell2.accessoryView = sw; } cell2.textLabel.text = @"New Register"; - ((UISwitch *)cell2.accessoryView).on = KFForceRegisterOnNextLaunch(); + ((UISwitch *)cell2.accessoryView).on = KIOUForceRegisterOnNextLaunch(); return cell2; } @@ -386,25 +386,25 @@ - (UITableViewCell *)tableView:(UITableView *)tableView // "3 of 5") + disclosure indicator. // We dequeue under distinct identifiers so the cached style stays // correct across reuse. - if (KFFeatureHasNavigation(f)) { + if (KIOUFeatureHasNavigation(f)) { static NSString *kId = @"feature-nav"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kId]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:kId]; } - cell.textLabel.text = KFFeatureLabel(f); + cell.textLabel.text = KIOUFeatureLabel(f); cell.accessoryView = nil; // disclosure indicator below cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; cell.selectionStyle = UITableViewCellSelectionStyleDefault; // Caption: master state + per-mode count for Kifu Autosave. if (f == KIOU_FEATURE_KIFU_AUTOSAVE) { - if (!KFFeatureEnabled(f)) { + if (!KIOUFeatureEnabled(f)) { cell.detailTextLabel.text = @"Off"; } else { int32_t on = 0; for (int i = 0; i < KIOU_MMODE_COUNT; i++) { - if (KFKifuModeEnabled((KiouMatchMode)i)) on++; + if (KIOUKifuModeEnabled((KiouMatchMode)i)) on++; } cell.detailTextLabel.text = [NSString stringWithFormat:@"%d of %ld", @@ -412,7 +412,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView } } else { cell.detailTextLabel.text = - KFFeatureEnabled(f) ? @"On" : @"Off"; + KIOUFeatureEnabled(f) ? @"On" : @"Off"; } cell.detailTextLabel.textColor = UIColor.secondaryLabelColor; return cell; @@ -425,9 +425,9 @@ - (UITableViewCell *)tableView:(UITableView *)tableView reuseIdentifier:kId]; cell.selectionStyle = UITableViewCellSelectionStyleNone; } - cell.textLabel.text = KFFeatureLabel(f); + cell.textLabel.text = KIOUFeatureLabel(f); UISwitch *sw = [[UISwitch alloc] init]; - sw.on = KFFeatureEnabled(f); + sw.on = KIOUFeatureEnabled(f); sw.tag = f; [sw addTarget:self action:@selector(onFeatureToggle:) forControlEvents:UIControlEventValueChanged]; @@ -449,7 +449,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView stepper.continuous = NO; // Only one row in Performance (FPS) for now. - int32_t idx = KFFPSIndex(); + int32_t idx = KIOUFPSIndex(); cell.textLabel.text = @"FPS"; cell.detailTextLabel.text = [NSString stringWithFormat:@"%d", kFpsPresets[idx]]; self.fpsValueLabel = cell.detailTextLabel; @@ -478,17 +478,17 @@ - (UITableViewCell *)tableView:(UITableView *)tableView if (indexPath.row == KF_ENGINE_ROW_DEPTH) { cell.textLabel.text = @"Analysis Depth"; cell.detailTextLabel.text = [NSString stringWithFormat:@"%d", - (int)KFAnalysisDepth()]; + (int)KIOUAnalysisDepth()]; self.depthValueLabel = cell.detailTextLabel; stepper.minimumValue = 1; stepper.maximumValue = 36; stepper.stepValue = 1; - stepper.value = KFAnalysisDepth(); + stepper.value = KIOUAnalysisDepth(); [stepper addTarget:self action:@selector(onDepthChanged:) forControlEvents:UIControlEventValueChanged]; } else if (indexPath.row == KF_ENGINE_ROW_HASH) { - int32_t idx = KFAnalysisHashIndex(); + int32_t idx = KIOUAnalysisHashIndex(); cell.textLabel.text = @"Analysis Hash"; cell.detailTextLabel.text = [NSString stringWithFormat:@"%d MB", kHashPresets[idx]]; self.hashValueLabel = cell.detailTextLabel; @@ -502,12 +502,12 @@ - (UITableViewCell *)tableView:(UITableView *)tableView } else { // KF_ENGINE_ROW_SKILL cell.textLabel.text = @"Analysis Skill"; cell.detailTextLabel.text = [NSString stringWithFormat:@"%d", - (int)KFAnalysisSkillLevel()]; + (int)KIOUAnalysisSkillLevel()]; self.skillValueLabel = cell.detailTextLabel; stepper.minimumValue = 1; stepper.maximumValue = 20; stepper.stepValue = 1; - stepper.value = KFAnalysisSkillLevel(); + stepper.value = KIOUAnalysisSkillLevel(); [stepper addTarget:self action:@selector(onSkillChanged:) forControlEvents:UIControlEventValueChanged]; } @@ -539,16 +539,16 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath if (indexPath.section == KF_SECTION_ACCOUNT && indexPath.row == KF_ACCOUNT_ROW_ACTIVE) { - KFAccountsViewController *vc = [[KFAccountsViewController alloc] init]; + KIOUAccountsViewController *vc = [[KIOUAccountsViewController alloc] init]; [self.navigationController pushViewController:vc animated:YES]; return; } if (indexPath.section == KF_SECTION_FEATURES) { KiouFeature f = (KiouFeature)indexPath.row; - if (!KFFeatureHasNavigation(f)) return; + if (!KIOUFeatureHasNavigation(f)) return; if (f == KIOU_FEATURE_KIFU_AUTOSAVE) { - KFKifuModesViewController *vc = [[KFKifuModesViewController alloc] init]; + KIOUKifuModesViewController *vc = [[KIOUKifuModesViewController alloc] init]; [self.navigationController pushViewController:vc animated:YES]; } return; @@ -565,14 +565,14 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath // --------------------------------------------------------------------------- - (void)onForceRegisterChanged:(UISwitch *)sw { - KFSetForceRegisterOnNextLaunch(sw.on); + KIOUSetForceRegisterOnNextLaunch(sw.on); if (sw.on) { NSString *fresh = [[NSUUID UUID] UUIDString].lowercaseString; - KFSetPendingDistinctId(fresh); - KFSetPendingDeviceId(fresh); + KIOUSetPendingDistinctId(fresh); + KIOUSetPendingDeviceId(fresh); } else { - KFSetPendingDistinctId(nil); - KFSetPendingDeviceId(nil); + KIOUSetPendingDistinctId(nil); + KIOUSetPendingDeviceId(nil); } IPALog([NSString stringWithFormat:@"[SETTINGS] force_register=%s", sw.on ? "true" : "false"]); @@ -580,38 +580,38 @@ - (void)onForceRegisterChanged:(UISwitch *)sw { - (void)onFeatureToggle:(UISwitch *)sw { KiouFeature f = (KiouFeature)sw.tag; - KFSetFeatureEnabled(f, sw.isOn); + KIOUSetFeatureEnabled(f, sw.isOn); IPALog([NSString stringWithFormat: - @"[SETTINGS] %@ -> %@", KFFeatureLabel(f), sw.isOn ? @"ON" : @"OFF"]); + @"[SETTINGS] %@ -> %@", KIOUFeatureLabel(f), sw.isOn ? @"ON" : @"OFF"]); } - (void)onFpsChanged:(UIStepper *)stepper { int32_t idx = (int32_t)stepper.value; - KFSetFPSIndex(idx); - int32_t fps = KFTargetFPS(); + KIOUSetFPSIndex(idx); + int32_t fps = KIOUTargetFPS(); self.fpsValueLabel.text = [NSString stringWithFormat:@"%d", fps]; - KFApplyFPS(fps); // apply immediately + KIOUApplyFPS(fps); // apply immediately IPALog([NSString stringWithFormat:@"[SETTINGS] fps -> %d (idx=%d)", fps, idx]); } - (void)onDepthChanged:(UIStepper *)stepper { int32_t v = (int32_t)stepper.value; - KFSetAnalysisDepth(v); + KIOUSetAnalysisDepth(v); self.depthValueLabel.text = [NSString stringWithFormat:@"%d", v]; IPALog([NSString stringWithFormat:@"[SETTINGS] analysis depth -> %d", v]); } - (void)onHashChanged:(UIStepper *)stepper { int32_t idx = (int32_t)stepper.value; - KFSetAnalysisHashIndex(idx); - int32_t mb = KFAnalysisHashMB(); + KIOUSetAnalysisHashIndex(idx); + int32_t mb = KIOUAnalysisHashMB(); self.hashValueLabel.text = [NSString stringWithFormat:@"%d MB", mb]; IPALog([NSString stringWithFormat:@"[SETTINGS] analysis hash -> %d MB (idx=%d)", mb, idx]); } - (void)onSkillChanged:(UIStepper *)stepper { int32_t v = (int32_t)stepper.value; - KFSetAnalysisSkillLevel(v); + KIOUSetAnalysisSkillLevel(v); self.skillValueLabel.text = [NSString stringWithFormat:@"%d", v]; IPALog([NSString stringWithFormat:@"[SETTINGS] analysis skill -> %d", v]); } @@ -619,20 +619,20 @@ - (void)onSkillChanged:(UIStepper *)stepper { @end // =========================================================================== -// KFKifuModesViewController — per-mode kifu autosave toggles. +// KIOUKifuModesViewController — per-mode kifu autosave toggles. // // Pushed by the root controller when the Kifu Autosave row is tapped. One // section, KIOU_MMODE_COUNT rows (AI / CPUStream / LocalPvP / OnlinePvP / -// RecordReplay), each a UISwitch on `KFKifuModeEnabled(mode)`. +// RecordReplay), each a UISwitch on `KIOUKifuModeEnabled(mode)`. // // Independent of the master KIOU_FEATURE_KIFU_AUTOSAVE flag — the master // stays where it is on the root screen; this screen edits only the per-mode // flags. The on-device hook (Hook_KifuObserve.m's -// KFKifuObserveMatchEnd) gates emission on BOTH the master and the +// KIOUKifuObserveMatchEnd) gates emission on BOTH the master and the // per-mode flag. // =========================================================================== -@implementation KFKifuModesViewController +@implementation KIOUKifuModesViewController - (instancetype)init { if ((self = [super initWithStyle:UITableViewStyleInsetGrouped])) { @@ -669,9 +669,9 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cell.selectionStyle = UITableViewCellSelectionStyleNone; } KiouMatchMode m = (KiouMatchMode)indexPath.row; - cell.textLabel.text = KFKifuModeLabel(m); + cell.textLabel.text = KIOUKifuModeLabel(m); UISwitch *sw = [[UISwitch alloc] init]; - sw.on = KFKifuModeEnabled(m); + sw.on = KIOUKifuModeEnabled(m); sw.tag = m; [sw addTarget:self action:@selector(onModeToggle:) forControlEvents:UIControlEventValueChanged]; @@ -681,10 +681,10 @@ - (UITableViewCell *)tableView:(UITableView *)tableView - (void)onModeToggle:(UISwitch *)sw { KiouMatchMode m = (KiouMatchMode)sw.tag; - KFSetKifuModeEnabled(m, sw.isOn); + KIOUSetKifuModeEnabled(m, sw.isOn); IPALog([NSString stringWithFormat: @"[SETTINGS] kifu mode %@ -> %@", - KFKifuModeLabel(m), sw.isOn ? @"ON" : @"OFF"]); + KIOUKifuModeLabel(m), sw.isOn ? @"ON" : @"OFF"]); } @end @@ -708,7 +708,7 @@ - (void)onModeToggle:(UISwitch *)sw { return fallback; } -void KFPresentSettings(void) { +void KIOUPresentSettings(void) { dispatch_async(dispatch_get_main_queue(), ^{ UIWindow *win = kfActiveWindow(); if (!win) { IPALog(@"[SETTINGS] no active window"); return; } @@ -720,7 +720,7 @@ void KFPresentSettings(void) { // Wrap in a UINavigationController so feature rows that need a // sub-screen (e.g. Kifu Autosave's per-mode toggles) get push // transitions for free. - KFSettingsViewController *root_vc = [[KFSettingsViewController alloc] init]; + KIOUSettingsViewController *root_vc = [[KIOUSettingsViewController alloc] init]; UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:root_vc]; nav.modalPresentationStyle = UIModalPresentationFormSheet; diff --git a/Sources/KiouForge/Hook/Version.m b/Sources/KiouForge/Hook/Version.m index 2361171..89ad88f 100644 --- a/Sources/KiouForge/Hook/Version.m +++ b/Sources/KiouForge/Hook/Version.m @@ -8,7 +8,7 @@ // =========================================================================== #ifndef IPA_CHINLAN -void KFInstallVersionHook(__unused uintptr_t unityBase) { } +void KIOUInstallVersionHook(__unused uintptr_t unityBase) { } #else -void KFPublishVersionSlots(__unused uintptr_t unityBase) { } +void KIOUPublishVersionSlots(__unused uintptr_t unityBase) { } #endif diff --git a/Sources/KiouForge/Internal.h b/Sources/KiouForge/Internal.h index 132d151..7b4a673 100644 --- a/Sources/KiouForge/Internal.h +++ b/Sources/KiouForge/Internal.h @@ -8,7 +8,7 @@ // Internal.h — KiouForge tweak-local declarations. // // The KIOU binary catalog (RVAs, hook-id enum, dispatcher externs, shared -// installer protos, KFUniTaskRet, KFNavigateToTitleScene) lives in +// installer protos, KIOUUniTaskRet, KIOUNavigateToTitleScene) lives in // vendor/KIOU-Hook/KIOUHook.h and is brought in by the import above. // =========================================================================== @@ -36,13 +36,13 @@ static inline void writeI32(void *base, uintptr_t off, int32_t val) { // --------------------------------------------------------------------------- // Tweak-local hook installers (KiouForge feature hooks). -// Shared installer protos (KFInstallAccountObserveHook / -// KFInstallGrpcLoggingHook) come from KIOUHook.h. +// Shared installer protos (KIOUInstallAccountObserveHook / +// KIOUInstallGrpcLoggingHook) come from KIOUHook.h. // --------------------------------------------------------------------------- -void KFInstallFrameRateHook(uintptr_t unityBase); -void KFInstallAnalysisTuneHook(uintptr_t unityBase); -void KFInstallKifuObserveHook(uintptr_t unityBase); +void KIOUInstallFrameRateHook(uintptr_t unityBase); +void KIOUInstallAnalysisTuneHook(uintptr_t unityBase); +void KIOUInstallKifuObserveHook(uintptr_t unityBase); // --------------------------------------------------------------------------- // SyncSearchResult — mirrors Project.RshogiEngine.SyncSearchResult (struct). @@ -59,7 +59,7 @@ typedef struct { int32_t Score; void *PV; // il2cpp String* int32_t Depth; -} KFSyncSearchResult; +} KIOUSyncSearchResult; // --------------------------------------------------------------------------- // KIF autosave — ported from KiouKifExporter. @@ -86,21 +86,21 @@ typedef struct { // x0 = self (the IMatchMode instance) // x1 = ct (CancellationToken) // x2 = mode_index (KiouMatchMode, injected by cave's MOVZ X2,#imm) -KFUniTaskRet KFKifuObserveMatchEnd(void *self, void *ct, uint32_t mode_index); +KIOUUniTaskRet KIOUKifuObserveMatchEnd(void *self, void *ct, uint32_t mode_index); // Kif_Writer pipeline. -NSString *KFKifWriterEmit(void *gameCtrl, +NSString *KIOUKifWriterEmit(void *gameCtrl, void *matchConfig, void *stateStore, const char *matchModeTag); // Helpers. -NSString *KFKifTimestamp(void); -NSString *KFKifSanitizeSegment(NSString *s, NSUInteger maxChars); -NSString *KFKifEnsureOutputDir(void); -NSString *KFKifDescribeStartpos(void *gameCtrl); -NSString *KFKifDescribeOpponents(void *matchConfig, void *stateStore); -NSString *KFKifTextFromGameController(void *gameCtrl, +NSString *KIOUKifTimestamp(void); +NSString *KIOUKifSanitizeSegment(NSString *s, NSUInteger maxChars); +NSString *KIOUKifEnsureOutputDir(void); +NSString *KIOUKifDescribeStartpos(void *gameCtrl); +NSString *KIOUKifDescribeOpponents(void *matchConfig, void *stateStore); +NSString *KIOUKifTextFromGameController(void *gameCtrl, void *matchConfig, void *stateStore, const char *matchModeTag); @@ -113,8 +113,8 @@ NSString *KFKifTextFromGameController(void *gameCtrl, #define KIFOPTS_OFF_TIME_RULE_LABEL 0x38 #define KIFOPTS_OFF_ENDING_LABEL 0x48 -void *KFIl2cppStringNew(const char *utf8); -void KFKifFillWriteOptions(void *opts, +void *KIOUIl2cppStringNew(const char *utf8); +void KIOUKifFillWriteOptions(void *opts, void *matchConfig, void *stateStore, void *gameCtrl, @@ -131,11 +131,11 @@ typedef NS_ENUM(NSInteger, KiouFeature) { KIOU_FEATURE_COUNT, }; -bool KFFeatureEnabled(KiouFeature f); -void KFSetFeatureEnabled(KiouFeature f, bool enabled); -NSString *KFFeatureLabel(KiouFeature f); +bool KIOUFeatureEnabled(KiouFeature f); +void KIOUSetFeatureEnabled(KiouFeature f, bool enabled); +NSString *KIOUFeatureLabel(KiouFeature f); -bool KFFeatureHasNavigation(KiouFeature f); +bool KIOUFeatureHasNavigation(KiouFeature f); // --------------------------------------------------------------------------- // Per-match-mode toggles for kifu autosave. @@ -149,9 +149,9 @@ typedef NS_ENUM(NSInteger, KiouMatchMode) { KIOU_MMODE_COUNT, }; -bool KFKifuModeEnabled(KiouMatchMode m); -void KFSetKifuModeEnabled(KiouMatchMode m, bool enabled); -NSString *KFKifuModeLabel(KiouMatchMode m); +bool KIOUKifuModeEnabled(KiouMatchMode m); +void KIOUSetKifuModeEnabled(KiouMatchMode m, bool enabled); +NSString *KIOUKifuModeLabel(KiouMatchMode m); // IMatchMode self -> _gameAdapter field offsets. The enum order MUST match // the observer rows of _SITES in recipes/ — the recipe bakes each index as @@ -178,21 +178,21 @@ static const char *const kKiouMatchModeTags[KIOU_MMODE_COUNT] = { #define KIOU_FPS_PRESET_COUNT 7 // Presets (preset[i] is the FPS value): {15,24,30,45,60,90,120} -int32_t KFTargetFPS(void); -int32_t KFFPSIndex(void); -void KFSetFPSIndex(int32_t idx); +int32_t KIOUTargetFPS(void); +int32_t KIOUFPSIndex(void); +void KIOUSetFPSIndex(int32_t idx); // --------------------------------------------------------------------------- // Analysis engine tuning (NativeSyncSession post-game path only). // --------------------------------------------------------------------------- -int32_t KFAnalysisDepth(void); -void KFSetAnalysisDepth(int32_t v); +int32_t KIOUAnalysisDepth(void); +void KIOUSetAnalysisDepth(int32_t v); #define KIOU_ANALYSIS_HASH_PRESET_COUNT 6 // Presets: {16, 64, 128, 256, 512, 1024} -int32_t KFAnalysisHashIndex(void); -void KFSetAnalysisHashIndex(int32_t idx); -int32_t KFAnalysisHashMB(void); -int32_t KFAnalysisSkillLevel(void); -void KFSetAnalysisSkillLevel(int32_t v); +int32_t KIOUAnalysisHashIndex(void); +void KIOUSetAnalysisHashIndex(int32_t idx); +int32_t KIOUAnalysisHashMB(void); +int32_t KIOUAnalysisSkillLevel(void); +void KIOUSetAnalysisSkillLevel(int32_t v); diff --git a/Sources/KiouForge/Kif/Helpers.m b/Sources/KiouForge/Kif/Helpers.m index 0ab0d53..e575473 100644 --- a/Sources/KiouForge/Kif/Helpers.m +++ b/Sources/KiouForge/Kif/Helpers.m @@ -8,7 +8,7 @@ // =========================================================================== // kif_trace_log — KIF_TRACE-gated wrapper around IPALog. // -// Diagnostic logging inside KFKifFillWriteOptions (the KIFWriteOptions +// Diagnostic logging inside KIOUKifFillWriteOptions (the KIFWriteOptions // fill path) is verbose enough that we don't want it in release builds. // Pass `-DKIF_TRACE=1` to the compiler (Makefile honors `TRACE=1`) when // you need to see every pointer the fill walks and every slot it writes. @@ -26,13 +26,13 @@ // =========================================================================== // Helpers.m — small utilities for the KIF export path. // -// * KFKifTimestamp — filename timestamp -// * KFKifSanitizeSegment — make user-supplied strings safe -// * KFKifEnsureOutputDir — create Documents/KiouForge/ -// * KFKifTextFromGameController — full KIF 2.0 via KIFWriter.Write -// * KFKifDescribeStartpos — pick "startpos"/"sfen-"/"unknown" +// * KIOUKifTimestamp — filename timestamp +// * KIOUKifSanitizeSegment — make user-supplied strings safe +// * KIOUKifEnsureOutputDir — create Documents/KiouForge/ +// * KIOUKifTextFromGameController — full KIF 2.0 via KIFWriter.Write +// * KIOUKifDescribeStartpos — pick "startpos"/"sfen-"/"unknown" // -// KFKifTextFromGameController is the bridge into il2cpp — everything else is +// KIOUKifTextFromGameController is the bridge into il2cpp — everything else is // pure Foundation. // // Background: GameController.GetKifuText (RVA 0x5D43D10) returns an in-app @@ -51,7 +51,7 @@ // just runs the il2cpp init; it sets // no field to a non-zero value) // ↓ -// KFKifFillWriteOptions(opts, ...) → best-effort write of: +// KIOUKifFillWriteOptions(opts, ...) → best-effort write of: // StartDateTime (unconditional) // MatchTitle (unconditional) // EndingLabel (from GameController.Reason) @@ -182,10 +182,10 @@ static void resolveIl2cppFunctions(void) { } // --------------------------------------------------------------------------- -// KFKifTextFromGameController +// KIOUKifTextFromGameController // // Run the full GetUSIText → ParseUSI → KIFWriteOptions..ctor → -// KFKifFillWriteOptions → KIFWriter.Write pipeline and return the +// KIOUKifFillWriteOptions → KIFWriter.Write pipeline and return the // resulting KIF 2.0 string. Returns nil on any failure (NULL receiver, // USI text empty, ParseUSI gave back NULL, KIFWriter.Write threw, …). // @@ -197,7 +197,7 @@ static void resolveIl2cppFunctions(void) { // instance accessors and static methods in KIOU. Confirmed by Frida probe // (packages/frida/hook_kiou_kifwriter_probe.js). // --------------------------------------------------------------------------- -NSString *KFKifTextFromGameController(void *gameCtrl, +NSString *KIOUKifTextFromGameController(void *gameCtrl, void *matchConfig, void *stateStore, const char *matchModeTag) { @@ -260,7 +260,7 @@ static void resolveIl2cppFunctions(void) { // Step 3.5: fill the user-visible KIFWriteOptions fields. Safe to call // unconditionally — internal failures fall back to leaving the // specific field unset, which matches the pre-Phase-3 behavior. - KFKifFillWriteOptions(opts, matchConfig, stateStore, gameCtrl, matchModeTag); + KIOUKifFillWriteOptions(opts, matchConfig, stateStore, gameCtrl, matchModeTag); // Step 4: KIFWriter.Write(record, opts) → KIF 2.0 string void *kifStrPtr = NULL; @@ -280,14 +280,14 @@ static void resolveIl2cppFunctions(void) { } // --------------------------------------------------------------------------- -// KFKifDescribeStartpos +// KIOUKifDescribeStartpos // // Look at the GameController's PositionHistory[0] — that's the starting // position the match was set up from. Convert to SFEN; if it equals the // standard initial SFEN, return "startpos". Otherwise hash the SFEN and // return "sfen-<8 hex>" so the filename stays short. // --------------------------------------------------------------------------- -NSString *KFKifDescribeStartpos(void *gameCtrl) { +NSString *KIOUKifDescribeStartpos(void *gameCtrl) { resolveIl2cppFunctions(); if (!g_PositionToSFEN) return @"unknown"; if (!ptrLooksValid(gameCtrl)) return @"unknown"; @@ -325,13 +325,13 @@ static void resolveIl2cppFunctions(void) { } // --------------------------------------------------------------------------- -// KFKifTimestamp +// KIOUKifTimestamp // // Format the current wall clock as "YYYYMMDDTHHMMSS" (UTC). No separators // other than the ISO 'T' so the result is safe to drop into a POSIX // filename and sorts lexicographically. // --------------------------------------------------------------------------- -NSString *KFKifTimestamp(void) { +NSString *KIOUKifTimestamp(void) { static NSDateFormatter *fmt = nil; static dispatch_once_t once; dispatch_once(&once, ^{ @@ -344,7 +344,7 @@ static void resolveIl2cppFunctions(void) { } // --------------------------------------------------------------------------- -// KFKifSanitizeSegment +// KIOUKifSanitizeSegment // // Strip characters that are unsafe in POSIX filenames: NUL, '/', and any // C0/C1 control characters (U+0000–U+001F, U+007F–U+009F). Everything @@ -355,7 +355,7 @@ static void resolveIl2cppFunctions(void) { // Truncates to `maxChars` NSString code units after stripping. Falls back to // @"unknown" when the result is empty. // --------------------------------------------------------------------------- -NSString *KFKifSanitizeSegment(NSString *s, NSUInteger maxChars) { +NSString *KIOUKifSanitizeSegment(NSString *s, NSUInteger maxChars) { if (s.length == 0) return @"unknown"; NSMutableString *out = [NSMutableString stringWithCapacity:s.length]; for (NSUInteger i = 0; i < s.length; i++) { @@ -378,10 +378,10 @@ static void resolveIl2cppFunctions(void) { } // --------------------------------------------------------------------------- -// KFKifDescribeOpponents +// KIOUKifDescribeOpponents // // Build the "{black}vs{white}" filename segment from the live match objects. -// Mirrors the name-resolution order used by KFKifFillWriteOptions: +// Mirrors the name-resolution order used by KIOUKifFillWriteOptions: // 1. GameStateStore ReactiveProperty (online friend matches — // MatchConfig stays at the "プレイヤー" placeholder for the whole game, // but stateStore gets the real name once peer info arrives) @@ -390,7 +390,7 @@ static void resolveIl2cppFunctions(void) { // Either side may fall back to "unknown" independently. // Returns @"unknownvsunknown" when both sources are absent. // --------------------------------------------------------------------------- -NSString *KFKifDescribeOpponents(void *matchConfig, void *stateStore) { +NSString *KIOUKifDescribeOpponents(void *matchConfig, void *stateStore) { NSString *black = nil; NSString *white = nil; @@ -426,13 +426,13 @@ static void resolveIl2cppFunctions(void) { } } - NSString *b = KFKifSanitizeSegment(black ?: @"", 24); - NSString *w = KFKifSanitizeSegment(white ?: @"", 24); + NSString *b = KIOUKifSanitizeSegment(black ?: @"", 24); + NSString *w = KIOUKifSanitizeSegment(white ?: @"", 24); return [NSString stringWithFormat:@"%@vs%@", b, w]; } // --------------------------------------------------------------------------- -// KFIl2cppStringNew +// KIOUIl2cppStringNew // // dlsym the runtime's il2cpp_string_new export the first time we need it // and cache the function pointer. Returns NULL on: @@ -443,14 +443,14 @@ static void resolveIl2cppFunctions(void) { // "not present" which is what we want anyway) // // The returned il2cpp string is owned by the il2cpp runtime. See the -// Internal.h declaration of KFIl2cppStringNew for the GC lifetime caveat — +// Internal.h declaration of KIOUIl2cppStringNew for the GC lifetime caveat — // the string is NOT rooted, and we rely on the conservative Boehm GC // scanning the live stacks during the synchronous KIFWriter.Write to // keep it alive. That's an implementation assumption, not a contract. // --------------------------------------------------------------------------- typedef void *(*il2cpp_string_new_t)(const char *str); -void *KFIl2cppStringNew(const char *utf8) { +void *KIOUIl2cppStringNew(const char *utf8) { if (!utf8 || !*utf8) return NULL; static il2cpp_string_new_t fn = NULL; static dispatch_once_t once; @@ -550,7 +550,7 @@ static void resolveIl2cppFunctions(void) { } // --------------------------------------------------------------------------- -// KFKifFillWriteOptions +// KIOUKifFillWriteOptions // // Write the five user-visible KIFWriteOptions fields directly into `opts`. // `opts` MUST be a buffer that g_KIFOpts_Ctor() has already run on. @@ -596,7 +596,7 @@ static void resolveIl2cppFunctions(void) { // - rooting each string with il2cpp_gchandle_new for the duration // of KIFWriter.Write and releasing the handles afterwards. // --------------------------------------------------------------------------- -void KFKifFillWriteOptions(void *opts, +void KIOUKifFillWriteOptions(void *opts, void *matchConfig, void *stateStore, void *gameCtrl, @@ -724,11 +724,11 @@ void KFKifFillWriteOptions(void *opts, // ----- MatchTitle: "{mode} @ {iso8601}" via il2cpp_string_new. { - NSString *iso = KFKifTimestamp(); + NSString *iso = KIOUKifTimestamp(); NSString *title = [NSString stringWithFormat:@"%s @ %@", matchModeTag ? matchModeTag : "unknown", iso]; void *titleStr = - KFIl2cppStringNew(title.UTF8String); + KIOUIl2cppStringNew(title.UTF8String); kif_trace_log(@"[FILL] MatchTitle il2cpp_string_new(\"%@\") = %p", title, titleStr); if (titleStr) { @@ -745,7 +745,7 @@ void KFKifFillWriteOptions(void *opts, matchConfig, MC_OFF_TIME_CONTROL, tc, label ?: @"(nil)"); if (label) { - void *labelStr = KFIl2cppStringNew(label.UTF8String); + void *labelStr = KIOUIl2cppStringNew(label.UTF8String); if (labelStr) { memcpy(base + KIFOPTS_OFF_TIME_RULE_LABEL, &labelStr, sizeof(void *)); @@ -761,7 +761,7 @@ void KFKifFillWriteOptions(void *opts, gameCtrl, GC_OFF_WIN_REASON, (int)reason, label ?: @"(nil — none/unset)"); if (label) { - void *labelStr = KFIl2cppStringNew(label.UTF8String); + void *labelStr = KIOUIl2cppStringNew(label.UTF8String); if (labelStr) { memcpy(base + KIFOPTS_OFF_ENDING_LABEL, &labelStr, sizeof(void *)); @@ -791,7 +791,7 @@ void KFKifFillWriteOptions(void *opts, } // --------------------------------------------------------------------------- -// KFKifEnsureOutputDir +// KIOUKifEnsureOutputDir // // Returns the absolute path of ~/Documents/KiouForge/, creating it if // necessary. NSDocumentDirectory resolves to the running app's sandbox @@ -803,7 +803,7 @@ void KFKifFillWriteOptions(void *opts, // // Returns nil if NSFileManager refuses to create the directory. // --------------------------------------------------------------------------- -NSString *KFKifEnsureOutputDir(void) { +NSString *KIOUKifEnsureOutputDir(void) { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); diff --git a/Sources/KiouForge/Kif/Writer.m b/Sources/KiouForge/Kif/Writer.m index e6edf72..69615d1 100644 --- a/Sources/KiouForge/Kif/Writer.m +++ b/Sources/KiouForge/Kif/Writer.m @@ -23,14 +23,14 @@ // names are safe to include directly without percent-encoding or hashing. // =========================================================================== -NSString *KFKifWriterEmit(void *gameCtrl, +NSString *KIOUKifWriterEmit(void *gameCtrl, void *matchConfig, void *stateStore, const char *matchModeTag) { // 1. Get the KIF text. matchConfig / stateStore may be NULL — in // that case player names and time-rule label come out blank, // which is acceptable. - NSString *kif = KFKifTextFromGameController(gameCtrl, + NSString *kif = KIOUKifTextFromGameController(gameCtrl, matchConfig, stateStore, matchModeTag); @@ -43,7 +43,7 @@ } // 2. Make sure the output directory exists. - NSString *outDir = KFKifEnsureOutputDir(); + NSString *outDir = KIOUKifEnsureOutputDir(); if (!outDir) { IPALog(@"[KIF] emit failed: output dir unavailable"); return nil; @@ -53,12 +53,12 @@ // Format: {timestamp}_{mode}_{black}vs{white}_{startpos}.kif // Player names are kept as-is (Unicode) — APFS and iOS Files handle // Japanese filenames natively. Only POSIX-unsafe characters (NUL, '/', - // control codes) are stripped by KFKifSanitizeSegment. - NSString *ts = KFKifTimestamp(); - NSString *modeSeg = KFKifSanitizeSegment( + // control codes) are stripped by KIOUKifSanitizeSegment. + NSString *ts = KIOUKifTimestamp(); + NSString *modeSeg = KIOUKifSanitizeSegment( matchModeTag ? @(matchModeTag) : @"unknown", 32); - NSString *opponentsSeg = KFKifDescribeOpponents(matchConfig, stateStore); - NSString *startposSeg = KFKifDescribeStartpos(gameCtrl); + NSString *opponentsSeg = KIOUKifDescribeOpponents(matchConfig, stateStore); + NSString *startposSeg = KIOUKifDescribeStartpos(gameCtrl); NSString *filename = [NSString stringWithFormat:@"%@_%@_%@_%@.kif", ts, modeSeg, opponentsSeg, startposSeg]; @@ -86,16 +86,16 @@ } // =========================================================================== -// KFKifuObserveMatchEnd — entry point for all 5 IMatchMode.OnMatchEnd +// KIOUKifuObserveMatchEnd — entry point for all 5 IMatchMode.OnMatchEnd // sites. The observer cave (recipes/kiouforge.py) saves caller registers, // injects the mode index in X2 via MOVZ, BLRs through the slot, restores // registers, executes the displaced prologue, then jumps to orig+4. So -// our return value is effectively dead — we still zero a KFUniTaskRet for +// our return value is effectively dead — we still zero a KIOUUniTaskRet for // shape correctness. // // We honour two feature flags: // * master KIOU_FEATURE_KIFU_AUTOSAVE -// * per-mode KFKifuModeEnabled(mode_index) +// * per-mode KIOUKifuModeEnabled(mode_index) // Either being off skips emission silently. // =========================================================================== @@ -121,17 +121,17 @@ return readPtr(adapter, KIOU_ADAPTER_OFF_GAMECTRL); } -KFUniTaskRet KFKifuObserveMatchEnd(void *self, void *ct, +KIOUUniTaskRet KIOUKifuObserveMatchEnd(void *self, void *ct, uint32_t mode_index) { (void)ct; - KFUniTaskRet zero = { NULL, NULL }; + KIOUUniTaskRet zero = { NULL, NULL }; // Master toggle. - if (!KFFeatureEnabled(KIOU_FEATURE_KIFU_AUTOSAVE)) return zero; + if (!KIOUFeatureEnabled(KIOU_FEATURE_KIFU_AUTOSAVE)) return zero; // Per-mode toggle. if (mode_index < KIOU_MMODE_COUNT && - !KFKifuModeEnabled((KiouMatchMode)mode_index)) { + !KIOUKifuModeEnabled((KiouMatchMode)mode_index)) { return zero; } @@ -159,7 +159,7 @@ KFUniTaskRet KFKifuObserveMatchEnd(void *self, void *ct, @"[KIFU] %s self=%p gameCtrl=%p matchConfig=%p stateStore=%p — emitting", modeName, self, gameCtrl, matchConfig, stateStore]); - NSString *path = KFKifWriterEmit(gameCtrl, matchConfig, stateStore, modeName); + NSString *path = KIOUKifWriterEmit(gameCtrl, matchConfig, stateStore, modeName); if (path) { IPALog([NSString stringWithFormat:@"[KIFU] %s -> %@", modeName, path]); } diff --git a/Sources/KiouForge/Persistence.m b/Sources/KiouForge/Persistence.m index 74cc27c..45e57af 100644 --- a/Sources/KiouForge/Persistence.m +++ b/Sources/KiouForge/Persistence.m @@ -17,7 +17,7 @@ } } -NSString *KFFeatureLabel(KiouFeature f) { +NSString *KIOUFeatureLabel(KiouFeature f) { switch (f) { case KIOU_FEATURE_FPS_OVERRIDE: return @"FPS Override"; case KIOU_FEATURE_DISABLE_AFK: return @"AFK Guard"; @@ -27,13 +27,13 @@ } } -bool KFFeatureHasNavigation(KiouFeature f) { +bool KIOUFeatureHasNavigation(KiouFeature f) { // Kifu Autosave drills into a per-mode sub-screen. Everything else is // a plain toggle. return f == KIOU_FEATURE_KIFU_AUTOSAVE; } -bool KFFeatureEnabled(KiouFeature f) { +bool KIOUFeatureEnabled(KiouFeature f) { NSString *key = featureKey(f); if (!key) return false; NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; @@ -42,7 +42,7 @@ bool KFFeatureEnabled(KiouFeature f) { return [defs boolForKey:key]; } -void KFSetFeatureEnabled(KiouFeature f, bool enabled) { +void KIOUSetFeatureEnabled(KiouFeature f, bool enabled) { NSString *key = featureKey(f); if (!key) return; NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; @@ -68,22 +68,22 @@ static int32_t clampInt(int32_t v, int32_t lo, int32_t hi) { return v; } -int32_t KFFPSIndex(void) { +int32_t KIOUFPSIndex(void) { NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; if ([defs objectForKey:kFpsIndexKey] == nil) return 4; // index 4 = 60 fps return clampInt((int32_t)[defs integerForKey:kFpsIndexKey], 0, KIOU_FPS_PRESET_COUNT - 1); } -void KFSetFPSIndex(int32_t idx) { +void KIOUSetFPSIndex(int32_t idx) { NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; [defs setInteger:clampInt(idx, 0, KIOU_FPS_PRESET_COUNT - 1) forKey:kFpsIndexKey]; [defs synchronize]; } -int32_t KFTargetFPS(void) { - return kFpsPresets[KFFPSIndex()]; +int32_t KIOUTargetFPS(void) { + return kFpsPresets[KIOUFPSIndex()]; } // --------------------------------------------------------------------------- @@ -94,7 +94,7 @@ int32_t KFTargetFPS(void) { static NSString *const kAnalysisHashIndexKey = @"kiou_forge.analysis_hash_idx"; static NSString *const kAnalysisSkillLevelKey = @"kiou_forge.analysis_skill_level"; -int32_t KFAnalysisDepth(void) { +int32_t KIOUAnalysisDepth(void) { // Match the retail KifuDetailPopupAnalysisPresenter.SearchDepth default (15). // Users raise it for stronger analysis at the cost of longer run time. NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; @@ -102,7 +102,7 @@ int32_t KFAnalysisDepth(void) { return clampInt((int32_t)[defs integerForKey:kAnalysisDepthKey], 1, 36); } -void KFSetAnalysisDepth(int32_t v) { +void KIOUSetAnalysisDepth(int32_t v) { NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; [defs setInteger:clampInt(v, 1, 36) forKey:kAnalysisDepthKey]; [defs synchronize]; @@ -114,31 +114,31 @@ void KFSetAnalysisDepth(int32_t v) { 16, 64, 128, 256, 512, 1024 }; -int32_t KFAnalysisHashIndex(void) { +int32_t KIOUAnalysisHashIndex(void) { NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; if ([defs objectForKey:kAnalysisHashIndexKey] == nil) return 0; // 16 MB return clampInt((int32_t)[defs integerForKey:kAnalysisHashIndexKey], 0, KIOU_ANALYSIS_HASH_PRESET_COUNT - 1); } -void KFSetAnalysisHashIndex(int32_t idx) { +void KIOUSetAnalysisHashIndex(int32_t idx) { NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; [defs setInteger:clampInt(idx, 0, KIOU_ANALYSIS_HASH_PRESET_COUNT - 1) forKey:kAnalysisHashIndexKey]; [defs synchronize]; } -int32_t KFAnalysisHashMB(void) { - return kAnalysisHashPresetsMB[KFAnalysisHashIndex()]; +int32_t KIOUAnalysisHashMB(void) { + return kAnalysisHashPresetsMB[KIOUAnalysisHashIndex()]; } -int32_t KFAnalysisSkillLevel(void) { +int32_t KIOUAnalysisSkillLevel(void) { NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; if ([defs objectForKey:kAnalysisSkillLevelKey] == nil) return 20; return clampInt((int32_t)[defs integerForKey:kAnalysisSkillLevelKey], 1, 20); } -void KFSetAnalysisSkillLevel(int32_t v) { +void KIOUSetAnalysisSkillLevel(int32_t v) { NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; [defs setInteger:clampInt(v, 1, 20) forKey:kAnalysisSkillLevelKey]; [defs synchronize]; @@ -162,7 +162,7 @@ void KFSetAnalysisSkillLevel(int32_t v) { } } -NSString *KFKifuModeLabel(KiouMatchMode m) { +NSString *KIOUKifuModeLabel(KiouMatchMode m) { switch (m) { case KIOU_MMODE_AI_MATCH: return @"AI Match"; case KIOU_MMODE_CPU_STREAM: return @"CPU Stream"; @@ -173,7 +173,7 @@ void KFSetAnalysisSkillLevel(int32_t v) { } } -bool KFKifuModeEnabled(KiouMatchMode m) { +bool KIOUKifuModeEnabled(KiouMatchMode m) { NSString *key = kifuModeKey(m); if (!key) return false; NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; @@ -182,7 +182,7 @@ bool KFKifuModeEnabled(KiouMatchMode m) { return [defs boolForKey:key]; } -void KFSetKifuModeEnabled(KiouMatchMode m, bool enabled) { +void KIOUSetKifuModeEnabled(KiouMatchMode m, bool enabled) { NSString *key = kifuModeKey(m); if (!key) return; NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; diff --git a/Sources/KiouForge/Settings.h b/Sources/KiouForge/Settings.h index 7c05a34..901d288 100644 --- a/Sources/KiouForge/Settings.h +++ b/Sources/KiouForge/Settings.h @@ -10,10 +10,10 @@ // settings table itself lives in Hook_SettingsUI.m and Persistence.m — this // header only declares the gesture installer that opens it. // -// KFGestureInstall() — attach a UIScreenEdgePanGestureRecognizer +// KIOUGestureInstall() — attach a UIScreenEdgePanGestureRecognizer // (right edge) to the key window. Right-edge swipe → present the // existing KEditorSettingsViewController via -// `KFPresentSettings()` (defined in Hook_SettingsUI.m). +// `KIOUPresentSettings()` (defined in Hook_SettingsUI.m). // // Safe to call multiple times (no-op once a right-edge recognizer is // already attached) and safe to call from any thread — the @@ -21,4 +21,4 @@ // and retries until the UIWindow is up. // =========================================================================== -void KFGestureInstall(void); +void KIOUGestureInstall(void); diff --git a/Sources/KiouForge/Settings.m b/Sources/KiouForge/Settings.m index 2600606..60ef9a1 100644 --- a/Sources/KiouForge/Settings.m +++ b/Sources/KiouForge/Settings.m @@ -9,7 +9,7 @@ // // Design: // * The settings table itself is owned by Hook_SettingsUI.m via -// `KFPresentSettings()`. We don't duplicate any of that +// `KIOUPresentSettings()`. We don't duplicate any of that // UI here — we only own the gesture that opens it. // * The recognizer is attached to the key window's gesture-recognizer // list. Touches inside game UI are unaffected: only screen-edge @@ -22,13 +22,13 @@ // stay easy to diff side-by-side. // =========================================================================== -// KFPresentSettings() is implemented in Hook_SettingsUI.m. Same +// KIOUPresentSettings() is implemented in Hook_SettingsUI.m. Same // extern-in-callsite pattern Hook_FriendUnhide.m uses; do not lift this // into Internal.h. -extern void KFPresentSettings(void); +extern void KIOUPresentSettings(void); // --------------------------------------------------------------------------- -// KFKeyWindow — iOS 13+ safe replacement for the deprecated +// KIOUKeyWindow — iOS 13+ safe replacement for the deprecated // [UIApplication sharedApplication].keyWindow. Walks connected // UIWindowScenes and returns the first key window in a foreground-active // scene. Falls back to the first visible window if none is marked key. @@ -38,7 +38,7 @@ // at file scope). Keeping a copy here costs nothing and means this file is // self-contained — Hook_SettingsUI.m doesn't need to expose its helper. // --------------------------------------------------------------------------- -static UIWindow *KFKeyWindow(void) { +static UIWindow *KIOUKeyWindow(void) { for (UIScene *scene in [UIApplication sharedApplication].connectedScenes) { if (![scene isKindOfClass:[UIWindowScene class]]) continue; UIWindowScene *ws = (UIWindowScene *)scene; @@ -52,46 +52,46 @@ } // =========================================================================== -// KFGestureHandler — target for the UIScreenEdgePanGestureRecognizer. +// KIOUGestureHandler — target for the UIScreenEdgePanGestureRecognizer. // Kept as a separate NSObject so its lifetime is independent of any VC. // =========================================================================== -@interface KFGestureHandler : NSObject +@interface KIOUGestureHandler : NSObject - (void)handleEdgePan:(UIScreenEdgePanGestureRecognizer *)gr; @end -@implementation KFGestureHandler +@implementation KIOUGestureHandler - (void)handleEdgePan:(UIScreenEdgePanGestureRecognizer *)gr { // Fire on Began only — we don't need to track the drag. if (gr.state != UIGestureRecognizerStateBegan) return; IPALog(@"[KF] right-edge swipe began -> presenting settings"); - KFPresentSettings(); + KIOUPresentSettings(); } @end // --------------------------------------------------------------------------- -// KFGestureInstall — public entry point called from Tweak.m. +// KIOUGestureInstall — public entry point called from Tweak.m. // // Attaches a UIScreenEdgePanGestureRecognizer (right edge) to the key // window. The handler object is retained statically so it lives for the // app's lifetime without needing an owner. Retries every second until // the window is up; once attached, becomes a no-op. // --------------------------------------------------------------------------- -void KFGestureInstall(void) { +void KIOUGestureInstall(void) { dispatch_async(dispatch_get_main_queue(), ^{ - UIWindow *win = KFKeyWindow(); + UIWindow *win = KIOUKeyWindow(); if (!win) { dispatch_after( dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - KFGestureInstall(); + KIOUGestureInstall(); }); return; } - // Guard: don't install twice (e.g. if KFGestureInstall is somehow + // Guard: don't install twice (e.g. if KIOUGestureInstall is somehow // called a second time after a window recreation). for (UIGestureRecognizer *gr in win.gestureRecognizers) { if ([gr isKindOfClass:[UIScreenEdgePanGestureRecognizer class]]) { @@ -104,9 +104,9 @@ void KFGestureInstall(void) { } } - static KFGestureHandler *sHandler = nil; + static KIOUGestureHandler *sHandler = nil; static dispatch_once_t once; - dispatch_once(&once, ^{ sHandler = [[KFGestureHandler alloc] init]; }); + dispatch_once(&once, ^{ sHandler = [[KIOUGestureHandler alloc] init]; }); UIScreenEdgePanGestureRecognizer *gr = [[UIScreenEdgePanGestureRecognizer alloc] diff --git a/Sources/KiouForge/Tweak.m b/Sources/KiouForge/Tweak.m index e848ea2..94df713 100644 --- a/Sources/KiouForge/Tweak.m +++ b/Sources/KiouForge/Tweak.m @@ -8,11 +8,11 @@ // KiouForge — entry point. // // Distribution shapes: -// JB rootless / jailed (Dobby static): KFInstall*Hook() patches the +// JB rootless / jailed (Dobby static): KIOUInstall*Hook() patches the // UnityFramework at runtime via MSHookFunction / DobbyHook. // Chinlan (make ipa): UnityFramework is statically rewritten so each // site BLs into a __TEXT cave; the dylib publishes hook pointers into -// the __DATA slot table via KFChinlanPublish(). +// the __DATA slot table via KIOUChinlanPublish(). // =========================================================================== static BOOL g_unityHooked = NO; @@ -46,15 +46,15 @@ static void installUnityHooks(uintptr_t unityBase, const char *unityName) { (unsigned long)unityBase, unityName ? unityName : "?"]); #if IPA_CHINLAN - // Publish the slot table and bypass entries before any KFInstall* runs; + // Publish the slot table and bypass entries before any KIOUInstall* runs; // the chinlan branch of each installer reads g_kfHookSlot. - KFChinlanPublish(unityBase); + KIOUChinlanPublish(unityBase); #endif - KFInstallFrameRateHook(unityBase); - KFInstallAnalysisTuneHook(unityBase); - KFInstallKifuObserveHook(unityBase); - KFInstallAccountObserveHook(unityBase); - KFInstallGrpcLoggingHook(unityBase); + KIOUInstallFrameRateHook(unityBase); + KIOUInstallAnalysisTuneHook(unityBase); + KIOUInstallKifuObserveHook(unityBase); + KIOUInstallAccountObserveHook(unityBase); + KIOUInstallGrpcLoggingHook(unityBase); g_unityHooked = YES; IPALog(@"=== KiouForge: all hooks installed ==="); @@ -81,7 +81,7 @@ static void installUnityHooks(uintptr_t unityBase, const char *unityName) { // Settings panel (right-edge swipe). Dispatches to main queue internally // and retries until the key window is available — safe to call here. - KFGestureInstall(); + KIOUGestureInstall(); // Wire UnityFramework hooks the moment UnityFramework is mapped. // _dyld_register_func_for_add_image fires synchronously for every image From 2b5d0361358a65621f3c6551e36bc158cb6668c3 Mon Sep 17 00:00:00 2001 From: tkgstrator Date: Wed, 1 Jul 2026 09:16:25 +0000 Subject: [PATCH 6/9] chore(submodules): bump KIOU-Hook vendor revision Co-Authored-By: Claude Opus 4.8 (1M context) --- vendor/KIOU-Hook | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/KIOU-Hook b/vendor/KIOU-Hook index 85f00e1..be6b307 160000 --- a/vendor/KIOU-Hook +++ b/vendor/KIOU-Hook @@ -1 +1 @@ -Subproject commit 85f00e19039194fac6b34443e60eb3ad9406d17b +Subproject commit be6b3078f7f93ac963b27095b3eeed5b932391a1 From 30c06990482327c6e4ac73922e78f155bf97b8f6 Mon Sep 17 00:00:00 2001 From: tkgstrator Date: Wed, 1 Jul 2026 10:16:52 +0000 Subject: [PATCH 7/9] chore(submodules): bump KIOU-Hook vendor revision Co-Authored-By: Claude Opus 4.8 (1M context) --- vendor/KIOU-Hook | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/KIOU-Hook b/vendor/KIOU-Hook index be6b307..c96a1e6 160000 --- a/vendor/KIOU-Hook +++ b/vendor/KIOU-Hook @@ -1 +1 @@ -Subproject commit be6b3078f7f93ac963b27095b3eeed5b932391a1 +Subproject commit c96a1e6819bdd751125eaa079a817afdbd80943e From 8400e3f2337b3665ccc03091ebd36f2bf38d7e0d Mon Sep 17 00:00:00 2001 From: tkgstrator Date: Wed, 1 Jul 2026 10:21:53 +0000 Subject: [PATCH 8/9] chore(submodules): bump KIOU-Hook vendor revision Co-Authored-By: Claude Opus 4.8 (1M context) --- vendor/KIOU-Hook | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/KIOU-Hook b/vendor/KIOU-Hook index c96a1e6..1f08ca6 160000 --- a/vendor/KIOU-Hook +++ b/vendor/KIOU-Hook @@ -1 +1 @@ -Subproject commit c96a1e6819bdd751125eaa079a817afdbd80943e +Subproject commit 1f08ca62bcca66f37acf590f7f4f06a09feb8cb8 From 12e7e8f8fc9ae0e0c2cfc7c83fd9ea9abbaeef56 Mon Sep 17 00:00:00 2001 From: tkgstrator Date: Wed, 1 Jul 2026 10:29:11 +0000 Subject: [PATCH 9/9] chore(submodules): bump KIOU-Hook vendor revision Co-Authored-By: Claude Opus 4.8 (1M context) --- vendor/KIOU-Hook | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/KIOU-Hook b/vendor/KIOU-Hook index 1f08ca6..3079c5e 160000 --- a/vendor/KIOU-Hook +++ b/vendor/KIOU-Hook @@ -1 +1 @@ -Subproject commit 1f08ca62bcca66f37acf590f7f4f06a09feb8cb8 +Subproject commit 3079c5e5b96867cb81d54b4e92c11cd0ab31673d