Tutorials: Injectors - Nnubes256/Dorito GitHub Wiki
Injectors
When modding Javascript games, you can usually see that, on its script files, a large amount of the game's variable names are pretty much random, and that all the script is compacted on one single line, or on a few. We can say that this script is minified.
Minification usually is done for two reasons: to give the game a chance against reverse-enginnering(not useful for these purposes, as given enough time a normal programmer will end up understanding the code) and to reduce the game's size(the main reason. In fact, almost all scripts that you browser loads when you do normal web browsing are usually minified because of this, as it saves bandwidth).
For modders, this adds two problems:
- Editing game code directly becomes counter-productive: you have to de-minify(colloquially known as 'beautify') the file, do reverse-enginnering(search the code we want to edit), edit the code(which means more reverse-enginnering), test if the edit works, and minify it back.
- Distribution of patches becomes a nightmare: either you distribute the entire script with the modification(which can lead in legal issues with the developers) or distribute it as a patch(which is not efective with minified Javascript, and requires you to repeat patching with every game version).
Dorito solves the latter by providing a way to add code to functions dynamically: injectors.
Overriding a function
To add code to a function, we'll use the override function, which takes three arguments:
- A reference to the object where the function is stored.
- The name of the function inside the object you want to modify.
- A decorator: a function that takes the original function as argument and has to return another function with the new behavior.
What does the override function is simply this:
Injectors.override = function(object, methodName, overrideFunction) {
object[methodName] = overrideFunction(object[methodName]);
};
Basically, we call our decorator function(overrideFunction), which takes our original function as argument. We then replace our old function with the return value of the decorator.
Decorators
Let's have a closer look at decorators.
myCustomDecorator = function(original) {
return function() { // InnerFunction
var returnValue = original.apply(this, newArguments); // [1]
if(returnValue === 0)
return true;
else
return false;
};
};
Here, our original function will be replaced by InnerFunction, and all calls to the original function will now forward to this function, with arguments and context equal of those of the original function. This means you are also responsible of return values, so watch out!.
Note that we'll still have a reference to the original function(original), so we can call it even after replacing it.
In this example, we'll call our original function with the original arguments, but instead of simply returning the call result, we'll return true if it's 0, and false otherwise.
If you want to call the original function, or your custom function to execute, or whatever, use Function.apply(this, args), so the context doesn't change unexpectedly. ([1]).
Here's how you would use your custom decorator:
// Our minified function
// Suppose this function is on the global namespace(window).
function abx(b) {
// This will return 1 if b is null/undefined/etc, 2 if it exists and 0 if it's also an string.
return b ? typeof b === 'string' ? 0 : 2 : 1;
}
// ---------------------
abx(); // returns 1
abx(7); // returns 2
abx("Hi"); // returns 0
override(window, "abx", myCustomDecorator);
abx(); // returns false
abx(7); // returns false
abx("Hi"); // returns true
Built-in inject types
TODO write