Take a different approach which requires less cursed code

This commit is contained in:
Nuckyz 2024-05-24 03:08:35 -03:00
parent b38ab066d9
commit 9bbec66ea5
No known key found for this signature in database
GPG key ID: 440BF8296E1C4AD9

View file

@ -6,7 +6,6 @@
import { Settings } from "@api/Settings"; import { Settings } from "@api/Settings";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { UNCONFIGURABLE_PROPERTIES } from "@utils/misc";
import { canonicalizeMatch, canonicalizeReplacement } from "@utils/patches"; import { canonicalizeMatch, canonicalizeReplacement } from "@utils/patches";
import { PatchReplacement } from "@utils/types"; import { PatchReplacement } from "@utils/types";
@ -17,34 +16,36 @@ import { _initWebpack, beforeInitListeners, factoryListeners, ModuleFactory, mod
const logger = new Logger("WebpackInterceptor", "#8caaee"); const logger = new Logger("WebpackInterceptor", "#8caaee");
const initCallbackRegex = canonicalizeMatch(/{return \i\(".+?"\)}/); const initCallbackRegex = canonicalizeMatch(/{return \i\(".+?"\)}/);
const allProxiedModules = new Set<WebpackRequire["m"]>(); /** A set with all the module factories objects */
const allModuleFactories = new Set<WebpackRequire["m"]>();
const modulesProxyHandler: ProxyHandler<WebpackRequire["m"]> = { function defineModuleFactoryGetter(modules: WebpackRequire["m"], id: PropertyKey, factory: ModuleFactory) {
...Object.fromEntries(Object.getOwnPropertyNames(Reflect).map(propName => Object.defineProperty(modules, id, {
[propName, (...args: any[]) => Reflect[propName](...args)] get: () => {
)), // $$vencordOriginal means the factory is already patched
get: (target, p) => {
const propValue = Reflect.get(target, p, target);
// If the property is not a number, we are not dealing with a module factory
// $$vencordOriginal means the factory is already patched, $$vencordRequired means it has already been required
// and replaced with the original
// @ts-ignore // @ts-ignore
if (propValue == null || Number.isNaN(Number(p)) || propValue.$$vencordOriginal != null || propValue.$$vencordRequired === true) { if (factory.$$vencordOriginal != null) {
return propValue; return factory;
} }
// This patches factories if eagerPatches are disabled // This patches factories if eagerPatches are disabled
const patchedFactory = patchFactory(p, propValue); return (factory = patchFactory(id, factory));
Reflect.set(target, p, patchedFactory, target);
return patchedFactory;
}, },
set: (target, p, newValue) => { configurable: true,
// $$vencordRequired means we are resetting the factory to its original after being required enumerable: true
});
}
const moduleFactoriesHandler: ProxyHandler<WebpackRequire["m"]> = {
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 (!Settings.eagerPatches || newValue?.$$vencordRequired === true || Number.isNaN(Number(p))) { if (Number.isNaN(Number(p))) {
return Reflect.set(target, p, newValue, target); return Reflect.set(target, p, newValue, receiver);
}
if (!Settings.eagerPatches) {
defineModuleFactoryGetter(target, p, newValue);
return true;
} }
const existingFactory = Reflect.get(target, p, target); const existingFactory = Reflect.get(target, p, target);
@ -58,18 +59,16 @@ const modulesProxyHandler: ProxyHandler<WebpackRequire["m"]> = {
const patchedFactory = patchFactory(p, newValue); const patchedFactory = patchFactory(p, newValue);
// Modules are only patched once, so we need to set the patched factory on all the modules // Modules are only patched once, so we need to set the patched factory on all the modules
for (const proxiedModules of allProxiedModules) { for (const moduleFactories of allModuleFactories) {
Reflect.set(proxiedModules, p, patchedFactory, proxiedModules); Object.defineProperty(moduleFactories, p, {
value: patchedFactory,
configurable: true,
enumerable: true,
writable: true
});
} }
return true; return true;
},
ownKeys: target => {
const keys = Reflect.ownKeys(target);
for (const key of UNCONFIGURABLE_PROPERTIES) {
if (!keys.includes(key)) keys.push(key);
}
return keys;
} }
}; };
@ -154,43 +153,35 @@ Object.defineProperty(Function.prototype, "O", {
// 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
// We replace its prototype with our proxy, which is responsible for patching the module factories // We replace wrap it with our proxy, which is responsible for patching the module factories, or setting up getters for them
Object.defineProperty(Function.prototype, "m", { Object.defineProperty(Function.prototype, "m", {
configurable: true, configurable: true,
set(this: WebpackRequire, originalModules: WebpackRequire["m"]) { set(this: WebpackRequire, moduleFactories: WebpackRequire["m"]) {
// 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 one
const { stack } = new Error(); const { stack } = new Error();
if ((stack?.includes("discord.com") || stack?.includes("discordapp.com")) && !Array.isArray(originalModules)) { if ((stack?.includes("discord.com") || stack?.includes("discordapp.com")) && !Array.isArray(moduleFactories)) {
logger.info("Found Webpack module factory", stack.match(/\/assets\/(.+?\.js)/)?.[1] ?? ""); logger.info("Found Webpack module factory", stack.match(/\/assets\/(.+?\.js)/)?.[1] ?? "");
// The new object which will contain the factories for (const id in moduleFactories) {
const proxiedModules: WebpackRequire["m"] = Object.create(Object.getPrototypeOf(originalModules));
// @ts-ignore
proxiedModules[Symbol.toStringTag] = "ProxiedModules";
for (const id in originalModules) {
// 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) {
proxiedModules[id] = patchFactory(id, originalModules[id]); moduleFactories[id] = patchFactory(id, moduleFactories[id]);
} else { } else {
proxiedModules[id] = originalModules[id]; defineModuleFactoryGetter(moduleFactories, id, moduleFactories[id]);
}
} }
// Clear the original object so pre-populated factories are patched if eagerPatches are disabled allModuleFactories.add(moduleFactories);
delete originalModules[id];
}
allProxiedModules.add(proxiedModules);
// @ts-ignore // @ts-ignore
originalModules[Symbol.toStringTag] = "OriginalModules"; moduleFactories[Symbol.toStringTag] = "ModuleFactories";
Object.setPrototypeOf(originalModules, new Proxy(proxiedModules, modulesProxyHandler)); moduleFactories = new Proxy(moduleFactories, moduleFactoriesHandler);
} }
Object.defineProperty(this, "m", { Object.defineProperty(this, "m", {
value: originalModules, value: moduleFactories,
configurable: true, configurable: true,
enumerable: true, enumerable: true,
writable: true writable: true
@ -325,10 +316,13 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
} }
const patchedFactory: ModuleFactory = (module, exports, require) => { const patchedFactory: ModuleFactory = (module, exports, require) => {
// @ts-ignore for (const moduleFactories of allModuleFactories) {
originalFactory.$$vencordRequired = true; Object.defineProperty(moduleFactories, id, {
for (const proxiedModules of allProxiedModules) { value: originalFactory,
Reflect.set(proxiedModules, id, originalFactory, proxiedModules); configurable: true,
enumerable: true,
writable: true
});
} }
if (wreq == null && IS_DEV) { if (wreq == null && IS_DEV) {