Writing patches
Changing code
Once a target is found, you add code to it. You write the code as Kotlin calls; Reseam turns them into Android bytecode and handles the registers.
execute {
loadFeed.before {
call(AdBlocker.onFeedLoad, thisObject.field(feedItems))
}
buildTitle.after {
returnValue(call(Badges.append, result))
}
configureDownloads.replace {
thisObject.set(maxParallelDownloads, int(8))
returnVoid()
}
}
Where code goes
| On a method | |
|---|---|
before { } |
at the start |
after { } |
before every return; result is the value being returned |
replace { } |
instead of the whole body |
On a point, before { } and after { } place code just before or just after that instruction.
Shortcuts for common cases: alwaysReturn(true), alwaysReturnNull(), replaceAllStrings(old, new), replaceAllLiterals(old, new).
Warning
The Kotlin in a code block runs once, while patching, and each call adds instructions. A Kotlin if chooses what to add; it is not a branch in the app. Use whenTrue and the other branches below.
Values
thisObject, param(i), lastParam |
the object and the method's parameters |
int(1), bool(true), string("x"), nullObject |
constants |
call(extMethod, args), value.call(...) |
call a method and use its result |
value.field(f), staticField(f) |
read a field |
value.set(f, v), setStatic(f, v) |
write a field |
value.cast(type), newInstance(type) |
cast, or create an object |
capture("name"), value.assign(v) |
read a captured value, or overwrite it |
The reference lists everything. A value belongs to the block that created it; using it in another block fails.
Branches and returns
whenTrue(call(isCurrentUser, param(0))) {
returnTrue()
} otherwise {
returnFalse()
}
whenTrue, whenFalse, whenNull, whenNotNull, whenEqual, whenNotEqual, and whenInstanceOf each take an optional otherwise { }. returnVoid, returnValue, returnTrue, returnFalse, and returnNull return from the method.
Gates
A gate checks a setting while the app runs:
gate(AppSettings.hideAds) {
showAd.alwaysReturn()
adSlot.alwaysReturnNull()
loadFeed.after { thisObject.set(adCount, int(0)) }
}
whenEnabled(AppSettings.fasterDownloads) {
thisObject.set(maxParallelDownloads, int(8))
}
Hooks written inside gate(setting) { } only take effect while the setting is on: before { }, after { }, and alwaysReturn…, on methods and points. On a point, skip() skips that one call. Inside a code block, whenEnabled branches on a setting.
Calling your own code
For anything longer than a few lines, write Kotlin in an extension, mark what patches call with @Export, and call it through the generated binding:
import app.example.ads.bindings.AdBlocker
loadFeed.before { AdBlocker.onFeedLoad(thisObject.field(feedItems)) }
Redirecting a call
point.redirectTo(extMethod) replaces one call with a call to your static method. For an instance call, the object comes first. bytecode.redirectCalls(owner, name, extMethod) does the same for every call to a method anywhere in the app:
bytecode.redirectCalls("android.telephony.TelephonyManager", "getDeviceId", Identity.deviceId)
Calls made from your own extension code keep going to the original, so your method can still call it.
Code at app start
appEntry { } adds code that runs once when the app's process starts, before any screen opens. application is the app's Application object:
appEntry { AdBlocker.init(application) }
The app must name an Application class in its manifest.
Passing a value between blocks
To set a value in one place and read it in another within the same method, reserve a local:
val seen = loadItems.reserveLocal("seen", Type.Boolean)
loadItems.point { string("sponsored") }.after { local(seen).assign(bool(true)) }
loadItems.after { whenTrue(local(seen)) { call(Telemetry.sponsoredShown) } }
It starts as zero or null when the method begins.
Things to know
- A point's
before { }also runs when a branch jumps to that instruction. Itsafter { }does not: a branch that jumps straight to the next instruction skips it. For code that must run on every path, usebefore { }on a later point, or the method'safter { }. - Added code that needs more registers than the surrounding instructions can address fails with
Cannot allocate ... registers. Move that logic into an extension. - Reading a field gives a copy. Write it back with
set, notassign. - Access is checked: calling a private method of another class fails while patching.