Merge branch 'modules-proxy-patches' into immediate-finds-modules-proxy
This commit is contained in:
commit
ebae259f5d
|
@ -327,8 +327,51 @@ async function runtime(token: string) {
|
||||||
// Enable eagerPatches to make all patches apply regardless of the module being required
|
// Enable eagerPatches to make all patches apply regardless of the module being required
|
||||||
Vencord.Settings.eagerPatches = true;
|
Vencord.Settings.eagerPatches = true;
|
||||||
|
|
||||||
let wreq: typeof Vencord.Webpack.wreq;
|
// The main patch for starting the reporter chunk loading
|
||||||
|
Vencord.Plugins.patches.push({
|
||||||
|
plugin: "Vencord Reporter",
|
||||||
|
find: '"Could not find app-mount"',
|
||||||
|
replacement: [{
|
||||||
|
match: /(?<="use strict";)/,
|
||||||
|
replace: "Vencord.Webpack._initReporter();"
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
Vencord.Webpack.waitFor(
|
||||||
|
Vencord.Webpack.filters.byProps("loginToken"),
|
||||||
|
m => {
|
||||||
|
console.log("[PUP_DEBUG]", "Logging in with token...");
|
||||||
|
m.loginToken(token);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
Vencord.Webpack._initReporter = function () {
|
||||||
|
// initReporter is called in the patched entry point of Discord
|
||||||
|
// setImmediate to only start searching for lazy chunks after Discord initialized the app
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log("[PUP_DEBUG]", "Loading all chunks...");
|
||||||
|
|
||||||
|
Vencord.Webpack.factoryListeners.add(factory => {
|
||||||
|
// setImmediate to avoid blocking the factory patching execution while checking for lazy chunks
|
||||||
|
setTimeout(() => {
|
||||||
|
let isResolved = false;
|
||||||
|
searchAndLoadLazyChunks(String(factory)).then(() => isResolved = true);
|
||||||
|
|
||||||
|
chunksSearchPromises.push(() => isResolved);
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const factoryId in wreq.m) {
|
||||||
|
let isResolved = false;
|
||||||
|
searchAndLoadLazyChunks(String(wreq.m[factoryId])).then(() => isResolved = true);
|
||||||
|
|
||||||
|
chunksSearchPromises.push(() => isResolved);
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const wreq = Vencord.Util.proxyLazy(() => Vencord.Webpack.wreq);
|
||||||
const { canonicalizeMatch, Logger } = Vencord.Util;
|
const { canonicalizeMatch, Logger } = Vencord.Util;
|
||||||
|
|
||||||
const validChunks = new Set<string>();
|
const validChunks = new Set<string>();
|
||||||
|
@ -426,43 +469,7 @@ async function runtime(token: string) {
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vencord.Webpack.waitFor(
|
|
||||||
Vencord.Webpack.filters.byProps("loginToken"),
|
|
||||||
m => {
|
|
||||||
console.log("[PUP_DEBUG]", "Logging in with token...");
|
|
||||||
m.loginToken(token);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
Vencord.Webpack.beforeInitListeners.add(async webpackRequire => {
|
|
||||||
console.log("[PUP_DEBUG]", "Loading all chunks...");
|
|
||||||
|
|
||||||
wreq = webpackRequire;
|
|
||||||
|
|
||||||
Vencord.Webpack.factoryListeners.add(factory => {
|
|
||||||
// setImmediate to avoid blocking the factory patching execution while checking for lazy chunks
|
|
||||||
setTimeout(() => {
|
|
||||||
let isResolved = false;
|
|
||||||
searchAndLoadLazyChunks(String(factory)).then(() => isResolved = true);
|
|
||||||
|
|
||||||
chunksSearchPromises.push(() => isResolved);
|
|
||||||
}, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
// setImmediate to only search the initial factories after Discord initialized the app
|
|
||||||
// our beforeInitListeners are called before Discord initializes the app
|
|
||||||
setTimeout(() => {
|
|
||||||
for (const factoryId in wreq.m) {
|
|
||||||
let isResolved = false;
|
|
||||||
searchAndLoadLazyChunks(String(wreq.m[factoryId])).then(() => isResolved = true);
|
|
||||||
|
|
||||||
chunksSearchPromises.push(() => isResolved);
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
await chunksSearchingDone;
|
await chunksSearchingDone;
|
||||||
wreq = wreq!;
|
|
||||||
|
|
||||||
// Require deferred entry points
|
// Require deferred entry points
|
||||||
for (const deferredRequire of deferredRequires) {
|
for (const deferredRequire of deferredRequires) {
|
||||||
|
|
|
@ -6,24 +6,33 @@
|
||||||
|
|
||||||
import { Settings } from "@api/Settings";
|
import { Settings } from "@api/Settings";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { canonicalizeMatch, canonicalizeReplacement } from "@utils/patches";
|
import { canonicalizeReplacement } from "@utils/patches";
|
||||||
import { PatchReplacement } from "@utils/types";
|
import { PatchReplacement } from "@utils/types";
|
||||||
|
|
||||||
import { traceFunction } from "../debug/Tracer";
|
import { traceFunction } from "../debug/Tracer";
|
||||||
import { patches } from "../plugins";
|
import { patches } from "../plugins";
|
||||||
import { _initWebpack, beforeInitListeners, factoryListeners, ModuleFactory, moduleListeners, OnChunksLoaded, waitForSubscriptions, WebpackRequire, wreq } from ".";
|
import { _initWebpack, factoryListeners, ModuleFactory, moduleListeners, waitForSubscriptions, WebpackRequire, wreq } from ".";
|
||||||
|
|
||||||
|
type PatchedModuleFactory = ModuleFactory & {
|
||||||
|
$$vencordOriginal?: ModuleFactory;
|
||||||
|
};
|
||||||
|
|
||||||
|
type PatchedModuleFactories = Record<PropertyKey, PatchedModuleFactory> & {
|
||||||
|
[Symbol.toStringTag]?: "ModuleFactories";
|
||||||
|
};
|
||||||
|
|
||||||
const logger = new Logger("WebpackInterceptor", "#8caaee");
|
const logger = new Logger("WebpackInterceptor", "#8caaee");
|
||||||
const initCallbackRegex = canonicalizeMatch(/{return \i\(".+?"\)}/);
|
|
||||||
|
|
||||||
/** A set with all the module factories objects */
|
/** A set with all the module factories objects */
|
||||||
const allModuleFactories = new Set<WebpackRequire["m"]>();
|
const allModuleFactories = new Set<PatchedModuleFactories>();
|
||||||
|
|
||||||
function defineModuleFactoryGetter(modulesFactories: WebpackRequire["m"], id: PropertyKey, factory: ModuleFactory) {
|
function defineModuleFactoryGetter(modulesFactories: PatchedModuleFactories, id: PropertyKey, factory: PatchedModuleFactory) {
|
||||||
Object.defineProperty(modulesFactories, id, {
|
Object.defineProperty(modulesFactories, id, {
|
||||||
|
configurable: true,
|
||||||
|
enumerable: true,
|
||||||
|
|
||||||
get() {
|
get() {
|
||||||
// $$vencordOriginal means the factory is already patched
|
// $$vencordOriginal means the factory is already patched
|
||||||
// @ts-ignore
|
|
||||||
if (factory.$$vencordOriginal != null) {
|
if (factory.$$vencordOriginal != null) {
|
||||||
return factory;
|
return factory;
|
||||||
}
|
}
|
||||||
|
@ -32,20 +41,16 @@ function defineModuleFactoryGetter(modulesFactories: WebpackRequire["m"], id: Pr
|
||||||
return (factory = patchFactory(id, factory));
|
return (factory = patchFactory(id, factory));
|
||||||
},
|
},
|
||||||
set(v: ModuleFactory) {
|
set(v: ModuleFactory) {
|
||||||
// @ts-ignore
|
|
||||||
if (factory.$$vencordOriginal != null) {
|
if (factory.$$vencordOriginal != null) {
|
||||||
// @ts-ignore
|
|
||||||
factory.$$vencordOriginal = v;
|
factory.$$vencordOriginal = v;
|
||||||
} else {
|
} else {
|
||||||
factory = v;
|
factory = v;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
configurable: true,
|
|
||||||
enumerable: true
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const moduleFactoriesHandler: ProxyHandler<WebpackRequire["m"]> = {
|
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))) {
|
||||||
|
@ -66,9 +71,7 @@ const moduleFactoriesHandler: ProxyHandler<WebpackRequire["m"]> = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this factory is already patched
|
// Check if this factory is already patched
|
||||||
// @ts-ignore
|
|
||||||
if (existingFactory?.$$vencordOriginal != null) {
|
if (existingFactory?.$$vencordOriginal != null) {
|
||||||
// @ts-ignore
|
|
||||||
existingFactory.$$vencordOriginal = newValue;
|
existingFactory.$$vencordOriginal = newValue;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -89,84 +92,6 @@ const moduleFactoriesHandler: ProxyHandler<WebpackRequire["m"]> = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// wreq.O is the webpack onChunksLoaded function
|
|
||||||
// Discord uses it to await for all the chunks to be loaded before initializing the app
|
|
||||||
// We monkey patch it to also monkey patch the initialize app callback to get immediate access to the webpack require and run our listeners before doing it
|
|
||||||
Object.defineProperty(Function.prototype, "O", {
|
|
||||||
configurable: true,
|
|
||||||
|
|
||||||
set(this: WebpackRequire, onChunksLoaded: WebpackRequire["O"]) {
|
|
||||||
// When using react devtools or other extensions, or even when discord loads the sentry, we may also catch their webpack here.
|
|
||||||
// This ensures we actually got the right one
|
|
||||||
// this.e (wreq.e) is the method for loading a chunk, and only the main webpack has it
|
|
||||||
const { stack } = new Error();
|
|
||||||
if ((stack?.includes("discord.com") || stack?.includes("discordapp.com")) && String(this.e).includes("Promise.all")) {
|
|
||||||
logger.info("Found main WebpackRequire.onChunksLoaded");
|
|
||||||
|
|
||||||
delete (Function.prototype as any).O;
|
|
||||||
|
|
||||||
const originalOnChunksLoaded = onChunksLoaded;
|
|
||||||
onChunksLoaded = function (result, chunkIds, callback, priority) {
|
|
||||||
if (callback != null && initCallbackRegex.test(String(callback))) {
|
|
||||||
Object.defineProperty(this, "O", {
|
|
||||||
value: originalOnChunksLoaded,
|
|
||||||
configurable: true,
|
|
||||||
enumerable: true,
|
|
||||||
writable: true
|
|
||||||
});
|
|
||||||
|
|
||||||
const wreq = this;
|
|
||||||
|
|
||||||
const originalCallback = callback;
|
|
||||||
callback = function (this: unknown) {
|
|
||||||
logger.info("Patched initialize app callback invoked, initializing our internal references to WebpackRequire and running beforeInitListeners");
|
|
||||||
_initWebpack(wreq);
|
|
||||||
|
|
||||||
for (const beforeInitListener of beforeInitListeners) {
|
|
||||||
beforeInitListener(wreq);
|
|
||||||
}
|
|
||||||
|
|
||||||
originalCallback.apply(this, arguments as any);
|
|
||||||
};
|
|
||||||
|
|
||||||
callback.toString = originalCallback.toString.bind(originalCallback);
|
|
||||||
arguments[2] = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
originalOnChunksLoaded.apply(this, arguments as any);
|
|
||||||
} as WebpackRequire["O"];
|
|
||||||
|
|
||||||
onChunksLoaded.toString = originalOnChunksLoaded.toString.bind(originalOnChunksLoaded);
|
|
||||||
|
|
||||||
// Returns whether a chunk has been loaded
|
|
||||||
Object.defineProperty(onChunksLoaded, "j", {
|
|
||||||
configurable: true,
|
|
||||||
|
|
||||||
set(v: OnChunksLoaded["j"]) {
|
|
||||||
function setValue(target: any) {
|
|
||||||
Object.defineProperty(target, "j", {
|
|
||||||
value: v,
|
|
||||||
configurable: true,
|
|
||||||
enumerable: true,
|
|
||||||
writable: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setValue(onChunksLoaded);
|
|
||||||
setValue(originalOnChunksLoaded);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.defineProperty(this, "O", {
|
|
||||||
value: onChunksLoaded,
|
|
||||||
configurable: true,
|
|
||||||
enumerable: true,
|
|
||||||
writable: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// wreq.m is the webpack object containing module factories.
|
// wreq.m is the webpack object containing module factories.
|
||||||
// This is pre-populated with module factories, and is also populated via webpackGlobal.push
|
// This 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 module factories object, so this also targets that
|
// The sentry module also has their own webpack with a pre-populated module factories object, so this also targets that
|
||||||
|
@ -174,13 +99,35 @@ Object.defineProperty(Function.prototype, "O", {
|
||||||
Object.defineProperty(Function.prototype, "m", {
|
Object.defineProperty(Function.prototype, "m", {
|
||||||
configurable: true,
|
configurable: true,
|
||||||
|
|
||||||
set(this: WebpackRequire, moduleFactories: WebpackRequire["m"]) {
|
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 one
|
// 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)) {
|
||||||
logger.info("Found Webpack module factories", stack.match(/\/assets\/(.+?\.js)/)?.[1] ?? "");
|
logger.info("Found Webpack module factories", stack.match(/\/assets\/(.+?\.js)/)?.[1] ?? "");
|
||||||
|
|
||||||
|
// 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
|
||||||
|
const setterTimeout = setTimeout(() => delete (this as Partial<WebpackRequire>).p, 0);
|
||||||
|
Object.defineProperty(this, "p", {
|
||||||
|
configurable: true,
|
||||||
|
|
||||||
|
set(this: WebpackRequire, v: WebpackRequire["p"]) {
|
||||||
|
if (v !== "/assets/") return;
|
||||||
|
|
||||||
|
logger.info("Main Webpack found, initializing internal references to WebpackRequire ");
|
||||||
|
_initWebpack(this);
|
||||||
|
clearTimeout(setterTimeout);
|
||||||
|
|
||||||
|
Object.defineProperty(this, "p", {
|
||||||
|
value: v,
|
||||||
|
configurable: true,
|
||||||
|
enumerable: true,
|
||||||
|
writable: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
for (const id in moduleFactories) {
|
for (const id in moduleFactories) {
|
||||||
// If we have eagerPatches enabled we have to patch the pre-populated factories
|
// If we have eagerPatches enabled we have to patch the pre-populated factories
|
||||||
if (Settings.eagerPatches) {
|
if (Settings.eagerPatches) {
|
||||||
|
@ -192,7 +139,6 @@ Object.defineProperty(Function.prototype, "m", {
|
||||||
|
|
||||||
allModuleFactories.add(moduleFactories);
|
allModuleFactories.add(moduleFactories);
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
moduleFactories[Symbol.toStringTag] = "ModuleFactories";
|
moduleFactories[Symbol.toStringTag] = "ModuleFactories";
|
||||||
moduleFactories = new Proxy(moduleFactories, moduleFactoriesHandler);
|
moduleFactories = new Proxy(moduleFactories, moduleFactoriesHandler);
|
||||||
}
|
}
|
||||||
|
@ -332,10 +278,9 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
|
||||||
if (!patch.all) patches.splice(i--, 1);
|
if (!patch.all) patches.splice(i--, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const patchedFactory: ModuleFactory = (module, exports, require) => {
|
const patchedFactory: PatchedModuleFactory = (module, exports, require) => {
|
||||||
for (const moduleFactories of allModuleFactories) {
|
for (const moduleFactories of allModuleFactories) {
|
||||||
Object.defineProperty(moduleFactories, id, {
|
Object.defineProperty(moduleFactories, id, {
|
||||||
// @ts-ignore
|
|
||||||
value: patchedFactory.$$vencordOriginal,
|
value: patchedFactory.$$vencordOriginal,
|
||||||
configurable: true,
|
configurable: true,
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
|
@ -402,7 +347,6 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
|
||||||
};
|
};
|
||||||
|
|
||||||
patchedFactory.toString = originalFactory.toString.bind(originalFactory);
|
patchedFactory.toString = originalFactory.toString.bind(originalFactory);
|
||||||
// @ts-ignore
|
|
||||||
patchedFactory.$$vencordOriginal = originalFactory;
|
patchedFactory.$$vencordOriginal = originalFactory;
|
||||||
|
|
||||||
return patchedFactory;
|
return patchedFactory;
|
||||||
|
|
|
@ -83,7 +83,6 @@ export type ModCallbackFnWithId = (module: ModuleExports, id: PropertyKey) => vo
|
||||||
export const waitForSubscriptions = new Map<FilterFn, ModCallbackFn>();
|
export const waitForSubscriptions = new Map<FilterFn, ModCallbackFn>();
|
||||||
export const moduleListeners = new Set<ModCallbackFnWithId>();
|
export const moduleListeners = new Set<ModCallbackFnWithId>();
|
||||||
export const factoryListeners = new Set<(factory: ModuleFactory) => void>();
|
export const factoryListeners = new Set<(factory: ModuleFactory) => void>();
|
||||||
export const beforeInitListeners = new Set<(wreq: WebpackRequire) => void>();
|
|
||||||
|
|
||||||
export function _initWebpack(webpackRequire: WebpackRequire) {
|
export function _initWebpack(webpackRequire: WebpackRequire) {
|
||||||
wreq = webpackRequire;
|
wreq = webpackRequire;
|
||||||
|
|
Loading…
Reference in a new issue