revenge-bundle-next/lib/patcher/README.md
2025-10-24 21:27:07 +07:00

341 lines
8.6 KiB
Markdown

# @revenge-mod/patcher
This is a JavaScript library that allows to monkey patch functions in a way that is easy to use and understand.
It is made by [ryan-0324](https://github.com/ryan-0324) [(source uploaded on Discord)](https://discord.com/channels/1205207689832038522/1284131216156655646/1316076586981789709) and licensed under [CC0](./LICENSE).
This source code is polished and adapted to work with Revenge. We do not claim any rights to this library. You may use it granted you follow the license.
## How does this work?
Think of hooks as separate functions that are called `before`, `after`, or `instead` of the original function.
You can use them to modify the behavior of the original function, or to add new functionality.
### Before hooks
Before hooks allow you to modify the arguments passed to the original function, or to perform some action before the original function is called.
```js
import { before } from '@revenge-mod/patcher'
const obj = {
method: (a) => console.log('Original method called with:', a)
}
before(obj, 'method', ([a]) => {
console.log('Before method called with:', a)
// Modify arguments by returning new array
return [a + 1]
})
obj.method(2)
// CONSOLE OUTPUT:
// Before method called with: 2
// Original method called with: 3
```
### After hooks
After hooks allow you to modify the return value of the original function, or to perform some action after the original function is called.
```js
import { after } from '@revenge-mod/patcher'
const obj = {
method: (a) => a * 2
}
after(obj, 'method', (result) => {
console.log('After method called with result:', result)
// Modify return value
return result + 1
})
console.log(obj.method(2)) // 5
// CONSOLE OUTPUT:
// After method called with result: 4
// 5
```
### Instead hooks
Instead hooks allow you to completely replace the original function with a new one, while still being able to call the original function if needed.
```js
import { instead } from '@revenge-mod/patcher'
const obj = {
method: (a) => {
console.log('Original method called with:', a)
return 'original result'
}
}
instead(obj, 'method', ([a], original) => {
console.log('Instead method called with:', a)
// Call the original function if needed
const originalResult = original('modified')
console.log('Original method was called')
// Return a new value
return 'new value'
})
console.log(obj.method('test')) // 'new value'
// CONSOLE OUTPUT:
// Instead method called with: test
// Original method called with: modified
// Original method was called
// new value
```
### Combining hooks
You can combine multiple hooks on the same function.
```js
import { before, after, instead } from '@revenge-mod/patcher'
const obj = {
method: (a) => a * 2
}
before(obj, 'method', ([a]) => {
console.log('Before method called with:', a)
return [a + 1] // 2 becomes 3
})
instead(obj, 'method', ([a], original) => {
console.log('Instead method called with:', a)
const result = original(a) // Call original with modified arg (3)
console.log('Original method returned:', result)
return result + 1 // 6 becomes 7
})
after(obj, 'method', (result) => {
console.log('After method called with result:', result)
return result + 1 // 7 becomes 8
})
console.log(obj.method(2)) // 8
// CONSOLE OUTPUT:
// Before method called with: 2
// Instead method called with: 3
// Original method returned: 6
// After method called with result: 7
// 8
```
### Stacking hooks
Stacking multiple hooks of same type will execute them in a specific order:
1. **Before hooks** (execute in reverse order - last added runs first)
2. **Instead hooks** (only the last added executes, unless calling original)
3. **After hooks** (execute in order - first added runs first)
Using multiple `before` hooks will execute them in **reverse order** (LIFO - Last In, First Out):
```js
import { before } from '@revenge-mod/patcher'
const obj = {
method: (a) => console.log('Original method called with:', a)
}
before(obj, 'method', ([a]) => {
console.log('First before hook called with:', a)
return [a + 1]
})
before(obj, 'method', ([a]) => {
console.log('Second before hook called with:', a)
return [a + 2]
})
obj.method(2)
// CONSOLE OUTPUT:
// Second before hook called with: 2
// First before hook called with: 4
// Original method called with: 5
```
Multiple `after` hooks execute in **normal order** (FIFO - First In, First Out):
```js
import { after } from '@revenge-mod/patcher'
const obj = {
method: (a) => a * 2
}
after(obj, 'method', (result) => {
console.log('First after hook called with result:', result)
return result + 1
})
after(obj, 'method', (result) => {
console.log('Second after hook called with result:', result)
return result + 2
})
console.log(obj.method(2)) // 7
// CONSOLE OUTPUT:
// First after hook called with result: 4
// Second after hook called with result: 5
// 7
```
Multiple `instead` hooks will only execute the **last one added**. To chain them, you must call the original function:
```js
import { instead } from '@revenge-mod/patcher'
const obj = {
method: (a) => `original-${a}`
}
instead(obj, 'method', ([a], original) => {
console.log('First instead hook called with:', a)
return `first-${original(a)}`
})
instead(obj, 'method', ([a], original) => {
console.log('Second instead hook called with:', a)
// Calling original will execute the previous instead hook
return `second-${original(a)}`
})
console.log(obj.method('test')) // 'second-first-original-test'
// CONSOLE OUTPUT:
// Second instead hook called with: test
// First instead hook called with: test
// second-first-original-test
```
### Removing hooks
Each hook returns an unpatch function that removes the hook:
```js
import { before } from '@revenge-mod/patcher'
const obj = {
method: (a) => console.log('Original method called with:', a)
}
const unpatch = before(obj, 'method', ([a]) => {
console.log('Before method called with:', a)
return [a + 1]
})
obj.method(2)
// CONSOLE OUTPUT:
// Before method called with: 2
// Original method called with: 3
unpatch() // Remove the hook
obj.method(2)
// CONSOLE OUTPUT:
// Original method called with: 2
```
Unpatch functions are safe to call multiple times and won't throw errors.
### Hook priority
All hooks support an optional priority parameter to control execution order. Higher priority hooks execute before lower priority hooks.
```js
import { before, after, instead, HookPriority } from '@revenge-mod/patcher'
const obj = {
method: (a) => a
}
// Before hooks execute in ascending priority order (lowest to highest)
before(obj, 'method', (args) => {
console.log('Low priority before hook')
return args
}, { priority: HookPriority.LOW })
before(obj, 'method', (args) => {
console.log('High priority before hook')
return args
}, { priority: HookPriority.HIGH })
obj.method(1)
// CONSOLE OUTPUT:
// Low priority before hook
// High priority before hook
// After hooks execute in descending priority order (highest to lowest)
after(obj, 'method', (result) => {
console.log('High priority after hook')
return result
}, { priority: HookPriority.HIGH })
after(obj, 'method', (result) => {
console.log('Low priority after hook')
return result
}, { priority: HookPriority.LOW })
obj.method(1)
// CONSOLE OUTPUT:
// High priority after hook
// Low priority after hook
// Instead hooks execute in descending priority order (highest to lowest)
instead(obj, 'method', function (args, original) {
console.log('High priority instead hook')
return original.apply(this, args)
}, { priority: HookPriority.HIGH })
instead(obj, 'method', function (args, original) {
console.log('Low priority instead hook')
return original.apply(this, args)
}, { priority: HookPriority.LOW })
obj.method(1)
// CONSOLE OUTPUT:
// High priority instead hook
// Low priority instead hook
```
Priority constants:
- `HookPriority.HIGHEST` (1000)
- `HookPriority.HIGH` (500)
- `HookPriority.NORMAL` (0) - default
- `HookPriority.LOW` (-500)
- `HookPriority.LOWEST` (-1000)
You can also use custom numeric values for fine-grained control.
### Constructor patching
You can also patch constructor functions:
```js
import { before } from '@revenge-mod/patcher'
class MyClass {
constructor(value) {
this.value = value
}
}
const exports = { MyClass }
before(exports, 'MyClass', ([value]) => {
console.log('Constructor called with:', value)
return [`modified-${value}`]
})
const instance = new exports.MyClass('test')
console.log(instance.value) // 'modified-test'
// CONSOLE OUTPUT:
// Constructor called with: test
// modified-test
```