Update patcher

pull/2/head
BakedPVP 4 years ago
parent 42a436f542
commit 84fa19f68d

@ -255,7 +255,6 @@
"settings": {
"jsdoc": {
"preferredTypes": {
"object": "Object",
"key": "key",
"value": "value",

@ -0,0 +1,21 @@
const { logger : { error } } = require('@utilities');
const Patcher = require('../patcher');
const _runPatches = (moduleId, originalArgs, originalReturn, _this) => {
const module = 'Module';
const submodule = 'Patcher';
let finalReturn = originalReturn;
const patches = Patcher.patches.filter(i => i.module === moduleId && !i.pre);
patches.forEach(i => {
try {
finalReturn = i.method.call(_this, originalArgs, finalReturn);
} catch (e) {
error(module, submodule, null, `Failed to run patch '${i.id}'.`, e);
}
});
return finalReturn;
};
module.exports = _runPatches;

@ -0,0 +1,11 @@
const Patcher = require('../patcher');
const _runPrePatches = (moduleId, originalArgs, _this) => {
const patches = Patcher.patches.filter(i => i.module === moduleId && i.pre);
if (patches.length === 0) {
return originalArgs;
}
return Patcher._runPrePatchesRecursive(patches, originalArgs, _this);
};
module.exports = _runPrePatches;

@ -0,0 +1,26 @@
const { logger : { error } } = require('@utilities');
const Patcher = require('../patcher');
const _runPrePatchesRecursive = (patches, originalArgs, _this) => {
const module = 'Module';
const submodule = 'Patcher';
const patch = patches.pop();
let args = patch.method.call(_this, originalArgs);
if (args === false) {
return false;
}
if (!Array.isArray(args)) {
error(module, submodule, null, `Pre-patch ${patch.id} returned something invalid. Patch will be ignored.`);
args = originalArgs;
}
if (patches.length > 0) {
return Patcher._runPrePatchesRecursive(patches, args, _this);
}
return args;
};
module.exports = _runPrePatchesRecursive;

@ -1,78 +1,77 @@
/* eslint-disable consistent-this *//* eslint-disable no-undef */
/* eslint-disable consistent-this */
const { randomBytes } = require('crypto');
const Util = require('@util');
const { logger : { error } } = require('@utilities');
const _module = 'Module';
const _submodule = 'Patcher';
module.exports = class Patcher {
constructor () {
this.patches = [];
}
const patcher = {
patches: [],
static _runPatches (moduleId, originalArgs, originalReturn, _this) {
_runPatches (moduleId, originalArgs, originalReturn, _this) {
let finalReturn = originalReturn;
const patches = this.patches.filter(i => i.module === moduleId && !i.pre);
const patches = patcher.patches.filter(i => i.module === moduleId && !i.pre);
patches.forEach(i => {
try {
finalReturn = i.method.call(_this, originalArgs, finalReturn);
} catch (err) {
Util.Logger.error(_module, _submodule, null, `Failed to run patch '${i.id}'.`, err);
error(_module, `${_submodule}:_runPatches`, null, `Failed to run patch '${i.id}'.`, err);
}
});
return finalReturn;
}
},
static _runPrePatches (moduleId, originalArgs, _this) {
const patches = this.patches.filter(i => i.module === moduleId && i.pre);
_runPrePatches (moduleId, originalArgs, _this) {
const patches = patcher.patches.filter(i => i.module === moduleId && i.pre);
if (patches.length === 0) {
return originalArgs;
}
return this._runPrePatchesRecursive(patches, originalArgs, _this);
}
return patcher._runPrePatchesRecursive(patches, originalArgs, _this);
},
static _runPrePatchesRecursive (patches, originalArgs, _this) {
_runPrePatchesRecursive (patches, originalArgs, _this) {
const patch = patches.pop();
let args = patch.method.call(_this, originalArgs);
if (args === false) {
return false;
}
if (!Util.Array.isArray(args)) {
Util.Logger.error(_module, _submodule, null, `Pre-patch ${patch.id} returned something invalid. Patch will be ignored.`);
if (!Array.isArray(args)) {
error(_module, `${_submodule}:_runPrePatchesRecursive`, null, `Pre-patch ${patch.id} returned something invalid. Patch will be ignored.`);
args = originalArgs;
}
if (patches.length > 0) {
return this._runPrePatchesRecursive(patches, args, _this);
return patcher._runPrePatchesRecursive(patches, args, _this);
}
return args;
}
},
/**
* Checks if a function is patched.
* @param {string} patchId Patch to check
* Check if a function is patched
* @param {string} patchId The patch to check
*/
static isPatched (patchId) {
this.patches.some(i => i.id === patchId);
}
isPatched (patchId) {
patcher.patches.some(i => i.id === patchId);
},
/**
* Patches a function.
* Patches a function
* @param {string} patchId ID of the patch, used for uninjecting
* @param {Object} moduleToPatch Module we should inject into
* @param {object} moduleToPatch Module we should inject into
* @param {string} func Name of the function we're aiming at
* @param {Function} patch Function to patch
* @param {boolean} pre Whether the injection should run before original code or not
* @returns {void}
*/
static patch (patchId, moduleToPatch, func, patch, pre = false) {
patch (patchId, moduleToPatch, func, patch, pre = false) {
if (!moduleToPatch) {
return Util.Logger.error(_module, _submodule, null, `Tried to patch undefined (patch ID '${patchId}').`);
return error(_module, `${_submodule}:patch`, null, `Tried to patch undefined (patch ID '${patchId}').`);
}
if (this.patches.find(i => i.id === patchId)) {
return Util.Logger.error(_module, _submodule, null, `Patch ID '${patchId}' is already used!`);
if (patcher.patches.find(i => i.id === patchId)) {
return error(_module, `${_submodule}:patch`, null, `Patch ID '${patchId}' is already used!`);
}
if (!moduleToPatch.__vizalityPatchId || !moduleToPatch.__vizalityPatchId[func]) {
@ -82,10 +81,10 @@ module.exports = class Patcher {
moduleToPatch[`__vizalityOriginal_${func}`] = moduleToPatch[func]; // To allow easier debugging
const _oldMethod = moduleToPatch[func];
moduleToPatch[func] = function (...args) {
const finalArgs = this._runPrePatches(id, args, this);
if (finalArgs !== false && Util.Array.isArray(finalArgs)) {
const finalArgs = patcher._runPrePatches(id, args, this);
if (finalArgs !== false && Array.isArray(finalArgs)) {
const returned = _oldMethod ? _oldMethod.call(this, ...finalArgs) : void 0;
return this._runPatches(id, finalArgs, returned, this);
return patcher._runPatches(id, finalArgs, returned, this);
}
};
// Reassign displayName, defaultProps etc etc, not to mess with other plugins
@ -93,22 +92,24 @@ module.exports = class Patcher {
// Allow code search even after patching
moduleToPatch[func].toString = (...args) => _oldMethod.toString(...args);
this.patches[id] = [];
patcher.patches[id] = [];
}
this.patches.push({
patcher.patches.push({
module: moduleToPatch.__vizalityPatchId[func],
id: patchId,
method: patch,
pre
});
}
},
/**
* Removes a patch.
* @param {string} patchId Patch specified during injection
* Removes an patch
* @param {string} patchId The patch specified during injection
*/
static unpatch (patchId) {
this.patches = this.patches.filter(i => i.id !== patchId);
unpatch (patchId) {
patcher.patches = patcher.patches.filter(i => i.id !== patchId);
}
};
module.exports = patcher;

@ -0,0 +1,11 @@
const Patcher = require('../patcher');
/**
* Check if a function is patched
* @param {String} patchId The patch to check
*/
const isPatched = (patchId) => {
Patcher.patches.some(i => i.id === patchId);
};
module.exports = isPatched;

@ -1,279 +0,0 @@
/**
* @copyright MIT License - (c) 2018 Zachary Rauen
* @see {@link https://github.com/rauenzi/BDPluginLibrary/blob/master/src/modules/patcher.js}
*/
/**
* Patcher that can patch other functions allowing you to run code before, after or
* instead of the original function. Can also alter arguments and return values. Most of
* the code here is sourced from @see {@link https://github.com/rauenzi/BDPluginLibrary}
* @module Patcher
* @namespace Patcher
* @version 0.0.2
*/
const { Webpack, Utilities } = require('@modules');
module.exports = class Patcher {
// Use window._patches instead of local variables in case something tries to whack the lib
static get patches () { return window._patches || (window._patches = []); }
/**
* Returns all the patches done by a specific caller.
* @param {string} name Name of the patch caller
* @returns {}
*/
static getPatchesByCaller (name) {
if (!name) return [];
const patches = [];
for (const patch of this.patches) {
for (const childPatch of patch.children) {
if (childPatch.caller === name) patches.push(childPatch);
}
}
return patches;
}
/**
* Unpatches all patches passed, or when a string is passed unpatches all
* patches done by that specific caller.
* @param {Array|string} patches Either an array of patches to unpatch or a caller name
*/
static unpatchAll (patches) {
if (typeof patches === 'string') patches = this.getPatchesByCaller(patches);
for (const patch of patches) {
patch.unpatch();
}
}
static resolveModule (module) {
if (!module || typeof (module) === 'function' || (typeof (module) === 'object' && !Array.isArray(module))) return module;
if (Array.isArray(module)) return Webpack.findByUniqueProperties(module);
return null;
}
static makeOverride (patch) {
return function () {
let returnValue;
if (!patch.children || !patch.children.length) return patch.originalFunction.apply(this, arguments);
for (const superPatch of patch.children.filter(c => c.type === 'before')) {
try {
superPatch.callback(this, arguments);
}
catch (err) {
// @todo Fix
Utilities.logger.error('Patcher', `Could not fire before callback of ${patch.functionName} for ${superPatch.caller}`, err);
}
}
const insteads = patch.children.filter(c => c.type === 'instead');
if (!insteads.length) {returnValue = patch.originalFunction.apply(this, arguments);}
else {
for (const insteadPatch of insteads) {
try {
const tempReturn = insteadPatch.callback(this, arguments, patch.originalFunction.bind(this));
if (typeof (tempReturn) !== 'undefined') returnValue = tempReturn;
}
catch (err) {
Utilities.logger.error('Patcher', `Could not fire instead callback of ${patch.functionName} for ${insteadPatch.caller}`, err);
}
}
}
for (const slavePatch of patch.children.filter(c => c.type === 'after')) {
try {
const tempReturn = slavePatch.callback(this, arguments, returnValue);
if (typeof (tempReturn) !== 'undefined') returnValue = tempReturn;
}
catch (err) {
Utilities.logger.error('Patcher', `Could not fire after callback of ${patch.functionName} for ${slavePatch.caller}`, err);
}
}
return returnValue;
};
}
static rePatch (patch) {
patch.proxyFunction = patch.module[patch.functionName] = this.makeOverride(patch);
}
static makePatch (module, functionName, name) {
const patch = {
name,
module,
functionName,
originalFunction: module[functionName],
proxyFunction: null,
revert: () => { // Calling revert will destroy any patches added to the same module after this
patch.module[patch.functionName] = patch.originalFunction;
patch.proxyFunction = null;
patch.children = [];
},
counter: 0,
children: []
};
patch.proxyFunction = module[functionName] = this.makeOverride(patch);
Object.assign(module[functionName], patch.originalFunction);
module[functionName].__originalFunction = patch.originalFunction;
module[functionName].toString = () => patch.originalFunction.toString();
this.patches.push(patch);
return patch;
}
/**
* Function with no arguments and no return value that may be called to revert changes
* made by {@link module:Patcher}, restoring (unpatching) original method.
* @callback module:Patcher~unpatch
*/
/**
* A callback that modifies method logic. This callback is called on each call of the
* original method and is provided all data about original call. Any of the data can be
* modified if necessary, but do so wisely.
*
* The third argument for the callback will be `undefined` for `before` patches,
* `originalFunction` for `instead` patches, and `returnValue` for `after` patches.
* @callback module:Patcher~patchCallback
* @param {object} thisObject - `this` in the context of the original function.
* @param {arguments} arguments - The original arguments of the original function.
* @param {(function|*)} extraValue - For `instead` patches, this is the original function
* from the module. For `after` patches, this is the return value of the function.
* @return {*} Makes sense only when using an `instead` or `after` patch. If something
* other than `undefined` is returned, the returned value replaces the value of `returnValue`.
* If used for `before` the return value is ignored.
*/
/**
* This method patches onto another function, allowing your code to run beforehand.
* Using this, you are also able to modify the incoming arguments before the original
* method is run.
* @param {string} caller Name of the caller of the patch function. Using this you can
* undo all patches with the same name using {@link module:Patcher.unpatchAll}. Use `""`
* if you don't care.
* @param {object} moduleToPatch Object with the function to be patched. Can also patch
* an object's prototype.
* @param {string} functionName Name of the method to be patched
* @param {module:Patcher~patchCallback} callback Function to run before the original method
* @param {object} options Object used to pass additional options.
* @param {string} [options.displayName] You can provide meaningful name for class/object
* provided in `what` param for logging purposes. By default, this function will try to
* determine name automatically.
* @param {boolean} [options.forcePatch=true] Set to `true` to patch even if the function
* doesnt exist. (Adds noop function in place).
* @returns {module:Patcher~unpatch} Function with no arguments and no return value that
* should be called to cancel (unpatch) this patch. You should save and run it when your
* plugin is stopped.
*/
static before (caller, moduleToPatch, functionName, callback, options = {}) {
return this.pushChildPatch(caller, moduleToPatch, functionName, callback, Object.assign(options, { type: 'before' }));
}
/**
* This method patches onto another function, allowing your code to run after.
* Using this, you are also able to modify the return value, using the return of your
* code instead.
* @param {string} caller Name of the caller of the patch function. Using this you can
* undo all patches with the same name using {@link module:Patcher.unpatchAll}. Use `""`
* if you don't care.
* @param {object} moduleToPatch Object with the function to be patched. Can also patch
* an object's prototype.
* @param {string} functionName Name of the method to be patched
* @param {module:Patcher~patchCallback} callback Function to run instead of the original method
* @param {object} options Object used to pass additional options.
* @param {string} [options.displayName] You can provide meaningful name for
* class/object provided in `what` param for logging purposes. By default, this function
* will try to determine name automatically.
* @param {boolean} [options.forcePatch=true] Set to `true` to patch even if the function
* doesnt exist. (Adds noop function in place).
* @returns {module:Patcher~unpatch} Function with no arguments and no return value that
* should be called to cancel (unpatch) this patch. You should save and run it when your
* plugin is stopped.
*/
static after (caller, moduleToPatch, functionName, callback, options = {}) {
return this.pushChildPatch(caller, moduleToPatch, functionName, callback, Object.assign(options, { type: 'after' }));
}
/**
* This method patches onto another function, allowing your code to run instead.
* Using this, you are also able to modify the return value, using the return of your
* code instead.
* @param {string} caller Name of the caller of the patch function. Using this you can
* undo all patches with the same name using {@link module:Patcher.unpatchAll}. Use `""`
* if you don't care.
* @param {object} moduleToPatch Object with the function to be patched. Can also patch
* an object's prototype.
* @param {string} functionName Name of the method to be patched
* @param {module:Patcher~patchCallback} callback Function to run after theoriginal method
* @param {object} options Object used to pass additional options.
* @param {string} [options.displayName] You can provide meaningful name for class/object
* provided in `what` param for logging purposes. By default, this function will try to
* determine name automatically.
* @param {boolean} [options.forcePatch=true] Set to `true` to patch even if the function
* doesnt exist. (Adds noop function in place).
* @returns {module:Patcher~unpatch} Function with no arguments and no return value that
* should be called to cancel (unpatch) this patch. You should save and run it when your
* plugin is stopped.
*/
static instead (caller, moduleToPatch, functionName, callback, options = {}) {
return this.pushChildPatch(caller, moduleToPatch, functionName, callback, Object.assign(options, { type: 'instead' }));
}
/**
* This method patches onto another function, allowing your code to run before, instead
* or after the original function.
* Using this you are able to modify the incoming arguments before the original function
* is run as well as the return
* value before the original function actually returns.
* @param {string} caller Name of the caller of the patch function. Using this you can
* undo all patches with the same name using {@link module:Patcher.unpatchAll}. Use `""`
* if you don't care.
* @param {object} moduleToPatch Object with the function to be patched. Can also patch
* an object's prototype.
* @param {string} functionName Name of the method to be patched
* @param {module:Patcher~patchCallback} callback Function to run after the original method
* @param {object} options Object used to pass additional options.
* @param {string} [options.type=after] Determines whether to run the function `before`,
* `instead`, or `after` the original.
* @param {string} [options.displayName] You can provide meaningful name for class/object
* provided in `what` param for logging purposes. By default, this function will try to
* determine name automatically.
* @param {boolean} [options.forcePatch=true] Set to `true` to patch even if the function
* doesnt exist. (Adds noop function in place).
* @returns {module:Patcher~unpatch} Function with no arguments and no return value that
* should be called to cancel (unpatch) this patch. You should save and run it when your
* plugin is stopped.
*/
static pushChildPatch (caller, moduleToPatch, functionName, callback, options = {}) {
const { type = 'after', forcePatch = true } = options;
const module = this.resolveModule(moduleToPatch);
if (!module) return null;
if (!module[functionName] && forcePatch) module[functionName] = function () {};
if (!(module[functionName] instanceof Function)) return null;
if (typeof moduleToPatch === 'string') options.displayName = moduleToPatch;
const displayName = options.displayName || module.displayName || module.name || module.constructor.displayName || module.constructor.name;
const patchId = `${displayName}.${functionName}`;
const patch = this.patches.find(p => p.module === module && p.functionName === functionName) || this.makePatch(module, functionName, patchId);
if (!patch.proxyFunction) this.rePatch(patch);
const child = {
caller,
type,
id: patch.counter,
callback,
unpatch: () => {
patch.children.splice(patch.children.findIndex(cpatch => cpatch.id === child.id && cpatch.type === type), 1);
if (patch.children.length <= 0) {
const patchNum = this.patches.findIndex(p => p.module === module && p.functionName === functionName);
if (patchNum < 0) return;
this.patches[patchNum].revert();
this.patches.splice(patchNum, 1);
}
}
};
patch.children.push(child);
patch.counter++;
return child.unpatch;
}
};

@ -0,0 +1,56 @@
const { logger : { error } } = require('@utilities');
const { randomBytes } = require('crypto');
const Patcher = require('../patcher');
/**
* Patches a function
* @param {String} patchId ID of the patch, used for uninjecting
* @param {object} moduleToPatch Module we should inject into
* @param {String} func Name of the function we're aiming at
* @param {function} patch Function to patch
* @param {Boolean} pre Whether the injection should run before original code or not
*/
const patch = (patchId, moduleToPatch, func, patch, pre = false) => {
const module = 'Module';
const submodule = 'Patcher:patch';
if (!moduleToPatch) {
return error(module, submodule, null, `Tried to patch undefined (patch ID '${patchId}').`);
}
if (Patcher.patches.find(i => i.id === patchId)) {
return error(module, submodule, null, `Patch ID '${patchId}' is already used!`);
}
if (!moduleToPatch.__vizalityPatchId || !moduleToPatch.__vizalityPatchId[func]) {
// 1st patch
const id = randomBytes(16).toString('hex');
moduleToPatch.__vizalityPatchId = Object.assign((moduleToPatch.__vizalityPatchId || {}), { [func]: id });
moduleToPatch[`__vizalityOriginal_${func}`] = moduleToPatch[func]; // To allow easier debugging
const _oldMethod = moduleToPatch[func];
moduleToPatch[func] = function (...args) {
const finalArgs = Patcher._runPrePatches(id, args, this);
if (finalArgs !== false && Array.isArray(finalArgs)) {
const returned = _oldMethod ? _oldMethod.call(this, ...finalArgs) : void 0;
return Patcher._runPatches(id, finalArgs, returned, this);
}
};
// Reassign displayName, defaultProps etc etc, not to mess with other plugins
Object.assign(moduleToPatch[func], _oldMethod);
// Allow code search even after patching
moduleToPatch[func].toString = (...args) => _oldMethod.toString(...args);
Patcher.patches[id] = [];
}
Patcher.patches.push({
module: moduleToPatch.__vizalityPatchId[func],
id: patchId,
method: patch,
pre
});
};
module.exports = patch;

@ -0,0 +1,11 @@
const Patcher = require('../patcher');
/**
* Removes an patch
* @param {String} patchId The patch specified during injection
*/
const unpatch = (patchId) => {
Patcher.patches = Patcher.patches.filter(i => i.id !== patchId);
};
module.exports = unpatch;
Loading…
Cancel
Save