Merge branch 'modules-proxy-patches' into immediate-finds-modules-proxy

This commit is contained in:
Nuckyz 2024-05-28 17:46:58 -03:00
commit 9f1f2b15a4
No known key found for this signature in database
GPG key ID: 440BF8296E1C4AD9
3 changed files with 71 additions and 82 deletions

View file

@ -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";

View file

@ -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[]) {

View file

@ -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;