blog / ios-re
pyzule-rw vs xkvm-ios-injector: the pure-Go IPA tweak injector cyan asked for
Go read the pyzule-rw README. Near the top, the author of the current mainstream IPA tweak-injection toolchain writes, verbatim:
"wouldn't a go rewrite be really cool? (or in rust or something, adding features to this makes me realize PYTHONM FUCKING SUCKS SOMEONE PLEASE REWRITE IN LIKE ANY COMPILED AND STATICALLY TYPED LANGUAGE PLEASE!!!!!!!"
xkvm-ios-injector is that rewrite. Pure Go, one static binary, no Python environment, no pipx, no venv. This post compares the two honestly — what cyan does, what xkvm does, and when each one wins.
The lineage, in one paragraph
Azule started the "give me an IPA and a tweak, get back an injected IPA" genre. pyzule cloned it in Python 3, then pyzule-rw / cyan rewrote it with shareable .cyan config files and a much larger feature surface. Everything in that bloodline is Python — which is exactly the pain the author is screaming about above.
What cyan does well
- Injects
.deb,.dylib, framework, bundle, and.appexfiles into IPAs - Shareable
.cyanconfigs that capture every option - Automatic dependency fixing — swaps in ElleKit for CydiaSubstrate, handles Cephei and Orion
- A broad set of convenience flags: change app name/version/bundle id/min-OS, merge plists, add entitlements, thin binaries to arm64, fakesign
- Runs on Linux, macOS, WSL, and jailbroken iOS
The cost: Python 3.9+ via pipx, plus lief if you want dylib injection on aarch64 Linux, plus a Python environment to babysit on every machine and every CI runner.
What xkvm does
- Injects dylibs, debs, frameworks, and bundles with correct load commands (
@rpath,@executable_path) and re-signing - Extracts injected tweaks back out of an app — recording where each lived so re-injection is automatic
- Converts between the three jailbreak layouts (rootful / rootless / roothide) and between
.deband.dylibforms — ports of the ecosystem's own converters - Fetches tweaks from Cydia repos by bundle id (Canister / MobileAPT) with dependency recursion
- Reads and writes the same
.cyanconfig language: generate withcgen, validate withcyan-check, apply with-z - Deterministic builds: same input, same output, zip-slip-safe extraction, and pure-Go Apple-format code signatures
No Python, no pipx, no lief. One static binary — the behavior is identical on your laptop and in CI. Go 1.26, MIT licensed, macOS primary with a Linux-tested core.
Honest tradeoffs
| pyzule-rw / cyan | xkvm-ios-injector | |
|---|---|---|
| Runtime | Python 3.9+ (pipx) | None — static Go binary |
| Config format | .cyan | .cyan (cgen / cyan-check / -z) |
| Injection formats | deb, dylib, framework, bundle, appex | dylib, deb, framework, bundle |
| Extraction | — | Yes, location-aware |
| Jailbreak layout conversion | — | rootful ↔ rootless ↔ roothide |
| Convenience flags (icon, plist merge, thinning…) | Broad | Focused core (growing) |
| CI-friendliness | Python env per runner | Single binary, reproducible |
Fair warning where cyan still leads: it ships convenience features xkvm doesn't list yet — app icon swaps, Info.plist merges, arm64 thinning. If you need the kitchen sink on a one-off tweak, cyan is the mature choice. If you need repeatable, scriptable, byte-faithful injection in an environment you don't want to babysit — that's the Go slot, and it was empty until now.
Which should you pick?
Pick cyan when you want the broadest flag surface on a manual tweak and already live in a Python ecosystem. Pick xkvm when you're building a pipeline: CI jobs, release automation, deterministic outputs, "the same machine as last week, every week." And if you've ever typed pipx install on a fresh box at 1am and watched it fight a Python version — you already know which one you want.