To demonstrate how to use Substrate to build an extension, we will now walk through the implementation of a concrete example: an extension that modifies the color of numerous interface components to become shades of violet. This extension is useless, but has the advantage of being immediately noticeable.
For our code to be loaded, our package must have the cydia.permission.SUBSTRATE permission.
We also the name of a class to be loaded, which we will define in the next step.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <meta-data android:name="com.saurik.substrate.main" android:value=".Main"/> </application> <uses-permission android:name="cydia.permission.SUBSTRATE"/> </manifest>
Once loaded, the static "initialize" method is executed, allowing us to run code that uses the public API provided by Substrate's MS class.
import com.saurik.substrate.MS; public class Main { static void initialize() { // ... code to run when extension is loaded } }
In order to change the code for a class, we first need a reference to that class. Substrate allows us to provide a callback that will be executed when classes with specific descriptors are loaded.
public class Main { static void initialize() { MS.hookClassLoad("android.content.res.Resources", new MS.ClassLoadHook() { public void classLoaded(Class<?> resources) { // ... code to modify the class when loaded } }); } }
The implementation of the method is proxied through the provided MS.MethodHook instance: all types—both in and out—are "boxed" to Object.
To call the previous implementation, we also set up an instance of MS.MethodPointer, which can be invoked at any time to run the original code.
Here, we call through to the original and modify the result, removing all green from the color and jacking up the red (yielding shades of violet).
Please note that this example explicitly avoids simplifications such as MS.MethodAlteration and the usage of Java generics for overall code clarity.
public void classLoaded(Class<?> resources) { Method getColor; try { getColor = resources.getMethod("getColor", Integer.TYPE); } catch (NoSuchMethodException e) { getColor = null; } if (getColor != null) { final MS.MethodPointer old = new MS.MethodPointer(); MS.hookMethod(resources, getColor, new MS.MethodHook() { public Object invoked(Object resources, Object... args) throws Throwable { int color = (Integer) old.invoke(resources, args); return color & ~0x0000ff00 | 0x00ff0000; } }, old); } }
Using Java generics and MS.MethodAlteration allows you to simplify the implementation: the return and object types can be explicitly specified, and the MS.MethodPointer old is not required.