Writing patches
Finding code
App code is renamed with every release, so a patch never refers to a method by its name. It describes the method instead: the text it uses, what it returns, what it calls. That description is a target. Reseam searches the app for the one method that fits.
Declare targets as top-level values. They are searched for the first time a patch uses them, and the result is reused for the rest of that patch.
Methods
val isAd = method("isAd") {
strings("ad_impression")
returns(Type.Boolean)
}
The constraints you will use most:
| Constraint | Matches methods that |
|---|---|
strings("a", "b") |
load each of these exact strings |
stringsStartingWith("reel/") |
load a string starting with this |
literals(42) |
use each of these number constants |
returns(Type.Boolean) |
return this type |
params(Type.String, Type.Int) |
take exactly these parameters |
hasParam(type), paramCount(n) |
take this parameter, or this many |
inClass(classTarget) |
are declared in this class |
calls(target), calls { name("create") } |
call this method |
calledBy(target) |
are called by this method |
flags(AccessFlags.STATIC) |
have these access flags |
opcodeSequence(...) |
contain these instructions back to back |
custom { instructionCount < 10 } |
pass your own check |
Types can be written as Type constants, dotted names (java.lang.String), or descriptors (Ljava/lang/String;). The reference lists every constraint.
Exactly one match
A method target must match exactly one method. If nothing matches, or several do, the patch fails and lists what it found. To settle a tie:
- add a constraint that only the right method meets,
rankBy("label") { ... }to score candidates and take the single best one,first()to take whichever sorts first, only when the matches are interchangeable.
Use methods { } instead of method { } when you want every match.
Warning
Give every target something specific: a string, a number, a class, or a call. A target like method { returns(Type.Boolean); paramCount(1) } fits half the app, and adding first() to it picks a different method after the next app update.
Classes and fields
val mainActivity = klass("com.example.app.MainActivity")
val onResume = mainActivity.method("onResume") { params() }
val prefs = mainActivity.fieldOfType("android.content.SharedPreferences")
val adLoader = klass("adLoader") {
strings("ad_unit_id", "ad_request_failed")
}
klass("full.Name") looks a class up by name; that works for classes the app doesn't obfuscate. klass("label") { } searches by description, like method. A class target narrows method, methods, field, and fieldOfType to that class. Add inherited = true to include methods the class inherits.
Obfuscation renames an enum's fields but not the names its constants report, so find enums by those names:
val plan = klass("plan") { enumConstants("FREE", "PREMIUM") }
val premium = plan.enumConstant("PREMIUM")
planGetter.replace { returnValue(staticField(premium)) }
enumConstants(...) finds the enum that has these constants. enumConstant(name) is the field holding the constant whose name() is name.
Your own code is left out
Searches only cover the app's own code, not the code your bundle adds to it (see Shipping your own code). Otherwise a match would depend on which patches ran before. Add includeExtensions() to search it too.
When no query fits
methodTarget, classTarget, and fieldTarget take a block that finds the result by hand, with the whole app available:
val adState = classTarget("adState") {
bytecode.findClass(adCounterField.owner) ?: error("ad state class missing")
}
These are slow on large apps; keep them narrow.
Debugging a target
A failed target reports how many methods it looked at, why each was rejected, and the closest misses. A target that resolved is logged at debug level with the method it picked. Inside execute, target.explain() returns the same report.
Common mistakes:
returns("boolean")is not a type. UseType.Booleanor"Z".stringsmatches whole strings only. UsestringsStartingWithfor a prefix.- If every candidate is rejected with
class mismatch, the class target ininClassfound the wrong class.
Next: Pointing at instructions.