Merge branch 'modules-proxy-patches' into immediate-finds-modules-proxy
This commit is contained in:
commit
9f1f2b15a4
|
@ -23,10 +23,10 @@ export * as Util from "./utils";
|
||||||
export * as QuickCss from "./utils/quickCss";
|
export * as QuickCss from "./utils/quickCss";
|
||||||
export * as Updater from "./utils/updater";
|
export * as Updater from "./utils/updater";
|
||||||
export * as Webpack from "./webpack";
|
export * as Webpack from "./webpack";
|
||||||
|
export * as WebpackPatcher from "./webpack/patchWebpack";
|
||||||
export { PlainSettings, Settings };
|
export { PlainSettings, Settings };
|
||||||
|
|
||||||
import "./utils/quickCss";
|
import "./utils/quickCss";
|
||||||
import "./webpack/patchWebpack";
|
|
||||||
|
|
||||||
import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab";
|
import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab";
|
||||||
import { StartAt } from "@utils/types";
|
import { StartAt } from "@utils/types";
|
||||||
|
|
|
@ -100,7 +100,7 @@ export function pluralise(amount: number, singular: string, plural = singular +
|
||||||
return amount === 1 ? `${amount} ${singular}` : `${amount} ${plural}`;
|
return amount === 1 ? `${amount} ${singular}` : `${amount} ${plural}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Unconfigurable properties for proxies */
|
/** Proxies which have an internal target but use a function as the main target require these properties to be unconfigurable */
|
||||||
export const UNCONFIGURABLE_PROPERTIES = ["arguments", "caller", "prototype"];
|
export const UNCONFIGURABLE_PROPERTIES = ["arguments", "caller", "prototype"];
|
||||||
|
|
||||||
export function interpolateIfDefined(strings: TemplateStringsArray, ...args: any[]) {
|
export function interpolateIfDefined(strings: TemplateStringsArray, ...args: any[]) {
|
||||||
|
|
|
@ -14,6 +14,8 @@ import { traceFunction } from "../debug/Tracer";
|
||||||
import { patches } from "../plugins";
|
import { patches } from "../plugins";
|
||||||
import { _initWebpack, factoryListeners, ModuleFactory, moduleListeners, waitForSubscriptions, WebpackRequire, wreq } from ".";
|
import { _initWebpack, factoryListeners, ModuleFactory, moduleListeners, waitForSubscriptions, WebpackRequire, wreq } from ".";
|
||||||
|
|
||||||
|
type AnyWebpackRequire = Partial<WebpackRequire> & Pick<WebpackRequire, "m">;
|
||||||
|
|
||||||
type PatchedModuleFactory = ModuleFactory & {
|
type PatchedModuleFactory = ModuleFactory & {
|
||||||
$$vencordOriginal?: ModuleFactory;
|
$$vencordOriginal?: ModuleFactory;
|
||||||
};
|
};
|
||||||
|
@ -22,31 +24,50 @@ type PatchedModuleFactories = Record<PropertyKey, PatchedModuleFactory>;
|
||||||
|
|
||||||
const logger = new Logger("WebpackInterceptor", "#8caaee");
|
const logger = new Logger("WebpackInterceptor", "#8caaee");
|
||||||
|
|
||||||
/** A set with all the module factories objects */
|
/** A set with all the Webpack instances */
|
||||||
const allModuleFactories = new Set<PatchedModuleFactories>();
|
export const allWebpackInstances = new Set<AnyWebpackRequire>();
|
||||||
/** Whether we tried to fallback to factory WebpackRequire, or disabled patches */
|
/** Whether we tried to fallback to factory WebpackRequire, or disabled patches */
|
||||||
let wreqFallbackApplied = false;
|
let wreqFallbackApplied = false;
|
||||||
|
|
||||||
|
type Define = typeof Reflect.defineProperty;
|
||||||
|
const define: Define = (target, p, attributes) => {
|
||||||
|
if (Object.hasOwn(attributes, "value")) {
|
||||||
|
attributes.writable = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Reflect.defineProperty(target, p, {
|
||||||
|
configurable: true,
|
||||||
|
enumerable: true,
|
||||||
|
...attributes
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// wreq.m is the Webpack object containing module factories.
|
// wreq.m is the Webpack object containing module factories.
|
||||||
// We wrap it with our proxy, which is responsible for patching the module factories when they are set, or definining getters for the patched versions.
|
// We wrap it with our proxy, which is responsible for patching the module factories when they are set, or definining getters for the patched versions.
|
||||||
// If this is the main Webpack, we also set up the internal references to WebpackRequire.
|
// If this is the main Webpack, we also set up the internal references to WebpackRequire.
|
||||||
// wreq.m is pre-populated with module factories, and is also populated via webpackGlobal.push
|
// wreq.m is pre-populated with module factories, and is also populated via webpackGlobal.push
|
||||||
// The sentry module also has their own Webpack with a pre-populated wreq.m, so this also patches the sentry module factories.
|
// The sentry module also has their own Webpack with a pre-populated wreq.m, so this also patches the sentry module factories.
|
||||||
Reflect.defineProperty(Function.prototype, "m", {
|
define(Function.prototype, "m", {
|
||||||
configurable: true,
|
enumerable: false,
|
||||||
|
|
||||||
set(this: WebpackRequire, moduleFactories: PatchedModuleFactories) {
|
set(this: WebpackRequire, moduleFactories: PatchedModuleFactories) {
|
||||||
// When using React DevTools or other extensions, we may also catch their Webpack here.
|
// When using React DevTools or other extensions, we may also catch their Webpack here.
|
||||||
// This ensures we actually got the right ones.
|
// This ensures we actually got the right ones.
|
||||||
const { stack } = new Error();
|
const { stack } = new Error();
|
||||||
if ((stack?.includes("discord.com") || stack?.includes("discordapp.com")) && !Array.isArray(moduleFactories)) {
|
if (!(stack?.includes("discord.com") || stack?.includes("discordapp.com")) || Array.isArray(moduleFactories)) {
|
||||||
const fileName = stack.match(/\/assets\/(.+?\.js)/)?.[1];
|
define(this, "m", { value: moduleFactories });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileName = stack?.match(/\/assets\/(.+?\.js)/)?.[1];
|
||||||
logger.info("Found Webpack module factories" + interpolateIfDefined` in ${fileName}`);
|
logger.info("Found Webpack module factories" + interpolateIfDefined` in ${fileName}`);
|
||||||
|
|
||||||
|
allWebpackInstances.add(this);
|
||||||
|
|
||||||
// Define a setter for the bundlePath property of WebpackRequire. Only the main Webpack has this property.
|
// Define a setter for the bundlePath property of WebpackRequire. Only the main Webpack has this property.
|
||||||
// So if the setter is called, this means we can initialize the internal references to WebpackRequire.
|
// So if the setter is called, this means we can initialize the internal references to WebpackRequire.
|
||||||
Reflect.defineProperty(this, "p", {
|
define(this, "p", {
|
||||||
configurable: true,
|
enumerable: false,
|
||||||
|
|
||||||
set(this: WebpackRequire, bundlePath: WebpackRequire["p"]) {
|
set(this: WebpackRequire, bundlePath: WebpackRequire["p"]) {
|
||||||
if (bundlePath !== "/assets/") return;
|
if (bundlePath !== "/assets/") return;
|
||||||
|
@ -55,47 +76,31 @@ Reflect.defineProperty(Function.prototype, "m", {
|
||||||
_initWebpack(this);
|
_initWebpack(this);
|
||||||
clearTimeout(setterTimeout);
|
clearTimeout(setterTimeout);
|
||||||
|
|
||||||
Reflect.defineProperty(this, "p", {
|
define(this, "p", { value: bundlePath });
|
||||||
value: bundlePath,
|
|
||||||
configurable: true,
|
|
||||||
enumerable: true,
|
|
||||||
writable: true
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// setImmediate to clear this property setter if this is not the main Webpack.
|
// setImmediate to clear this property setter if this is not the main Webpack.
|
||||||
// If this is the main Webpack, wreq.m will always be set before the timeout runs.
|
// If this is the main Webpack, wreq.m will always be set before the timeout runs.
|
||||||
const setterTimeout = setTimeout(() => Reflect.deleteProperty(this, "p"), 0);
|
const setterTimeout = setTimeout(() => Reflect.deleteProperty(this, "p"), 0);
|
||||||
|
|
||||||
// This needs to be added before the loop below
|
define(moduleFactories, Symbol.toStringTag, {
|
||||||
allModuleFactories.add(moduleFactories);
|
value: "ModuleFactories",
|
||||||
|
enumerable: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// The proxy responsible for patching the module factories when they are set, or definining getters for the patched versions
|
||||||
|
const proxiedModuleFactories = new Proxy(moduleFactories, moduleFactoriesHandler);
|
||||||
|
/*
|
||||||
|
If Discord ever decides to set module factories using the variable of the modules object directly, instead of wreq.m, switch the proxy to the prototype
|
||||||
|
Reflect.setPrototypeOf(moduleFactories, new Proxy(moduleFactories, moduleFactoriesHandler));
|
||||||
|
*/
|
||||||
|
|
||||||
|
define(this, "m", { value: proxiedModuleFactories });
|
||||||
|
|
||||||
// Patch the pre-populated factories
|
// Patch the pre-populated factories
|
||||||
for (const id in moduleFactories) {
|
for (const id in moduleFactories) {
|
||||||
defineModulesFactoryGetter(id, Settings.eagerPatches ? patchFactory(id, moduleFactories[id]) : moduleFactories[id]);
|
defineModulesFactoryGetter(id, Settings.eagerPatches ? patchFactory(id, moduleFactories[id]) : moduleFactories[id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Reflect.defineProperty(moduleFactories, Symbol.toStringTag, {
|
|
||||||
value: "ModuleFactories",
|
|
||||||
configurable: true,
|
|
||||||
writable: true,
|
|
||||||
enumerable: false
|
|
||||||
});
|
|
||||||
|
|
||||||
// The proxy responsible for patching the module factories when they are set, or definining getters for the patched versions
|
|
||||||
moduleFactories = new Proxy(moduleFactories, moduleFactoriesHandler);
|
|
||||||
/*
|
|
||||||
If Discord ever decides to set module factories using the variable of the modules object directly, instead of wreq.m, switch the proxy to the prototype
|
|
||||||
Reflect.setPrototypeOf(moduleFactories, new Proxy(moduleFactories, moduleFactoriesHandler));
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
Reflect.defineProperty(this, "m", {
|
|
||||||
value: moduleFactories,
|
|
||||||
configurable: true,
|
|
||||||
enumerable: true,
|
|
||||||
writable: true
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -111,11 +116,8 @@ Reflect.defineProperty(Function.prototype, "m", {
|
||||||
function defineModulesFactoryGetter(id: PropertyKey, factory: PatchedModuleFactory) {
|
function defineModulesFactoryGetter(id: PropertyKey, factory: PatchedModuleFactory) {
|
||||||
// Define the getter in all the module factories objects. Patches are only executed once, so make sure all module factories object
|
// Define the getter in all the module factories objects. Patches are only executed once, so make sure all module factories object
|
||||||
// have the patched version
|
// have the patched version
|
||||||
for (const moduleFactories of allModuleFactories) {
|
for (const wreq of allWebpackInstances) {
|
||||||
Reflect.defineProperty(moduleFactories, id, {
|
define(wreq.m, id, {
|
||||||
configurable: true,
|
|
||||||
enumerable: true,
|
|
||||||
|
|
||||||
get() {
|
get() {
|
||||||
// $$vencordOriginal means the factory is already patched
|
// $$vencordOriginal means the factory is already patched
|
||||||
if (factory.$$vencordOriginal != null) {
|
if (factory.$$vencordOriginal != null) {
|
||||||
|
@ -155,13 +157,7 @@ const moduleFactoriesHandler: ProxyHandler<PatchedModuleFactories> = {
|
||||||
set: (target, p, newValue, receiver) => {
|
set: (target, p, newValue, receiver) => {
|
||||||
// If the property is not a number, we are not dealing with a module factory
|
// If the property is not a number, we are not dealing with a module factory
|
||||||
if (Number.isNaN(Number(p))) {
|
if (Number.isNaN(Number(p))) {
|
||||||
Reflect.defineProperty(target, p, {
|
return define(target, p, { value: newValue });
|
||||||
value: newValue,
|
|
||||||
configurable: true,
|
|
||||||
enumerable: true,
|
|
||||||
writable: true
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingFactory = Reflect.get(target, p, receiver);
|
const existingFactory = Reflect.get(target, p, receiver);
|
||||||
|
@ -332,13 +328,8 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
|
||||||
PatchedFactory(...args: Parameters<ModuleFactory>) {
|
PatchedFactory(...args: Parameters<ModuleFactory>) {
|
||||||
// Restore the original factory in all the module factories objects,
|
// Restore the original factory in all the module factories objects,
|
||||||
// because we want to make sure the original factory is restored properly, no matter what is the Webpack instance
|
// because we want to make sure the original factory is restored properly, no matter what is the Webpack instance
|
||||||
for (const moduleFactories of allModuleFactories) {
|
for (const wreq of allWebpackInstances) {
|
||||||
Reflect.defineProperty(moduleFactories, id, {
|
define(wreq.m, id, { value: patchedFactory.$$vencordOriginal });
|
||||||
value: patchedFactory.$$vencordOriginal,
|
|
||||||
configurable: true,
|
|
||||||
enumerable: true,
|
|
||||||
writable: true
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line prefer-const
|
// eslint-disable-next-line prefer-const
|
||||||
|
@ -387,10 +378,8 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
|
||||||
// There are (at the time of writing) 11 modules exporting the window
|
// There are (at the time of writing) 11 modules exporting the window
|
||||||
// Make these non enumerable to improve webpack search performance
|
// Make these non enumerable to improve webpack search performance
|
||||||
if (exports === window && typeof require === "function" && require.c != null) {
|
if (exports === window && typeof require === "function" && require.c != null) {
|
||||||
Reflect.defineProperty(require.c, id, {
|
define(require.c, id, {
|
||||||
value: require.c[id],
|
value: require.c[id],
|
||||||
configurable: true,
|
|
||||||
writable: true,
|
|
||||||
enumerable: false
|
enumerable: false
|
||||||
});
|
});
|
||||||
return factoryReturn;
|
return factoryReturn;
|
||||||
|
|
Loading…
Reference in a new issue