News Welcome to Reseam

Writing patches

Manifest, resources, and files

Besides code, a patch can change the rest of the APK. Inside execute { }:

manifest AndroidManifest.xml: permissions, activities, package, version
resources the app's resources: strings, colors, styles, layouts, images
files any file in the APK
bytecode every class, for lookups and app-wide replacements
hermes the app's Hermes JavaScript bundle
options the patch's options
log info, warn, and debug messages in the patch log

Manifest

manifest.addPermission("android.permission.INTERNET")
manifest.addActivity("app.example.SettingsActivity") { this["android:label"] = "Settings" }
manifest.setVersionName("2.14.0-patched")

manifest.edit {
    val application = findByTag("application").first()
    application["android:debuggable"] = "true"
}

edit { } opens the manifest as an XML document for anything the helpers don't cover.

Mount builds

On a rooted phone, a build can be mounted over the app installed from the store instead of installed as its own app. The app keeps its signature, its data, and Google sign-in. Android keeps reading the manifest of the installed app, so a mount build leaves out every patch that edits the manifest, and the patches that depend on it. Code, resources, and other files apply as usual.

Reach for code and resource changes where they do the job, so the patch works both ways. A patch whose purpose is the manifest, such as a package rename, is left out of mount builds.

Resources

resources.addString("reseam_label", "Reseam")
val appName = resources.getString("app_name")
val id = resources.id("string", "app_name")

resources.editXml("layout", "player_controls") {
    findByTag("ImageView").first()["android:layout_marginBottom"] = "6dp"
}

Release builds often rename resource files (res/layout/player_controls.xml becomes res/QYO.xml), so reach them by resource type and name, as editXml does.

Add an image or other file as a resource:

resources.addFile("drawable", "reseam_icon", "res/reseam_icon.png", bytes)
resources.addFile("mipmap", "reseam_launcher", "res/mipmap-xxhdpi/reseam_launcher.png", png, "xxhdpi")

The last argument is the configuration: empty for the default, a density, night or notnight, or vN. XML passed this way is compiled.

Styles and arrays:

resources.style("Theme.App.Light") { this["headerIcon"] = "@drawable/reseam_icon" }
resources.setStringArray("speed_values", resources.getArray("speed_values") + "3.0")

Warning

Release builds often strip resource names too, so resources.id("string", "app_name") can return null for a resource that exists. Take the id from where the app refers to it instead.

XML

manifest.edit, resources.editXml, and files.editXml(path) give you an XmlDocument: find elements by tag or attribute, read and set attributes, add, insert, and remove elements. The document closes when the block ends, so take values out of it, never the elements themselves.

To add your own layout, compile it and copy its elements in:

XmlDocument.compile(layoutXml).use { source ->
    resources.editXml("layout", "player_controls") {
        root.appendChild(adopt(source.root))
    }
}

Attribute names need resource ids: android: names resolve through Android's own table, other prefixes through the app's attributes. A name that resolves to nothing fails the patch.

Files

files.write("assets/reseam/logo.png", bytes)
files.copy("resources/logo.png", "assets/logo.png")
files.delete("assets/tracking.json")

files.copy takes a file from your bundle's resources/ folder; see Bundle projects.

App-wide replacements

bytecode.replaceAllStrings("com.example", newPackage)
bytecode.replaceStringsContaining("example.com") { old -> old.replace("example.com", "example.org") }

replaceAllStrings only matches whole strings. replaceStringsContaining lets you rewrite strings that contain a value; return null to leave one unchanged.

Split APKs

Apps from the Play Store often come as a base APK plus split APKs for languages, screen densities, and CPU types. manifest, resources, and files act on the base APK unless you pick a split with component(name):

for (name in manifest.components()) {
    manifest.component(name).edit { root["package"] = newPackage }
}

A package name or version change has to reach every split, or Android won't install the set.

Next: Reading obfuscated objects.

Edit this page