Skip to content

Latest commit

 

History

History
235 lines (177 loc) · 8.4 KB

File metadata and controls

235 lines (177 loc) · 8.4 KB

Patching

Aliucord currently uses our Aliuhook bindings to the LSPlant ART hooking framework for hooking existing methods at runtime.

You can use it to run your own code before, instead of or after any method of any Discord class

Finding the right method to patch

Refer to Finding Discord Stuff

Retrieving private fields / calling private methods inside patches

Refer to Reflection

The Basics

Every plugin has its own Patcher instance as patcher inside your Plugin class

You can add patches using patcher.patch(method, methodHook). This will return a Runnable that when invoked will remove the patch again. Alternatively, you can simply use patcher.unpatchAll() to remove all patches.

The patch method essentially takes two arguments. A fully qualified method and a XC_MethodHook.

Assuming you want to patch the applyMagic method of the Magician class:

package com.discord.magic;

public class Magician {
    public void applyMagic(Context context, String kind, int count) {
        // Some magical code
    }
}

There are two ways to do so:

Specifying the className, methodName and arguments

Java
patcher.patch("com.discord.magic.Magician", "applyMagic", new Class<?>[] { Context.class, String.class, int.class }, myMethodHook);
Kotlin
patcher.patch("com.discord.magic.Magician", "applyMagic", arrayOf(Context::class.java, String::class.java, Int::class.javaPrimitiveType), myMethodHook)

Retrieving the Method yourself

Java
patcher.patch(Magician.class.getDeclaredMethod("applyMagic", Context.class, String.class, int.class), myMethodHook);
Kotlin
patcher.patch(Magician::class.java.getDeclaredMethod("applyMagic", Context::class.java, String::class.java, Int::class.javaPrimitiveType), myMethodHook)
// or for convenience one of before, instead or after like so:
patcher.before<Magician>("applyMagic", ...) {
    // receiver (this) is correctly typed magician here now so you could do something like
    someMagicianMethod()
}

Woah that looks scary D:

Due to Java's method overloads, there can be multiple methods with the same name but different parameters. Thus, it is necessary to specify the parameter types of the method. .class here (or ::class.java if you are using Kotlin) simply retrieves the runtime representation of your class which can be used for Reflection. You basically just need to take the parameters of the desired method and append .class/::class.java to them!

Reference:

MethodHooks and MethodHookParams

The XC_MethodHook class describes how the method should be patched. Possible methods are beforeCall and afterCall. To replace the method you can either use beforeCall and set the result or use XC_MethodReplacement

For convenience, Aliucord provides the Hook, PreHook and InsteadHook classes that take a single lambda method as their only argument. These should always be used whenever possible, as it allows aliucord to catch and log errors appropriately.

No matter which patch method you decide for, you will always work with a MethodHookParam which is essentially a Context object of the method you're patching. It contains the thisObject (the class the method belongs to), the arguments passed to the method, the result of the method if any and way more. It behaves a bit differently depending on whether this is a prePatch or regular patch:

Field/Method name Description PrePatch Normal Patch
method The Method you are patching - -
thisObject The class this method belongs too or null if it is a static method - -
args All arguments passed to the method Altering these will pass the altered arguments to the original method -
getResult() Returns the result of the method null The result of the method or null if void
setResult(Object) Sets the result Will prevent the original method from running Altered result will be returned to caller
getThrowable() Returns the throwable if the method threw null Possible throwable
setThrowable(Throwable) Makes this method throw the specified Throwable Will prevent the original method from running You better not crash the app!!
resetResult() Resets result and throwable No longer prevent original method from running No longer throw; return null
invokeOriginalMethod() Invokes original method and returns the result Just use an afterPatch -

Some Examples

Everyone is now called Clyde - InsteadHook (returnConstant)

Java
import com.aliucord.patcher.InsteadHook;
import com.discord.models.user.CoreUser;

patcher.patch(CoreUser.class.getDeclaredMethod("getUsername"), InsteadHook.returnConstant("Clyde"));
Kotlin
import com.aliucord.patcher.InsteadHook
import com.discord.models.user.CoreUser

patcher.instead<CoreUser>("getUsername") { "Clyde" }

Rename all users named Clyde to Evil Clyde - Hook

Java
import com.aliucord.patcher.Hook;
import com.discord.models.user.CoreUser;

patcher.patch(CoreUser.class.getDeclaredMethod("getUsername"), new Hook(methodHookParam -> {
    var name = (String) methodHookParam.getResult();
    if (name != null && name.equalsIgnoreCase("Clyde")) methodHookParam.setResult("Evil Clyde");
});
Kotlin
import com.aliucord.patcher.Hook
import com.discord.models.user.CoreUser

patcher.after<CoreUser>("getUsername") {
    val name = it.result as String?
    if (name != null && name.equalsIgnoreCase("Clyde")) it.result = "Evil Clyde";
}

Rename specific User - PreHook

Java
import com.aliucord.patcher.PreHook;
import com.discord.models.user.CoreUser;

patcher.patch(CoreUser.class.getDeclaredMethod("getUsername"), new PreHook(methodHookParam -> {
    var currentUser = (CoreUser) methodHookParam.thisObject;
    long id = currentUser.getId();
    if (id == 343383572805058560L) methodHookParam.setResult("Not Clyde!!");
});
Kotlin
import com.aliucord.patcher.PreHook
import com.discord.models.user.CoreUser

patcher.before<CoreUser>("getUsername") {
    if (id == 343383572805058560L) it.result = "Not Clyde!!";
}

Hide your typing indicator from others - InsteadHook.DO_NOTHING

Java
import com.discord.stores.StoreUserTyping;
import top.canyie.pine.callback.InsteadHook;

patcher.patch(StoreUserTyping.class.getDeclaredMethod("setUserTyping", long.class), InsteadHook.DO_NOTHING);
Kotlin
import com.discord.stores.StoreUserTyping
import top.canyie.pine.callback.InsteadHook

patcher.instead<StoreUserTyping>("setUserTyping", Long::class.javaPrimitiveType) { null };