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
Refer to Finding Discord Stuff
Refer to Reflection
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:
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)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()
}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!
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 | - |
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" }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";
}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!!";
}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 };