Skip to content

Commit 31bcaee

Browse files
committed
Add parity in guidelines
Fixes #36
1 parent 28b6b3d commit 31bcaee

2 files changed

Lines changed: 198 additions & 196 deletions

File tree

docs/plugins/advanced/patching.md

Lines changed: 196 additions & 196 deletions
Original file line numberDiff line numberDiff line change
@@ -1,197 +1,197 @@
1-
---
2-
order: 1
3-
description: Patch other functions with your function.
4-
---
5-
6-
# Function Patching
7-
8-
This guide goes over the concept of function patching also sometimes referred to as monkey patching. If you're already familiar with this concept, consider checking out the [Examples](#examples) that show the utility provided by `BdApi.Patcher`.
9-
## Background
10-
11-
### What is a function patch?
12-
13-
A function patch an advanced technique for plugins that allow you to modify existing functions including Discord's own functions. There are typically three different "kinds" of patches as well. There are ones where you run your own code `before` the original function usually with the goal to modify the arguments before they are passed to the original function. There are patches meant to run `instead` of the original function, taking full control over arguments, functionality, and return value. And there are ones meant to run `after` the original function with the goal of modifying the return value before it is passed elsewhere.
14-
15-
### Why would I use one?
16-
17-
It's a great way to modify or extend Discord's functionality with your own while keeping integration mostly seemless. It can also act as a way to modify the way Discord works currently. Take the plugin [HideDisabledEmojis](https://betterdiscord.app/plugin/HideDisabledEmojis) for example, it uses function patching to modify the way Discord's internal functions work to stop trying to render emojis the user cannot use. Your possibilities for the plugins you can make increase exponentially, and the quality usually ends up being higher due to the tight integration with Discord.
18-
19-
### How can I patch a function?
20-
21-
Unfortunately, you can't patch a function *directly*, you have to modify the *reference* to the function that other code uses. That means if your target function is just a locally or globally available function like this
22-
23-
```js
24-
function yourTarget() {}
25-
```
26-
27-
then you can't really affect it. However, if your target is part of an object in some way, like being contained in an imported module, you can overwrite that reference with your own function causing everyone to call your function instead.
28-
29-
```js:line-numbers
30-
const someObject = {
31-
yourTarget: function() {
32-
console.log("red");
33-
}
34-
};
35-
36-
function targetUser() {
37-
someObject.yourTarget();
38-
}
39-
40-
targetUser(); // Logs "red"
41-
42-
// highlight-start
43-
function myNewFunction() {
44-
console.log("green");
45-
}
46-
47-
someObject.yourTarget = myNewFunction;
48-
// highlight-end
49-
50-
targetUser(); // Now logs "green"
51-
```
52-
53-
If you take a look at the highlighted section, we are creating a new function `myNewFunction` that logs `green` and assigning it to `someObject.yourTarget` effectively overwriting the target function. That means when `targetUser` is called again, your function gets run successfully because it references the `someObject` object. This here is known as an `instead` patch because it completely replaces the target. All patches start this way but can expanded to become a `before` or `after` patch by storing a reference and calling the original function. This also opens the door to subpatches and multiple users, but that can get complicated very fast.
54-
55-
#### BetterDiscord
56-
57-
Luckily, BetterDiscord already has a system in place to manage multiple patches per function and allows you to target different patch types. This means if you want to do a `before` or `after` patch, you no longer have to manually replace the function and retain references and call the original. All of this is done for you with `BdApi.Patcher`. Let's take a look at how our example above could be done with this module.
58-
59-
```js:line-numbers
60-
const someObject = {
61-
yourTarget: function() {
62-
console.log("red");
63-
}
64-
};
65-
66-
function targetUser() {
67-
someObject.yourTarget();
68-
}
69-
70-
targetUser(); // Logs "red"
71-
72-
// highlight-start
73-
BdApi.Patcher.instead("MyPlugin", someObject, "yourTarget", () => console.log("green"));
74-
// highlight-end
75-
76-
targetUser(); // Now logs "green"
77-
```
78-
79-
This code has the some effect as before, causing `targetUser` to instead log `green`. But lets take a closer look at the highlighted line. We have a call to `BdApi.Patcher.instead` which indicates we want to create an `instead` patch. We pass it `"MyPlugin"` which is an identifier used later to help removed all your patches with `BdApi.Patcher.unpatchAll`. Then we give it the target object `someObject` and the key of our target inside that object `yourTarget` and our new function to override the original. BetterDiscord takes care of the rest and even allows other plugins to patch on top of yours.
80-
81-
82-
## Examples
83-
84-
For all of these examples, our setup is the following:
85-
86-
```js:line-numbers
87-
function someGlobal() {
88-
console.log("global function");
89-
return 2;
90-
}
91-
92-
const someModule = {
93-
value: "foobar",
94-
method(val = 0) {
95-
const globalValue = someGlobal();
96-
return globalValue + 1 + val;
97-
},
98-
otherMethod(someArg) {
99-
console.log(`My value ${someArg}`);
100-
}
101-
};
102-
```
103-
104-
In this setup, `someGlobal` is a function that cannot be patched because there is no reference to replace. However `someModule.method` and `someModule.otherMethod` can both be patched.
105-
106-
### Before
107-
108-
If there's a function you want to modify the arguments for, a `before` patch is the right one for you. Take a look at this patch below.
109-
110-
```js:line-numbers
111-
BdApi.Patcher.before("MyPlugin", someModule, "otherMethod", (thisObject, args) => {
112-
console.log(args);
113-
});
114-
115-
someModule.otherMethod("something");
116-
// > ["something"]
117-
// > My value something
118-
```
119-
120-
In this example we didn't modify the arguments, we just wanted to log them out to see what kind of values we might get. This is a good technique to help modify arguments selectively. Suppose we don't mind that `something` is logged, but we don't like when `token` is logged. How might that look?
121-
122-
```js:line-numbers
123-
BdApi.Patcher.before("MyPlugin", someModule, "otherMethod", (thisObject, args) => {
124-
const firstArgument = args[0];
125-
// highlight-start
126-
if (firstArgument === "token") {
127-
args[0] = "redacted";
128-
}
129-
// highlight-end
130-
});
131-
132-
someModule.otherMethod("something"); // > My value something
133-
someModule.otherMethod("token"); // > My value redacted
134-
```
135-
136-
This highlighted section checks when someone passes `token` as the value to `otherMethod` and replaces it with `redacted`. Note the replacement that happens inside the `if` statement. This is another case of using a reference to overwrite, except this time it is in the `arguments` array. This is something to keep in mind as you do more `before` patches.
137-
138-
### Instead
139-
140-
You may have already seen a basic `instead` patch in the [section above](#how-can-i-patch-a-function) but let's take a look at a slightly more complex version.
141-
142-
```js:line-numbers
143-
function myFunction(val) {
144-
console.log(`Intercepted ${val}`);
145-
}
146-
147-
BdApi.Patcher.instead("MyPlugin", someModule, "method", (thisObject, args, originalFunction) => {
148-
const firstArgument = args[0];
149-
if (firstArgument === 5) return originalFunction(...args);
150-
if (firstArgument === 1) return myFunction(...args);
151-
});
152-
153-
someModule.method(5); // > 8
154-
someModule.method(1); // > Intercepted other
155-
someModule.method(1); // > undefined
156-
```
157-
158-
Take alook at the function we define in the `instead` patch. We have a new parameter `originalFunction` that BetterDiscord gives us to use as we see fit. In this example we use it for a specific value. If the value is `5` we let the original function run and return without modification. If the value is `1` we pass it to an external function and let that handle the arguments and the return. Otherwise, the function has no return value at all. This is a huge change to the function. It used to always return a value and now it only returns values for two cases. This is a good demonstration how much power function patching can have.
159-
160-
### After
161-
162-
This type of patch is perhaps the most frequently used in plugins, but if you've stuck with us for the first two, this one will be easy to get the hang of.
163-
164-
```js:line-numbers
165-
BdApi.Patcher.after("MyPlugin", someModule, "method", (thisObject, args, returnValue) => {
166-
return returnValue * 2;
167-
});
168-
169-
someModule.method(5); // > 16
170-
someModule.method(); // > 6
171-
```
172-
173-
You'll notice that `originalFunction` from before has turned into `returnValue`. Here we simply multiply that by `2` every time and return the value to the caller. So that means for any number we pass, the original function applies and returns, then our patch picks up that value and multiplies by `2`, then the function caller finally gets their value. The BetterDiscord `Patcher` will use whatever `return` value you use. However if you *don't* return anything, then the original return value is used. This can have profound effects. Consider this case below:
174-
175-
```js
176-
const myNewNumber = 5 / someModule.method(5);
177-
```
178-
179-
Now let's switch up our return value for only `5`.
180-
181-
```js:line-numbers
182-
BdApi.Patcher.after("MyPlugin", someModule, "method", (thisObject, args, returnValue) => {
183-
if (args[0] === 5) return {};
184-
});
185-
```
186-
187-
In our patch this time, we `return` a value only in the case of `5`, in all other cases the default `return` of the original function is used because we didn't return anything. If we wanted to stop that we could put a `return null;` on the next line. You may have also noticed that our return is no longer a value. So what happens to our case above?
188-
189-
```js:line-numbers
190-
BdApi.Patcher.after("MyPlugin", someModule, "method", (thisObject, args, returnValue) => {
191-
if (args[0] === 5) return {};
192-
});
193-
194-
const myNewNumber = 5 / someModule.method(5); // NaN
195-
```
196-
1+
---
2+
order: 1
3+
description: Patch other functions with your function.
4+
---
5+
6+
# Function Patching
7+
8+
This guide goes over the concept of function patching also sometimes referred to as monkey patching. If you're already familiar with this concept, consider checking out the [Examples](#examples) that show the utility provided by `BdApi.Patcher`.
9+
## Background
10+
11+
### What is a function patch?
12+
13+
A function patch an advanced technique for plugins that allow you to modify existing functions including Discord's own functions. There are typically three different "kinds" of patches as well. There are ones where you run your own code `before` the original function usually with the goal to modify the arguments before they are passed to the original function. There are patches meant to run `instead` of the original function, taking full control over arguments, functionality, and return value. And there are ones meant to run `after` the original function with the goal of modifying the return value before it is passed elsewhere.
14+
15+
### Why would I use one?
16+
17+
It's a great way to modify or extend Discord's functionality with your own while keeping integration mostly seemless. It can also act as a way to modify the way Discord works currently. Take the plugin [HideDisabledEmojis](https://betterdiscord.app/plugin/HideDisabledEmojis) for example, it uses function patching to modify the way Discord's internal functions work to stop trying to render emojis the user cannot use. Your possibilities for the plugins you can make increase exponentially, and the quality usually ends up being higher due to the tight integration with Discord.
18+
19+
### How can I patch a function?
20+
21+
Unfortunately, you can't patch a function *directly*, you have to modify the *reference* to the function that other code uses. That means if your target function is just a locally or globally available function like this
22+
23+
```js
24+
function yourTarget() {}
25+
```
26+
27+
then you can't really affect it. However, if your target is part of an object in some way, like being contained in an imported module, you can overwrite that reference with your own function causing everyone to call your function instead.
28+
29+
```js:line-numbers
30+
const someObject = {
31+
yourTarget: function() {
32+
console.log("red");
33+
}
34+
};
35+
36+
function targetUser() {
37+
someObject.yourTarget();
38+
}
39+
40+
targetUser(); // Logs "red"
41+
42+
// highlight-start
43+
function myNewFunction() {
44+
console.log("green");
45+
}
46+
47+
someObject.yourTarget = myNewFunction;
48+
// highlight-end
49+
50+
targetUser(); // Now logs "green"
51+
```
52+
53+
If you take a look at the highlighted section, we are creating a new function `myNewFunction` that logs `green` and assigning it to `someObject.yourTarget` effectively overwriting the target function. That means when `targetUser` is called again, your function gets run successfully because it references the `someObject` object. This here is known as an `instead` patch because it completely replaces the target. All patches start this way but can expanded to become a `before` or `after` patch by storing a reference and calling the original function. This also opens the door to subpatches and multiple users, but that can get complicated very fast.
54+
55+
#### BetterDiscord
56+
57+
Luckily, BetterDiscord already has a system in place to manage multiple patches per function and allows you to target different patch types. This means if you want to do a `before` or `after` patch, you no longer have to manually replace the function and retain references and call the original. All of this is done for you with `BdApi.Patcher`. Let's take a look at how our example above could be done with this module.
58+
59+
```js:line-numbers
60+
const someObject = {
61+
yourTarget: function() {
62+
console.log("red");
63+
}
64+
};
65+
66+
function targetUser() {
67+
someObject.yourTarget();
68+
}
69+
70+
targetUser(); // Logs "red"
71+
72+
// highlight-start
73+
BdApi.Patcher.instead("MyPlugin", someObject, "yourTarget", () => console.log("green"));
74+
// highlight-end
75+
76+
targetUser(); // Now logs "green"
77+
```
78+
79+
This code has the same effect as before, causing `targetUser` to instead log `green`. But lets take a closer look at the highlighted line. We have a call to `BdApi.Patcher.instead` which indicates we want to create an `instead` patch. We pass it `"MyPlugin"` which is an identifier used later to help removed all your patches with `BdApi.Patcher.unpatchAll`. Then we give it the target object `someObject` and the key of our target inside that object `yourTarget` and our new function to override the original. BetterDiscord takes care of the rest and even allows other plugins to patch on top of yours.
80+
81+
82+
## Examples
83+
84+
For all of these examples, our setup is the following:
85+
86+
```js:line-numbers
87+
function someGlobal() {
88+
console.log("global function");
89+
return 2;
90+
}
91+
92+
const someModule = {
93+
value: "foobar",
94+
method(val = 0) {
95+
const globalValue = someGlobal();
96+
return globalValue + 1 + val;
97+
},
98+
otherMethod(someArg) {
99+
console.log(`My value ${someArg}`);
100+
}
101+
};
102+
```
103+
104+
In this setup, `someGlobal` is a function that cannot be patched because there is no reference to replace. However `someModule.method` and `someModule.otherMethod` can both be patched.
105+
106+
### Before
107+
108+
If there's a function you want to modify the arguments for, a `before` patch is the right one for you. Take a look at this patch below.
109+
110+
```js:line-numbers
111+
BdApi.Patcher.before("MyPlugin", someModule, "otherMethod", (thisObject, args) => {
112+
console.log(args);
113+
});
114+
115+
someModule.otherMethod("something");
116+
// > ["something"]
117+
// > My value something
118+
```
119+
120+
In this example we didn't modify the arguments, we just wanted to log them out to see what kind of values we might get. This is a good technique to help modify arguments selectively. Suppose we don't mind that `something` is logged, but we don't like when `token` is logged. How might that look?
121+
122+
```js:line-numbers
123+
BdApi.Patcher.before("MyPlugin", someModule, "otherMethod", (thisObject, args) => {
124+
const firstArgument = args[0];
125+
// highlight-start
126+
if (firstArgument === "token") {
127+
args[0] = "redacted";
128+
}
129+
// highlight-end
130+
});
131+
132+
someModule.otherMethod("something"); // > My value something
133+
someModule.otherMethod("token"); // > My value redacted
134+
```
135+
136+
This highlighted section checks when someone passes `token` as the value to `otherMethod` and replaces it with `redacted`. Note the replacement that happens inside the `if` statement. This is another case of using a reference to overwrite, except this time it is in the `arguments` array. This is something to keep in mind as you do more `before` patches.
137+
138+
### Instead
139+
140+
You may have already seen a basic `instead` patch in the [section above](#how-can-i-patch-a-function) but let's take a look at a slightly more complex version.
141+
142+
```js:line-numbers
143+
function myFunction(val) {
144+
console.log(`Intercepted ${val}`);
145+
}
146+
147+
BdApi.Patcher.instead("MyPlugin", someModule, "method", (thisObject, args, originalFunction) => {
148+
const firstArgument = args[0];
149+
if (firstArgument === 5) return originalFunction(...args);
150+
if (firstArgument === 1) return myFunction(...args);
151+
});
152+
153+
someModule.method(5); // > 8
154+
someModule.method(1); // > Intercepted other
155+
someModule.method(1); // > undefined
156+
```
157+
158+
Take alook at the function we define in the `instead` patch. We have a new parameter `originalFunction` that BetterDiscord gives us to use as we see fit. In this example we use it for a specific value. If the value is `5` we let the original function run and return without modification. If the value is `1` we pass it to an external function and let that handle the arguments and the return. Otherwise, the function has no return value at all. This is a huge change to the function. It used to always return a value and now it only returns values for two cases. This is a good demonstration how much power function patching can have.
159+
160+
### After
161+
162+
This type of patch is perhaps the most frequently used in plugins, but if you've stuck with us for the first two, this one will be easy to get the hang of.
163+
164+
```js:line-numbers
165+
BdApi.Patcher.after("MyPlugin", someModule, "method", (thisObject, args, returnValue) => {
166+
return returnValue * 2;
167+
});
168+
169+
someModule.method(5); // > 16
170+
someModule.method(); // > 6
171+
```
172+
173+
You'll notice that `originalFunction` from before has turned into `returnValue`. Here we simply multiply that by `2` every time and return the value to the caller. So that means for any number we pass, the original function applies and returns, then our patch picks up that value and multiplies by `2`, then the function caller finally gets their value. The BetterDiscord `Patcher` will use whatever `return` value you use. However if you *don't* return anything, then the original return value is used. This can have profound effects. Consider this case below:
174+
175+
```js
176+
const myNewNumber = 5 / someModule.method(5);
177+
```
178+
179+
Now let's switch up our return value for only `5`.
180+
181+
```js:line-numbers
182+
BdApi.Patcher.after("MyPlugin", someModule, "method", (thisObject, args, returnValue) => {
183+
if (args[0] === 5) return {};
184+
});
185+
```
186+
187+
In our patch this time, we `return` a value only in the case of `5`, in all other cases the default `return` of the original function is used because we didn't return anything. If we wanted to stop that we could put a `return null;` on the next line. You may have also noticed that our return is no longer a value. So what happens to our case above?
188+
189+
```js:line-numbers
190+
BdApi.Patcher.after("MyPlugin", someModule, "method", (thisObject, args, returnValue) => {
191+
if (args[0] === 5) return {};
192+
});
193+
194+
const myNewNumber = 5 / someModule.method(5); // NaN
195+
```
196+
197197
This lead `myNewNumber` to become `NaN` or *not a number*. Which is ironic considering the variable name. But it's a good example of how careful we need to be when it comes to modifying the returns of functions.

0 commit comments

Comments
 (0)