News Welcome to Reseam

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. Its after { } does not: a branch that jumps straight to the next instruction skips it. For code that must run on every path, use before { } on a later point, or the method's after { }.
  • 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, not assign.
  • Access is checked: calling a private method of another class fails while patching.

Next: Manifest, resources, and files.

Edit this page