Auto-save every KIOU match as a standard KIF 2.0 file —
straight to the Files app, ready for AirDrop or hand-off to PiyoShogi,
Shogi Browser Q, Kifu for Windows, and the rest.
No PC, no extra tooling — the iPhone does it all.
Kiou Kif Exporter is the simplest possible kifu pipeline for KIOU: the
moment a match ends, the tweak calls the app's own internal KIF writer and
drops the resulting .kif file into the app's Documents/KiouKifExporter/
folder. Files.app picks it up, AirDrop ships it to the desktop, and any
kifu viewer that understands the standard KIF 2.0 format can replay it
without one byte of pre-processing.
There is no host-side bridge, no WebSocket server, no proxy. The entire flow lives in one ~110 KB dylib that hooks five lifecycle methods and writes a single file per match.
Kiou Kif Exporter is strictly read-then-write. During a match the
tweak's hooks observe IMatchMode.InitializeAsync and
IMatchMode.OnPlayerMoveAsync to keep a live GameController* pointer
in hand; when IMatchMode.OnMatchEndAsync fires, the tweak asks KIOU's
own KIFWriter.Write for the finished kifu and writes the resulting
string to disk. That's all.
The tweak never:
- writes back into
GameController,RecordManager, or any other in-memory game state, - alters which side wins, the move list, the result reason, or the on-screen replay,
- opens a socket, talks to the KIOU backend, or sits on the network path in any form.
The only piece of state Kiou Kif Exporter creates is a single .kif
file per match, inside the app's own sandbox at
Documents/KiouKifExporter/. Uninstalling the dylib returns KIOU to a
fully vanilla state and leaves your existing .kif files alone until
you delete them yourself.
For every match you play — VsAI, CPU Stream, Online PvP, Local PvP, or even Record Replay — a file lands at:
<KIOU sandbox>/Documents/KiouKifExporter/{ISO8601_UTC}_{mode}_{startpos}.kif
Example file name:
20260615T004046_CPUStreamMode_startpos.kif
Where:
| Segment | Meaning |
|---|---|
ISO8601_UTC |
UTC timestamp, yyyyMMdd'T'HHmmss — filename-safe, sorts lexicographically. |
mode |
One of AIMatchMode, CPUStreamMode, OnlinePvPMode, LocalPvPMode, RecordReplayMode. |
startpos |
startpos for a standard initial position, otherwise sfen-<8 hex> (SHA-1 prefix of the starting SFEN — handicaps, custom positions, etc). |
The file itself is the canonical KIF 2.0 format produced by KIOU's own
internal KIFWriter.Write. Concretely:
# ---- KIF形式 ----
手合割:平手
手数----指手---------消費時間--
1 2六歩(27)
2 2四歩(23)
3 2五歩(26)
...
19 同 竜(21)
20 7二玉(71)
21 6二銀打
Every desktop and iOS kifu viewer that reads .kif reads this — verified
against PiyoShogi (iOS) and Kifu for Windows on a standard install. No
re-encoding, no charset conversion: UTF-8 in, UTF-8 out.
KIFWriteOptions is populated at match end with metadata pulled from
the live MatchConfig, GameStateStore and GameController. Every
desktop and iOS viewer that reads .kif honors these rows.
| Header | Source |
|---|---|
先手:<name> / 後手:<name> |
GameStateStore._blackPlayerInfo / _whitePlayerInfo (ReactiveProperty<PlayerInfo> at +0x50 / +0x58 → currentValue at +0x20 → Name), with a fallback to `MatchConfig.<Black |
開始日時:YYYY/MM/DD HH:MM:SS |
Wall-clock NSDate captured at InitializeAsync, packed into a Nullable<DateTime> tick value at the match-end fill. |
棋戦:<title> |
Synthesized as "{mode} @ {iso8601}" (e.g. OnlinePvPMode @ 20260615T193557) via il2cpp_string_new. |
持ち時間:<label> |
kif_build_time_rule_label from MatchConfig.TimeControl (e.g. 1500秒+秒読み60秒). |
まで○○手で<reason> ending label |
kif_winreason_label(GameController.<Reason>k__BackingField) mapped to 投了 / 詰み / 時間切れ / 千日手 etc. |
The one slot still intentionally NULL is per-move consumption times
(KIFWriteOptions.ThinkingTimesMicros). The KIF body is still valid
without it; only the (M:SS/HH:MM:SS) annotation is absent. This is
queued for v0.4 — see Roadmap.
Full implementation map — including each setter RVA, the field offsets,
and the il2cpp string / List bridging plan — lives in
docs/plans/kiou_kif_exporter.md
in the repository root.
flowchart TD
init["IMatchMode.InitializeAsync"]
opm["IMatchMode.OnPlayerMoveAsync"]
endHook["IMatchMode.OnMatchEndAsync"]
cache(["g_gameCtrlCache · GameController*"])
init -- "latch from adapter arg" --> cache
opm -- "refresh (backup latch)" --> cache
endHook --> writer["kif_writer_emit_for_match_end()"]
cache --> writer
writer --> usiText["GameController.GetUSIText"]
usiText --> usiStr(["position startpos moves …"])
usiStr --> parse["USIParser.ParseUSI(string)"]
parse --> record(["RecordManager*"])
record --> kifwrite["KIFWriter.Write(record, opts)"]
opts(["KIFWriteOptions* · 0x60 NSMutableData"]) --> fill["kif_fill_write_options"]
fill --> kifwrite
kifwrite --> kif(["KIF 2.0 string"])
kif --> file[("<sandbox>/Documents/KiouKifExporter/<br/>{ISO8601}_{mode}_{startpos}.kif")]
file --> files["Files.app"]
files --> sinks["AirDrop · PiyoShogi · Shogi Browser Q · iCloud Drive"]
Five concrete IMatchMode implementations are hooked, three lifecycle
methods each — fifteen MSHookFunction / DobbyHook installs in total:
| Hook | Purpose |
|---|---|
InitializeAsync |
Latch GameController from the ShogiGameAdapter argument. |
OnPlayerMoveAsync |
Backup latch in case InitializeAsync handed us a NULL adapter. |
OnMatchEndAsync |
Run the KIF pipeline and write the file — before chaining to the original, while the GameController is still alive. |
The write is synchronous and blocks the OnMatchEndAsync handler for the
duration of a single KIFWriter.Write call (typically sub-millisecond).
After the file lands, the original OnMatchEndAsync runs as usual and
KIOU teardown proceeds.
Pick the row that matches how your device is signed.
Easiest path. The dylib ships as a regular .deb and is loaded by
MobileSubstrate / ElleKit on next launch.
- Download the latest
work.tkgstrator.kioukifexporter_<ver>_iphoneos-arm64.debfrom the Releases page. - Open the
.debin Sileo (or Zebra / Cydia) and install. - Respring or relaunch KIOU.
- To get the saved
.kiffiles into Files.app, follow Files-app exposure on jailbreak once. You only need to do this the first time the tweak is installed; theInfo.plistchange survives KIOU updates as long as the bundle UUID doesn't change.
Filza users can already see .kif files at
/var/mobile/Containers/Data/Application/<UUID>/Documents/KiouKifExporter/
even without that Info.plist tweak.
Single bundle — patch the IPA's Info.plist first so Files.app sees the
sandbox, then inject the dylib and install. TrollStore's permanent
signature covers any modification you make to the bundle on disk, so
the resulting install is stable.
- Extract a decrypted KIOU IPA to a working directory.
- Open
Payload/KIOU.app/Info.plistand add (or, in Plist Editor, tick):<key>UIFileSharingEnabled</key> <true/> <key>LSSupportsOpeningDocumentsInPlace</key> <true/>
- Copy
KiouKifExporter.dylibintoPayload/KIOU.app/Frameworks/and inject it withinsert_dyliboroptool:insert_dylib --weak --inplace --no-strip-codesig \ '@executable_path/Frameworks/KiouKifExporter.dylib' \ Payload/KIOU.app/KIOU - Re-zip as
KIOU-KifExporter.ipaand open it from Files.app on the device; TrollStore picks it up. - Launch KIOU. The
.kiffolder appears in Files.app under On My iPhone -> KIOU -> KiouKifExporter once the first match finishes.
Self-signed certificates expire (7 days for a free Apple ID, 1 year for a paid Developer Program account). Each refresh re-signs the same IPA, so the inject + plist tweak only has to happen once.
- Open Sideloadly and drop a decrypted KIOU
.ipaonto it. - Switch to the Advanced pane.
- Tick Enable File Sharing — Sideloadly injects
UIFileSharingEnabledandLSSupportsOpeningDocumentsInPlaceinto theInfo.plistfor you. - Under Inject dylibs, click Add and pick the
KiouKifExporter.dylibyou downloaded from the Releases page (themake jailedartifact). - Sign with your Apple ID and install.
AltStore is identical: drop the IPA into AltStore, then before signing go into Settings -> Advanced and add the dylib + tick file sharing.
Note (iOS 18): iOS 18's Code Signing Monitor (CSM) kills any inline hook before the dylib's constructor runs, so the runtime-hook variants above (MobileSubstrate, Dobby-static via Sideloadly) stop working on non-jailbroken iOS 18 devices. Use the statically-patched IPA path below instead.
For non-jailbroken iOS 18 — and as a generally safer option on any
iOS — Kiou Kif Exporter publishes hook function pointers into a
reserved __DATA slot of a statically-patched UnityFramework, so
every IMatchMode.OnMatchEndAsync calls into the dylib without
needing any runtime hook engine. The patched IPA is assembled on
your own host from a decrypted KIOU .ipa you supply — the
project never redistributes a re-bundled KIOU binary.
- Download
work.tkgstrator.kioukifexporter_<ver>-1_iphoneos-arm64-binpatch.dylibfrom the Releases page. This is the binpatch flavor of the dylib (Dobby-static, slot-publisher constructor — not the runtime-hook-jailed.dylib). - On your host, drop a decrypted KIOU
.ipaatassets/Kiou-1.0.1.ipa(or passDECRYPTED_IPA=explicitly) and build the patched IPA — see Build > Patched IPA. This rewires UnityFramework, adds anLC_LOAD_DYLIBfor the dylib, and re-zips intopackages/ipa/KiouKifExporter-binpatch.ipa. - Install the resulting IPA through your usual non-JB channel — TrollStore, Sideloadly, AltStore, or an Apple Developer Program signing flow.
- Launch KIOU. The KIF folder shows up in Files.app under On My iPhone -> KIOU -> KiouKifExporter after the first match.
The patched IPA already has UIFileSharingEnabled and
LSSupportsOpeningDocumentsInPlace enabled, so no further Info.plist
edits are needed.
The destination path uses NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, ...),
which resolves to the running app's sandbox Documents directory. For
Files.app to expose it, the KIOU bundle needs:
<key>UIFileSharingEnabled</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>These keys are absent from the retail KIOU Info.plist, so on a stock
install Files.app will not show a KIOU section even though the dylib is
faithfully writing .kif files. There are three ways to fix that:
| Distribution | How to enable |
|---|---|
| Jailbreak (rootless) | Edit Info.plist in-place (see Files-app exposure on jailbreak below). |
| TrollStore | Edit the Info.plist inside your locally-extracted .ipa before installing, then TrollStore accepts the modified bundle outright (CoreTrust exploit covers the Info.plist hash mismatch). |
| Sideloadly / AltStore / Apple Developer Program | Check "Enable File Sharing" in Sideloadly when sideloading; it injects both keys automatically. |
For development on a jailbroken device, edit the Info.plist directly.
Do not run ldid -S against the KIOU Mach-O — it will strip the
existing code-signature and AMFI will refuse to launch the app.
# 1. Backup
APP=/var/containers/Bundle/Application/<UUID>/KIOU.app
cp -p $APP/Info.plist $APP/Info.plist.bak
# 2. Patch (on host, in Python)
python3 - <<'PY'
import plistlib
with open('Info.plist','rb') as f:
pl = plistlib.load(f)
pl['UIFileSharingEnabled'] = True
pl['LSSupportsOpeningDocumentsInPlace'] = True
with open('Info.plist','wb') as f:
plistlib.dump(pl, f, fmt=plistlib.FMT_XML)
PY
# 3. scp back, then re-register and relaunch
scp Info.plist root@<device>:$APP/Info.plist
ssh root@<device> "uicache -p $APP && open com.neconome.shogi"Most jailbreak AMFI bypasses tolerate the _CodeSignature/CodeResources
mismatch the edit introduces — there is no need to re-sign anything.
This build targets:
| KIOU app version | 1.0.1 (CFBundleVersion 11) |
| iOS | 15.0 – 17.x for the Substrate / Dobby runtime-hook paths, arm64. iOS 18.x is supported via the statically-patched IPA path only — iOS 18's CSM kills runtime inline hooks before the dylib's constructor can run. |
All hooks are pinned to RVAs from this exact KIOU build's UnityFramework
binary. After a KIOU update the RVAs will drift and the tweak will silently
no-op (or, worst case, crash on a method whose signature changed). Don't
install this dylib against a KIOU version other than the one above without
re-deriving every RVA first.
- Theos with the standard iOS toolchain installed
(
$THEOSset). Kiou Kif Exporter is pure Objective-C — no Orion, no Swift runtime. - iOS 15.0–18.x, arm64. Runtime-hook paths (
make package/make jailed) target iOS 15–17; the patched-IPA path (make ipa) covers iOS 18. - For the sideload / patched-IPA paths: a decrypted copy of the KIOU
.ipa. The patched-IPA build also needs Python 3.10+ on the host (the recipe driver underrecipes/andshared/tools/).
Produces a .deb that installs to
/var/jb/Library/MobileSubstrate/DynamicLibraries/.
make package
# install over SSH (password: alpine on first run, then key-based)
make package install THEOS_DEVICE_IP=<device-ip>Produces a bare dylib for injection via Sideloadly / AltStore / Apple Developer Program. The dylib statically links Dobby so it has zero hook- engine dependency at install time.
make jailed
# -> packages/jailed/KiouKifExporter.dylibFor non-jailbroken iOS 18 (and as a more robust option on any iOS),
build a statically-patched IPA that publishes hook function pointers
into a reserved __DATA slot. No runtime hook engine is involved, so
iOS 18's CSM has nothing to flag.
make ipa DECRYPTED_IPA=/path/to/decrypted-kiou.ipa
# -> packages/ipa/KiouKifExporter-binpatch.ipaWhat make ipa does:
- Builds
packages/binpatch/KiouKifExporter.dylib(same link shape asmake jailedbut with-DIPA_BINPATCH=1so the constructor writes hook function pointers into a reserved__DATAslot instead of trying to inline-rewrite__TEXT). - Runs
shared/tools/build_patched_ipa.shdriven by the recipe atrecipes/kioukifexporter.py— adds anLC_LOAD_DYLIBfor the dylib, reserves the slot, and rewrites the prologue of everyIMatchMode.OnMatchEndAsyncto branch into a cave that calls the hook through the slot. - Patches the bundled
Info.plistso Files.app exposes the sandbox automatically, then re-zips into a TrollStore / Sideloadly / AltStore / Developer-Program-ready IPA.
This pipeline never redistributes the decrypted KIOU .ipa; you supply
your own via DECRYPTED_IPA=.
The make jailed target also prints an otool -L of the resulting
binary; it must not mention libsubstrate or libdobby.
Sideloadly install:
- Build with
make jailed. - In Sideloadly, load the decrypted KIOU
.ipa. - Add
packages/jailed/KiouKifExporter.dylibunder Inject dylib. - Tick Enable File Sharing so Files.app picks the sandbox up.
- Sign with your Apple ID / certificate and install.
The dylib writes its own diagnostic log into the KIOU sandbox:
<KIOU sandbox>/tmp/kioukifexporter.log
— which translates to /var/mobile/Containers/Data/Application/<UUID>/tmp/kioukifexporter.log
on a jailbroken device. Tail it over SSH to watch matches resolve in
real time:
ssh root@<device-ip> 'tail -F /var/mobile/Containers/Data/Application/*/tmp/kioukifexporter.log'Each match produces a tidy run of [MMODE] lifecycle lines and a final
[KIF] wrote <N> bytes -> ... confirmation.
| Phase | Scope |
|---|---|
| v0.1 | All 15 IMatchMode hooks installed, KIF pipeline live, file naming, sandbox output. KIFWriteOptions shipped all-null. |
| v0.2 | BlackPlayerName / WhitePlayerName / StartDateTime / MatchTitle / TimeRuleLabel / EndingLabel filled at match end. Player names come from GameStateStore._blackPlayerInfo / _whitePlayerInfo (the same ReactiveProperty the on-screen UI reads), with a MatchConfig fallback for AI / local / replay modes. |
| v0.3 (current) | Statically-patched IPA pipeline (make ipa) — bakes the same hook chain into a patched UnityFramework and reserves a __DATA slot, so non-jailbroken iOS 18 installs (where CSM kills runtime inline hooks) keep working without Dobby. The runtime-hook builds (make package / make jailed) keep covering iOS 15–17. |
| v0.4 | Per-move consumption times via mach_absolute_time() accumulation + List<long>..ctor() / Add(long) to build KIFWriteOptions.ThinkingTimesMicros — restores the (M:SS/HH:MM:SS) annotation on each move. |
| v0.5 | In-app settings sheet — a floating button opens a UIViewController overlay (same window-overlay pattern as KiouEditor) where the user can toggle auto-save per match mode (AIMatchMode, CPUStreamMode, OnlinePvPMode, LocalPvPMode, RecordReplayMode). Selections are persisted in NSUserDefaults; OnMatchEndAsync checks the flag and returns early if the mode is disabled. |
Kiou Kif Exporter shares its il2cpp helpers and logging plumbing with two sister projects you can install side-by-side. All three can coexist in the same KIOU process:
- Kiou Editor — the client-side customization suite (item unlock, premium gating, engine tuning, voice unlock, etc).
- Kiou Engine Bridge — bridges KIOU to a USI shogi engine (YaneuraOu and friends) so you can drive a desktop-grade analysis engine against the live board straight from the device.
Released under the MIT License — see the LICENSE file for the
full text.
Intended for authorized penetration testing and personal research. The
repository ships no proprietary KIOU assets and does not distribute the
IPA — sourcing a decrypted copy of KIOU for the sideload path is the
reader's responsibility. Treat this dylib as a research-grade observation
tool; don't redistribute the resulting .kif files in ways the rights
holders would object to.