Caution
This tool runs arbitrary scripts. Do not use this if you don't know what you are doing. Do not run scripts you do not trust.
patch-tools is a CLI for working with APKs (bundles too) through Morphe. You load packages into a
daemon, then search and inspect their code, generate fingerprints, and run Kotlin scripts that patch
them.
It's a Rust CLI tool with a bundled Kotlin scripting engine wrapping morphe-patcher (think jadx-revanced in a CLI but applying patches too).
- A JVM 17+ (JDK or JRE). The engine runs on an embedded JVM through JNI, so one must be present
at runtime — set
JAVA_HOMEif it isn't auto-discovered. Tune the JVM withMORPHE_JAVA_OPTS(default-Xmx4g). adbon yourPATH, only if you userun --install/--device.
Grab a prebuilt binary from the Releases page:
patch-tools-linux-x86_64patch-tools-macos-aarch64patch-tools-windows-x86_64.exe
Or build from source (just + a Rust toolchain + JDK 17):
just install # builds the engine JAR, then `cargo install --path patch-tools`The core idea: you write a Kotlin script (main.kts), and patch-tools run evaluates it against
the APKs you've loaded into the daemon. The script defines fingerprints and patches; the engine
runs them through Morphe and reports the results (or writes out a patched APK).
Start in an empty working directory and scaffold the script files:
patch-tools scaffoldThen read the generated AGENTS.md, even if you are a human.
It is the practical guide for how to write and iterate on main.kts.
The common loop is:
patch-tools daemon start # start the long-lived background process
patch-tools load path/to/app.apk # load an APK into it (stays loaded across runs)
patch-tools run main.kts # evaluate the script; iterate on main.kts and re-runThe daemon keeps running (one per working directory) until you stop it with patch-tools daemon stop — it won't exit on its own. Each one holds an APK and a JVM in memory (a few GB; see
MORPHE_JAVA_OPTS), so it's on you to close them when done. If a daemon is wedged, use your OS
process tools to kill it. On Unix-like systems:
pkill -f 'patch-tools internal-daemon'On Windows PowerShell:
Stop-Process -Name patch-tools -ForceYou iterate by editing main.kts and re-running patch-tools run main.kts, inspecting the diff
each time, until the patch does what you want. Once you're happy, install it onto a connected
device:
patch-tools run main.kts --installNote
--install requires exactly one connected adb device unless you pass --device <serial>.
It queries the Android current user and installs the patched APK for that user only, not for all
users on the device.
The engine processes whatever your script's final expression evaluates to. End the file with
MyFingerprint to process one item, or listOf(MyFingerprint, myPatch) to process several;
null/Unit means nothing to do. Patches (BytecodePatch, ResourcePatch, RawResourcePatch)
are applied, Fingerprints are matched, and anything else is reported via toString().
A minimal main.kts that finds a method and forces it to return true:
import app.morphe.patcher.Fingerprint
import app.morphe.patcher.extensions.InstructionExtensions.addInstructions
import app.morphe.patcher.literal
import app.morphe.patcher.patch.bytecodePatch
object PremiumCheckFingerprint : Fingerprint(
filters = listOf(literal(0)),
)
val forcePremiumPatch = bytecodePatch("Force premium") {
execute {
PremiumCheckFingerprint.methodOrNull?.addInstructions(
0,
"""
const/4 v0, 0x1
return v0
""".trimIndent(),
)
}
}
// Final expression: the fingerprint is matched, the patch is applied.
listOf(PremiumCheckFingerprint, forcePremiumPatch)print and println are shadowed inside scripts: instead of going to the console, their output is
captured by the engine and shown in the run results, associated with the item that produced it.
Rather than hand-writing fingerprints, use patch-tools fingerprint <method_id>: it analyzes a
method you've located in a loaded APK and emits a ready-to-paste Fingerprint declaration that
matches it. See the scaffolded AGENTS.md for the full workflow.
patch-tools --help and patch-tools <command> --help are authoritative.
Global option:
--format <markdown|human>: set the output format (defaults tomarkdown)
Commands:
daemon start [--apk <path>...]: start the daemon and optionally preload APKsdaemon stop: stop the daemondaemon status: show daemon statusload <apk_path>: load an APK, APKM, or XAPKunload [apk]: unload a package by package name, package/version, or internal ID; omit when exactly one APK is loadedrun <script_path> [--save[=PATH]] [--install] [--device <serial>]: run a.ktsscript; bare--savewrites patched APKs into the current directory using the engine's filename,--save=foo.apkrenames the output (single APK only),--save=dir/writes into that directory,--installinstalls them withadbscaffold: create amain.ktsandAGENTS.mdin the current directoryfingerprint [apk] <method_id> [-n, --limit <count>]: generate method fingerprints; omitapkwhen exactly one APK is loadedclass-fingerprint [apk] <class_id> [-n, --limit <count>]: generate class fingerprints; omitapkwhen exactly one APK is loadedcommon-fingerprint <apk> <method_id> <apk> <method_id>... [-n, --limit <count>]: generate fingerprints shared by equivalent methods across APKssearch <query...> [-n, --limit <count>]: fuzzy search methods across loaded APKs by namemap <old_apk> <method_id> <new_apk> [-n, --limit <count>]: rank methods in another loaded APK by similarity to a source methodsmali [apk] <method_id>: print a method's smali code; omitapkwhen exactly one APK is loadedcompletion <shell>: generate shell completions
Warning
- A lot (like a lot) of the code is AI-generated, this was originally a complex KMP app that blew out of proportion so I scaled it down to match what I actually wanted with the help of AI.
- This is
AI firstin the sense that the main user I targeted for this tool is AI agents, it works good for humans though. - The CLI weight is mostly the embedded kotlin compiler, nothing can be done about that.
- Combined with the setup in https://github.com/valonsodev/patch-explore I've had really good results having agents patch apps completely autonomously in 2 steps.
- Maintaining this is definitely not a priority for me, I made this to scratch my own itch and share it with whoever might find it useful. Feel free to PR, fork, or do whatever you want with the code.
- Do not expect active maintenance or semantic versioning. This is a dev tool, and releases are tied to Morphe library releases.
- Some Smali and XML diffs will probably do some wonky things.
This project is licensed under GPL-3.0-only. See LICENSE for the full terms and NOTICE for third-party notices.
