diff --git a/CHANGELOG.md b/CHANGELOG.md index d670297..994c049 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,17 @@ All notable changes to KiouForge are documented here. ## [Unreleased] +### Added + +- **KIOU 1.1.0 support** (CFBundleVersion 15). All 17 hook sites re-verified against the 1.1.0 Mach-O; every struct offset the KIF pipeline reads is unchanged from 1.0.2. + ### Changed +- `TARGET_VERSION` now defaults to `1.1.0`; `1.0.1` and `1.0.2` remain buildable by passing it explicitly. The Makefile maps the version to KIOU-Hook's `KIOU_HOOK_TARGET_BUILD` so the dylib and the recipe always agree on which addresses to bake in, and errors out on an unknown version instead of silently falling back. +- The five direct-call RVAs the KIF pipeline uses (`GetUSIText`, `ToSFEN`, `ParseUSI`, `KIFWriteOptions..ctor`, `KIFWriter.Write`) now resolve through the KIOU-Hook catalog instead of being pinned in `Kif/Helpers.m`, so they follow `TARGET_VERSION` like every other site. +- `make deploy` accepts `THEOS_DEVICE_PORT`, needed when the device is reached through an iproxy forward on the host — there port 22 answers as the host rather than the phone. +- FPS override logging only reports transitions. KIOU re-applies its own target every ~5 s, and logging each one buried every other line (198 of 242 lines in one session). +- Bumped the `vendor/KIOU-Hook` submodule to `8ab5868` (per-version RVA headers under `rva/`, `recipes/v1_1_0.py`) and `shared` (Kanade) to develop, which carries the verify_sites overload resolution and the `caves.py` placeholder rows 1.1.0 needs. - `make deploy` now works on TrollStore Lite devices. `TROLLSTORE_HELPER` defaults to empty and the target SSH-discovers the helper binary at deploy time, preferring `TrollStoreLite.app` over `TrollStore.app` and filtering out stale `TrollStorePersistenceHelper.app` leftovers whose entitlements expire between sessions (which manifest as ssh 255 / SIGKILL 137). The IPA is staged in `/var/mobile/Documents/` instead of `/tmp/` — the trollstorehelper sandbox cannot read `/tmp/` and rejects an IPA there with error 166. The staged file is `chown mobile:mobile`-ed after `scp` so the helper can open it. Non-Lite setups keep working unchanged; operators who pinned `TROLLSTORE_HELPER` in their `.env` are unaffected. `REMOTE_STAGING_DIR` is exposed as an override for setups that stage elsewhere. ## [0.2.1] — 2026-06-26 diff --git a/Makefile b/Makefile index e0df669..bbf17a4 100644 --- a/Makefile +++ b/Makefile @@ -20,8 +20,20 @@ TARGET_PROCESS := KIOU TARGET_BUNDLE_ID := com.neconome.shogi # Override on the command line: make ipa TARGET_VERSION=1.0.2 -TARGET_VERSION ?= 1.0.2 +TARGET_VERSION ?= 1.1.0 DECRYPTED_IPA ?= $(CURDIR)/assets/$(TARGET_VERSION)/Kiou-$(TARGET_VERSION).ipa + +# KIOU-Hook selects its per-version RVA header (vendor/KIOU-Hook/rva/) by +# CFBundleVersion, not by marketing version, so map one to the other here. +# Without the -D below the catalog silently falls back to its own default +# and the dylib gets built against another build's addresses. +KIOU_BUILD_1.0.1 := 11 +KIOU_BUILD_1.0.2 := 12 +KIOU_BUILD_1.1.0 := 15 +KIOU_HOOK_TARGET_BUILD := $(KIOU_BUILD_$(TARGET_VERSION)) +ifeq ($(KIOU_HOOK_TARGET_BUILD),) +$(error unknown TARGET_VERSION '$(TARGET_VERSION)'; known: 1.0.1 1.0.2 1.1.0) +endif IPA_RECIPE := recipes.__init__ KIOU_HOOK_DIR := $(CURDIR)/vendor/KIOU-Hook IPA_FRAMEWORK := UnityFramework @@ -70,6 +82,7 @@ endif $(TWEAK_NAME)_CFLAGS := -fobjc-arc -Wno-unused-function \ -D$(BUILD_COMMIT_DEFINE)=\"$(BUILD_COMMIT)\" \ -DKIOU_FORGE_VERSION=\"$(PACKAGE_VERSION)\" \ + -DKIOU_HOOK_TARGET_BUILD=$(KIOU_HOOK_TARGET_BUILD) \ -ISources/Chinlan -I$(TWEAK_SOURCES_DIR) \ -Ivendor/KIOU-Hook ifdef FINAL_RELEASE @@ -198,18 +211,26 @@ ipa:: chinlan # INSTALLED_IPA_BUNDLE_ID — bundle id used to relaunch the app # DEVICE_USER — SSH user (defaults to root) # REMOTE_STAGING_DIR — sandbox-visible dir to scp the IPA into +# THEOS_DEVICE_PORT — SSH port. Needed when the device is reached +# through an iproxy forward on the host +# (THEOS_DEVICE_IP=host.docker.internal), where +# port 22 answers as the host, not the phone. # --------------------------------------------------------------------------- TROLLSTORE_HELPER ?= INSTALLED_IPA_BUNDLE_ID ?= $(TARGET_BUNDLE_ID)$(if $(BUNDLE_ID_SUFFIX),.$(BUNDLE_ID_SUFFIX),) DEVICE_USER ?= root REMOTE_STAGING_DIR ?= /var/mobile/Documents +THEOS_DEVICE_PORT ?= 22 +# scp spells the port -P, ssh spells it -p. +DEVICE_SCP := scp -P $(THEOS_DEVICE_PORT) +DEVICE_SSH := ssh -p $(THEOS_DEVICE_PORT) .PHONY: deploy deploy: ipa @helper='$(TROLLSTORE_HELPER)'; \ if [ -z "$$helper" ]; then \ echo "==> discovering trollstorehelper on $(THEOS_DEVICE_IP)"; \ - helper=$$(ssh $(DEVICE_USER)@$(THEOS_DEVICE_IP) \ + helper=$$($(DEVICE_SSH) $(DEVICE_USER)@$(THEOS_DEVICE_IP) \ "find /var/jb/Applications /var/containers/Bundle/Application \ -maxdepth 4 -type f -name trollstorehelper 2>/dev/null \ | grep -v 'PersistenceHelper' \ @@ -222,15 +243,15 @@ deploy: ipa exit 1; \ fi; \ echo "==> helper: $$helper"; \ - echo "==> scp $(notdir $(IPA_OUT)) -> $(DEVICE_USER)@$(THEOS_DEVICE_IP):$(REMOTE_STAGING_DIR)/"; \ - scp -q $(IPA_OUT) $(DEVICE_USER)@$(THEOS_DEVICE_IP):$(REMOTE_STAGING_DIR)/$(notdir $(IPA_OUT)); \ - ssh $(DEVICE_USER)@$(THEOS_DEVICE_IP) \ + echo "==> scp $(notdir $(IPA_OUT)) -> $(DEVICE_USER)@$(THEOS_DEVICE_IP):$(THEOS_DEVICE_PORT):$(REMOTE_STAGING_DIR)/"; \ + $(DEVICE_SCP) -q $(IPA_OUT) $(DEVICE_USER)@$(THEOS_DEVICE_IP):$(REMOTE_STAGING_DIR)/$(notdir $(IPA_OUT)); \ + $(DEVICE_SSH) $(DEVICE_USER)@$(THEOS_DEVICE_IP) \ "chown mobile:mobile '$(REMOTE_STAGING_DIR)/$(notdir $(IPA_OUT))' 2>/dev/null || true"; \ echo "==> trollstorehelper install force $(REMOTE_STAGING_DIR)/$(notdir $(IPA_OUT))"; \ - ssh $(DEVICE_USER)@$(THEOS_DEVICE_IP) \ + $(DEVICE_SSH) $(DEVICE_USER)@$(THEOS_DEVICE_IP) \ "$$helper install force $(REMOTE_STAGING_DIR)/$(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 \ + @$(DEVICE_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")' diff --git a/README.ja.md b/README.ja.md index 8e719cb..453b891 100644 --- a/README.ja.md +++ b/README.ja.md @@ -12,7 +12,7 @@

version - targets KIOU + targets KIOU platform arch runs @@ -136,21 +136,21 @@ KIOU はオンラインゲームのため頻繁にアップデートされ、古 ### 機能 × バージョン対応表 -| 機能 | 1.0.1 (build 11) | 1.0.2 (build 12) | -|---|:---:|:---:| -| FPS Override | ✓ | ✓ | -| AFK Guard | ✓ | ✓ | -| Analysis Tune | ✓ | ✓ | -| Kifu Autosave | ✓ | ✓ | -| **アカウント切り替え** | — | ✓ | +| 機能 | 1.0.1 (build 11) | 1.0.2 (build 12) | 1.1.0 (build 15) | +|---|:---:|:---:|:---:| +| FPS Override | ✓ | ✓ | ✓ | +| AFK Guard | ✓ | ✓ | ✓ | +| Analysis Tune | ✓ | ✓ | ✓ | +| Kifu Autosave | ✓ | ✓ | ✓ | +| **アカウント切り替え** | — | ✓ | ✓ | -アカウント切り替えは 1.0.2 で追加されたフックサイトが必要なため、1.0.1 では非対応です。Jailed / JB ビルドは RVA がコンパイル時に固定されるため、常に**最新バージョンのみ**を対象とします。Patched IPA は `TARGET_VERSION` でビルド時にレシピを選択します。 +アカウント切り替えは 1.0.2 で追加されたフックサイトが必要なため、1.0.1 では非対応です。アドレスはどの配布形式でもビルド時に `TARGET_VERSION` で決まります。Patched IPA はレシピを、dylib は対応する RVA ヘッダを選択します。 ### プラットフォーム | | | |---|---| -| **最新対応 KIOU** | `1.0.2`(CFBundleVersion 12) | +| **最新対応 KIOU** | `1.1.0`(CFBundleVersion 15) | | **KIOU の最低 iOS バージョン** | 10.0(アプリの `MinimumOSVersion`) | | **KiouForge の最低 iOS バージョン** | 13.0(`UIWindowScene` が必要) | | **動作確認済み** | iOS 15.0 〜 26、arm64 | @@ -187,22 +187,22 @@ TrollStore が使えないデバイス向けです。[Sideloadly](https://sidelo **復号済み** の KIOU IPA が必要です([palera1n](https://palera.in/) + Filza または [TrollDecrypt](https://github.com/donato-fiore/TrollDecrypt) で取得できます)。App Store からダウンロードした IPA は FairPlay で暗号化されているため、そのままでは使用できません。 ```sh -# デフォルト(1.0.2) -mkdir -p assets/1.0.2 -cp ~/Downloads/Kiou-1.0.2.ipa assets/1.0.2/ +# デフォルト(1.1.0) +mkdir -p assets/1.1.0 +cp ~/Downloads/Kiou-1.1.0.ipa assets/1.1.0/ make ipa # -> packages/ipa/KiouForge-patched.ipa # バージョンを指定する場合 -make ipa TARGET_VERSION=1.0.1 +make ipa TARGET_VERSION=1.0.2 ``` フックサイトを編集した後や KIOU のアップデート後にビルドする場合: ```sh # 特定バージョンの dump + IPA でレシピを検証 -PYTHONPATH=shared:. TARGET_VERSION=1.0.2 python3 -m tools.verify_sites \ +PYTHONPATH=shared:vendor/KIOU-Hook TARGET_VERSION=1.1.0 python3 -m tools.verify_sites \ --recipe recipes \ - --index assets/1.0.2/dump.cs.index.json \ - --ipa assets/1.0.2/Kiou-1.0.2.ipa + --index assets/1.1.0/dump.cs.index.json \ + --ipa assets/1.1.0/Kiou-1.1.0.ipa ``` diff --git a/README.md b/README.md index f5f1148..588ed6c 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@

version - targets KIOU + targets KIOU platform arch runs @@ -179,23 +179,23 @@ server-side. The recommended target is always the **latest supported version**. ### Feature × version matrix -| Feature | 1.0.1 (build 11) | 1.0.2 (build 12) | -|---|:---:|:---:| -| FPS Override | ✓ | ✓ | -| AFK Guard | ✓ | ✓ | -| Analysis Tune | ✓ | ✓ | -| Kifu Autosave | ✓ | ✓ | -| **Account Switching** | — | ✓ | +| Feature | 1.0.1 (build 11) | 1.0.2 (build 12) | 1.1.0 (build 15) | +|---|:---:|:---:|:---:| +| FPS Override | ✓ | ✓ | ✓ | +| AFK Guard | ✓ | ✓ | ✓ | +| Analysis Tune | ✓ | ✓ | ✓ | +| Kifu Autosave | ✓ | ✓ | ✓ | +| **Account Switching** | — | ✓ | ✓ | -Account Switching requires hook sites introduced in 1.0.2; the Jailed/JB build -always targets the **latest** version only (RVAs are pinned at compile time). -The Patched IPA build selects the recipe at build time via `TARGET_VERSION`. +Account Switching requires hook sites introduced in 1.0.2. Every build shape +pins its addresses at build time via `TARGET_VERSION`: the Patched IPA picks +the recipe, and the dylib picks the matching RVA header. ### Platform | | | |---|---| -| **Latest supported KIOU** | `1.0.2` (CFBundleVersion 12) | +| **Latest supported KIOU** | `1.1.0` (CFBundleVersion 15) | | **KIOU minimum iOS** | 10.0 (`MinimumOSVersion` in app bundle) | | **KiouForge minimum iOS** | 13.0 (requires `UIWindowScene`) | | **Tested on** | iOS 15.0 – 26, arm64 | @@ -238,23 +238,23 @@ Filza, or [TrollDecrypt](https://github.com/donato-fiore/TrollDecrypt)). The App Store download is FairPlay-encrypted and cannot be patched directly. ```sh -# default (1.0.2) -mkdir -p assets/1.0.2 -cp ~/Downloads/Kiou-1.0.2.ipa assets/1.0.2/ +# default (1.1.0) +mkdir -p assets/1.1.0 +cp ~/Downloads/Kiou-1.1.0.ipa assets/1.1.0/ make ipa # -> packages/ipa/KiouForge-patched.ipa # target a specific version -make ipa TARGET_VERSION=1.0.1 +make ipa TARGET_VERSION=1.0.2 ``` Before building after editing hook sites or after a KIOU update: ```sh # verify current recipe against a specific version's dump + IPA -PYTHONPATH=shared:. TARGET_VERSION=1.0.2 python3 -m tools.verify_sites \ +PYTHONPATH=shared:vendor/KIOU-Hook TARGET_VERSION=1.1.0 python3 -m tools.verify_sites \ --recipe recipes \ - --index assets/1.0.2/dump.cs.index.json \ - --ipa assets/1.0.2/Kiou-1.0.2.ipa + --index assets/1.1.0/dump.cs.index.json \ + --ipa assets/1.1.0/Kiou-1.1.0.ipa ``` diff --git a/Sources/KiouForge/Hook/FrameRate.m b/Sources/KiouForge/Hook/FrameRate.m index 42353de..f4b58eb 100644 --- a/Sources/KiouForge/Hook/FrameRate.m +++ b/Sources/KiouForge/Hook/FrameRate.m @@ -16,7 +16,12 @@ static int32_t pickFPS(int32_t value) { int32_t v = KIOUFeatureEnabled(KIOU_FEATURE_FPS_OVERRIDE) ? KIOUTargetFPS() : value; - if (v != value) { + // KIOU re-applies its own target every ~5 s, so logging every + // override would bury everything else. Only report transitions. + static int32_t lastFrom = -1, lastTo = -1; + if (v != value && (value != lastFrom || v != lastTo)) { + lastFrom = value; + lastTo = v; IPALog([NSString stringWithFormat: @"[FPS] set_targetFrameRate %d -> %d (override)", value, v]); } diff --git a/Sources/KiouForge/Kif/Helpers.m b/Sources/KiouForge/Kif/Helpers.m index af7f607..640d302 100644 --- a/Sources/KiouForge/Kif/Helpers.m +++ b/Sources/KiouForge/Kif/Helpers.m @@ -35,10 +35,10 @@ // KIOUKifTextFromGameController is the bridge into il2cpp — everything else is // pure Foundation. // -// Background: GameController.GetKifuText (RVA 0x5D43D10) returns an in-app -// SUMMARY ("001 ☗ ☗3八飛 … まで、☖後手の勝ち(詰み)"), NOT the standard -// KIF 2.0 that desktop kifu viewers expect. We instead run the canonical -// pipeline KIOU uses internally: +// Background: GameController.GetKifuText returns an in-app SUMMARY +// ("001 ☗ ☗3八飛 … まで、☖後手の勝ち(詰み)"), NOT the standard KIF 2.0 +// that desktop kifu viewers expect. We instead run the canonical pipeline +// KIOU uses internally: // // GetUSIText(self) → "position startpos moves ..." // ↓ @@ -71,18 +71,10 @@ // touched by the fill is ThinkingTimesMicros (per-move clock, queued for v0.4). // =========================================================================== -// --------------------------------------------------------------------------- -// RVAs (KIOU 1.0.1 build 11). Same source of truth as KiouUsiProxy. -// --------------------------------------------------------------------------- -// RVAs pinned to KIOU 1.0.2 (dump.cs verified 2026-07-02). The previous -// values were 1.0.1 leftovers pointing at unrelated methods — calling -// through them at match-end crashed with a SIGSEGV inside GetUSIText -// (LDR from a bogus X10 loaded from *(gameCtrl+0xB0)). -#define RVA_GAMECTRL_GET_USI_TEXT 0x5D49970 // string GameController.GetUSIText(this) -#define RVA_POSITION_TO_SFEN 0x5D49C70 // string Position.ToSFEN(this) -#define RVA_USIPARSER_PARSE_USI 0x5D5CBB0 // static RecordManager USIParser.ParseUSI(string) -#define RVA_KIFWRITEOPTIONS_CTOR 0x5D5925C // void KIFWriteOptions..ctor(this) -#define RVA_KIFWRITER_WRITE 0x5D59264 // static string KIFWriter.Write(RecordManager, KIFWriteOptions) +// The five methods this pipeline calls are direct-ABI catalog rows in +// KIOU-Hook (hook_id = -1 — resolved and called, never hooked), so their +// addresses come from KIOUHookSiteAddr and follow KIOU_HOOK_TARGET_BUILD +// instead of being pinned to one app version here. // KIFWriteOptions instance size needed for the raw-buffer trick. See the // KIFOPTS_OFF_* constants in Internal.h for the field map. Last field @@ -159,29 +151,41 @@ static KIFWriter_Write_t g_KIFWriter_Write = NULL; static Position_ToSFEN_t g_PositionToSFEN = NULL; +// KIOUHookSiteAddr returns 0 for a name the catalog doesn't carry; return +// NULL rather than a pointer to unityBase itself so callers' existing null +// checks catch it. +static void *resolveSite(const char *name) { + uintptr_t addr = KIOUHookSiteAddr(name, g_unityBase); + if (addr == 0) { + IPALog([NSString stringWithFormat:@"[KIF] site unresolved: %s", name]); + return NULL; + } + return (void *)addr; +} + // Resolve the il2cpp NativeFunction pointers we use. Idempotent and cheap; // safe to call once per export call. static void resolveIl2cppFunctions(void) { if (g_unityBase == 0) return; if (!g_GetUSIText) { g_GetUSIText = (GameCtrl_GetUSIText_t) - (void *)(g_unityBase + RVA_GAMECTRL_GET_USI_TEXT); + resolveSite(KIOU_HOOK_NAME_GAMECTRL_GET_USI_TEXT); } if (!g_ParseUSI) { g_ParseUSI = (USIParser_ParseUSI_t) - (void *)(g_unityBase + RVA_USIPARSER_PARSE_USI); + resolveSite(KIOU_HOOK_NAME_USIPARSER_PARSE_USI); } if (!g_KIFOpts_Ctor) { g_KIFOpts_Ctor = (KIFWriteOptions_Ctor_t) - (void *)(g_unityBase + RVA_KIFWRITEOPTIONS_CTOR); + resolveSite(KIOU_HOOK_NAME_KIFWRITEOPTIONS_CTOR); } if (!g_KIFWriter_Write) { g_KIFWriter_Write = (KIFWriter_Write_t) - (void *)(g_unityBase + RVA_KIFWRITER_WRITE); + resolveSite(KIOU_HOOK_NAME_KIFWRITER_WRITE); } if (!g_PositionToSFEN) { g_PositionToSFEN = (Position_ToSFEN_t) - (void *)(g_unityBase + RVA_POSITION_TO_SFEN); + resolveSite(KIOU_HOOK_NAME_POSITION_TO_SFEN); } } diff --git a/control b/control index e1ca59c..091f645 100644 --- a/control +++ b/control @@ -1,6 +1,6 @@ Package: work.tkgstrator.kiouforge Name: KiouForge -Version: 0.2.1 +Version: 0.2.2 Architecture: iphoneos-arm64 Description: KIOU (Shogi) local quality-of-life tool. Extends the frame-rate preset beyond 30/60, suppresses false AFK warnings during long-think sessions, and lets you tune the depth and hash used by the on-device post-game kifu analysis engine. Runs entirely client-side and never modifies any server-stored data. For authorized testing only. Maintainer: NEVER KNOWS BEST diff --git a/docs/plans/on-device-smoke.md b/docs/plans/on-device-smoke.md index 8355a4d..36bfdc1 100644 --- a/docs/plans/on-device-smoke.md +++ b/docs/plans/on-device-smoke.md @@ -78,9 +78,10 @@ practice. Broader flows can layer on the same harness later. frida-il2cpp-bridge`, or `pip install frida-tools` if we go Python). The design below is bun-based to match KiouForge's existing tool chain in `shared/tools/`. -- **`assets/1.0.2/dump.cs.index.json`** (already checked in) — Frida - script reads class + method names from it so RVAs never appear - literally in the script (they change per app version). +- **`assets/$TARGET_VERSION/dump.cs.index.json`** (already checked in + for 1.0.1 / 1.0.2 / 1.1.0) — Frida script reads class + method names + from it so RVAs never appear literally in the script (they change per + app version). ## Architecture @@ -267,7 +268,7 @@ scripts/ case explicitly; user-visible failures still map to "SSH says frida-server crashed" rather than false test failures. - **Presenter signatures change per app version.** The script pins - to 1.0.2 today; when 1.0.3 lands, presenter method names or + to 1.1.0 today; when the next build lands, presenter method names or signatures may drift. Mitigation: keep the mapping (presenter, method, args) in a small YAML config file, loaded per `TARGET_VERSION`. diff --git a/shared b/shared index c29a372..b670411 160000 --- a/shared +++ b/shared @@ -1 +1 @@ -Subproject commit c29a372c7cd1d447c1f12a8b580986aba327e9ef +Subproject commit b6704113065f4ccef308926d87895c49f5691187 diff --git a/vendor/KIOU-Hook b/vendor/KIOU-Hook index 36a3ad1..8ab5868 160000 --- a/vendor/KIOU-Hook +++ b/vendor/KIOU-Hook @@ -1 +1 @@ -Subproject commit 36a3ad15d5f67cdcc3b68e7374049771c8a960a7 +Subproject commit 8ab586811f017520fe185631bc238c551d711adc