From 2a77941dff67fa0825b729af91be9432286c03f6 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sun, 19 May 2024 22:49:58 -0300
Subject: [PATCH 001/125] Proxy modules object for patching
---
scripts/generateReport.ts | 6 +
src/utils/constants.ts | 1 -
src/utils/lazy.ts | 10 +-
src/utils/misc.tsx | 3 +
src/webpack/patchWebpack.ts | 431 ++++++++++++++++--------------------
5 files changed, 210 insertions(+), 241 deletions(-)
diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts
index 164e409df6..6462d91a06 100644
--- a/scripts/generateReport.ts
+++ b/scripts/generateReport.ts
@@ -465,6 +465,12 @@ async function runtime(token: string) {
}
}));
+ // Call the getter for all the values in the modules object
+ // So modules that were not required get patched by our proxy
+ for (const id in wreq!.m) {
+ wreq!.m[id];
+ }
+
console.log("[PUP_DEBUG]", "Finished loading all chunks!");
for (const patch of Vencord.Plugins.patches) {
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index 44d13b54cf..01125c1af1 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -16,7 +16,6 @@
* along with this program. If not, see .
*/
-export const WEBPACK_CHUNK = "webpackChunkdiscord_app";
export const REACT_GLOBAL = "Vencord.Webpack.Common.React";
export const SUPPORT_CHANNEL_ID = "1026515880080842772";
diff --git a/src/utils/lazy.ts b/src/utils/lazy.ts
index a61785df96..b05855fa09 100644
--- a/src/utils/lazy.ts
+++ b/src/utils/lazy.ts
@@ -16,6 +16,8 @@
* along with this program. If not, see .
*/
+import { UNCONFIGURABLE_PROPERTIES } from "./misc";
+
export function makeLazy(factory: () => T, attempts = 5): () => T {
let tries = 0;
let cache: T;
@@ -29,10 +31,6 @@ export function makeLazy(factory: () => T, attempts = 5): () => T {
};
}
-// Proxies demand that these properties be unmodified, so proxyLazy
-// will always return the function default for them.
-const unconfigurable = ["arguments", "caller", "prototype"];
-
const handler: ProxyHandler = {};
const kGET = Symbol.for("vencord.lazy.get");
@@ -59,14 +57,14 @@ for (const method of [
handler.ownKeys = target => {
const v = target[kGET]();
const keys = Reflect.ownKeys(v);
- for (const key of unconfigurable) {
+ for (const key of UNCONFIGURABLE_PROPERTIES) {
if (!keys.includes(key)) keys.push(key);
}
return keys;
};
handler.getOwnPropertyDescriptor = (target, p) => {
- if (typeof p === "string" && unconfigurable.includes(p))
+ if (typeof p === "string" && UNCONFIGURABLE_PROPERTIES.includes(p))
return Reflect.getOwnPropertyDescriptor(target, p);
const descriptor = Reflect.getOwnPropertyDescriptor(target[kGET](), p);
diff --git a/src/utils/misc.tsx b/src/utils/misc.tsx
index fb08c93f6f..5bf2c23984 100644
--- a/src/utils/misc.tsx
+++ b/src/utils/misc.tsx
@@ -99,3 +99,6 @@ export const isPluginDev = (id: string) => Object.hasOwn(DevsById, id);
export function pluralise(amount: number, singular: string, plural = singular + "s") {
return amount === 1 ? `${amount} ${singular}` : `${amount} ${plural}`;
}
+
+/** Unconfigurable properties for proxies */
+export const UNCONFIGURABLE_PROPERTIES = ["arguments", "caller", "prototype"];
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index c7e424671f..9eaddb3d3b 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -1,23 +1,11 @@
/*
- * Vencord, a modification for Discord's desktop app
- * Copyright (c) 2022 Vendicated and contributors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
-*/
-
-import { WEBPACK_CHUNK } from "@utils/constants";
+ * Vencord, a Discord client mod
+ * Copyright (c) 2024 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
import { Logger } from "@utils/Logger";
+import { UNCONFIGURABLE_PROPERTIES } from "@utils/misc";
import { canonicalizeMatch, canonicalizeReplacement } from "@utils/patches";
import { PatchReplacement } from "@utils/types";
import { WebpackInstance } from "discord-types/other";
@@ -29,29 +17,30 @@ import { _initWebpack, beforeInitListeners, factoryListeners, moduleListeners, s
const logger = new Logger("WebpackInterceptor", "#8caaee");
const initCallbackRegex = canonicalizeMatch(/{return \i\(".+?"\)}/);
-let webpackChunk: any[];
-
-// Patch the window webpack chunk setter to monkey patch the push method before any chunks are pushed
-// This way we can patch the factory of everything being pushed to the modules array
-Object.defineProperty(window, WEBPACK_CHUNK, {
- configurable: true,
-
- get: () => webpackChunk,
- set: v => {
- if (v?.push) {
- if (!v.push.$$vencordOriginal) {
- logger.info(`Patching ${WEBPACK_CHUNK}.push`);
- patchPush(v);
-
- // @ts-ignore
- delete window[WEBPACK_CHUNK];
- window[WEBPACK_CHUNK] = v;
- }
+const modulesProxyhandler: ProxyHandler = {
+ ...Object.fromEntries(Object.getOwnPropertyNames(Reflect).map(propName =>
+ [propName, (target: any, ...args: any[]) => Reflect[propName](target, ...args)]
+ )),
+ get: (target, p: string) => {
+ const mod = Reflect.get(target, p);
+
+ // If the property is not a module id, return the value of it without trying to patch
+ if (mod == null || mod.$$vencordOriginal != null || Number.isNaN(Number(p))) return mod;
+
+ const patchedMod = patchFactory(p, mod);
+ Reflect.set(target, p, patchedMod);
+
+ return patchedMod;
+ },
+ set: (target, p, newValue) => Reflect.set(target, p, newValue),
+ ownKeys: target => {
+ const keys = Reflect.ownKeys(target);
+ for (const key of UNCONFIGURABLE_PROPERTIES) {
+ if (!keys.includes(key)) keys.push(key);
}
-
- webpackChunk = v;
+ return keys;
}
-});
+};
// wreq.O is the webpack onChunksLoaded function
// Discord uses it to await for all the chunks to be loaded before initializing the app
@@ -108,251 +97,225 @@ Object.defineProperty(Function.prototype, "O", {
}
});
-// wreq.m is the webpack module factory.
-// normally, this is populated via webpackGlobal.push, which we patch below.
-// However, Discord has their .m prepopulated.
-// Thus, we use this hack to immediately access their wreq.m and patch all already existing factories
-//
-// Update: Discord now has TWO webpack instances. Their normal one and sentry
-// Sentry does not push chunks to the global at all, so this same patch now also handles their sentry modules
+// wreq.m is the webpack object containing module factories.
+// This is pre-populated with modules, and is also populated via webpackGlobal.push
+// We replace its prototype with our proxy, which is responsible for returning patched module factories containing our patches
Object.defineProperty(Function.prototype, "m", {
configurable: true,
- set(v: any) {
+ set(originalModules: any) {
// When using react devtools or other extensions, we may also catch their webpack here.
// This ensures we actually got the right one
const { stack } = new Error();
if (stack?.includes("discord.com") || stack?.includes("discordapp.com")) {
logger.info("Found Webpack module factory", stack.match(/\/assets\/(.+?\.js)/)?.[1] ?? "");
- patchFactories(v);
+
+ // The new object which will contain the factories
+ const modules = Object.assign({}, originalModules);
+
+ // Clear the original object so pre-populated factories are patched
+ for (const propName in originalModules) {
+ delete originalModules[propName];
+ }
+
+ Object.setPrototypeOf(originalModules, new Proxy(modules, modulesProxyhandler));
}
Object.defineProperty(this, "m", {
- value: v,
+ value: originalModules,
configurable: true
});
}
});
-function patchPush(webpackGlobal: any) {
- function handlePush(chunk: any) {
+let webpackNotInitializedLogged = false;
+
+function patchFactory(id: string, mod: (module: any, exports: any, require: WebpackInstance) => void) {
+ for (const factoryListener of factoryListeners) {
try {
- patchFactories(chunk[1]);
+ factoryListener(mod);
} catch (err) {
- logger.error("Error in handlePush", err);
+ logger.error("Error in Webpack factory listener:\n", err, factoryListener);
}
-
- return handlePush.$$vencordOriginal.call(webpackGlobal, chunk);
}
- handlePush.$$vencordOriginal = webpackGlobal.push;
- handlePush.toString = handlePush.$$vencordOriginal.toString.bind(handlePush.$$vencordOriginal);
- // Webpack overwrites .push with its own push like so: `d.push = n.bind(null, d.push.bind(d));`
- // it wraps the old push (`d.push.bind(d)`). this old push is in this case our handlePush.
- // If we then repatched the new push, we would end up with recursive patching, which leads to our patches
- // being applied multiple times.
- // Thus, override bind to use the original push
- handlePush.bind = (...args: unknown[]) => handlePush.$$vencordOriginal.bind(...args);
-
- Object.defineProperty(webpackGlobal, "push", {
- configurable: true,
-
- get: () => handlePush,
- set(v) {
- handlePush.$$vencordOriginal = v;
- }
- });
-}
+ const originalMod = mod;
+ const patchedBy = new Set();
-let webpackNotInitializedLogged = false;
+ // Discords Webpack chunks for some ungodly reason contain random
+ // newlines. Cyn recommended this workaround and it seems to work fine,
+ // however this could potentially break code, so if anything goes weird,
+ // this is probably why.
+ // Additionally, `[actual newline]` is one less char than "\n", so if Discord
+ // ever targets newer browsers, the minifier could potentially use this trick and
+ // cause issues.
+ //
+ // 0, prefix is to turn it into an expression: 0,function(){} would be invalid syntax without the 0,
+ let code: string = "0," + mod.toString().replaceAll("\n", "");
-function patchFactories(factories: Record void>) {
- for (const id in factories) {
- let mod = factories[id];
+ for (let i = 0; i < patches.length; i++) {
+ const patch = patches[i];
+ if (patch.predicate && !patch.predicate()) continue;
- const originalMod = mod;
- const patchedBy = new Set();
+ const moduleMatches = typeof patch.find === "string"
+ ? code.includes(patch.find)
+ : patch.find.test(code);
- const factory = factories[id] = function (module: any, exports: any, require: WebpackInstance) {
- if (wreq == null && IS_DEV) {
- if (!webpackNotInitializedLogged) {
- webpackNotInitializedLogged = true;
- logger.error("WebpackRequire was not initialized, running modules without patches instead.");
- }
+ if (!moduleMatches) continue;
- return void originalMod(module, exports, require);
- }
+ patchedBy.add(patch.plugin);
- try {
- mod(module, exports, require);
- } catch (err) {
- // Just rethrow discord errors
- if (mod === originalMod) throw err;
+ const executePatch = traceFunction(`patch by ${patch.plugin}`, (match: string | RegExp, replace: string) => code.replace(match, replace));
+ const previousMod = mod;
+ const previousCode = code;
- logger.error("Error in patched module", err);
- return void originalMod(module, exports, require);
- }
+ // We change all patch.replacement to array in plugins/index
+ for (const replacement of patch.replacement as PatchReplacement[]) {
+ if (replacement.predicate && !replacement.predicate()) continue;
- exports = module.exports;
+ const lastMod = mod;
+ const lastCode = code;
- if (!exports) return;
+ canonicalizeReplacement(replacement, patch.plugin);
- // There are (at the time of writing) 11 modules exporting the window
- // Make these non enumerable to improve webpack search performance
- if (exports === window && require.c) {
- Object.defineProperty(require.c, id, {
- value: require.c[id],
- enumerable: false,
- configurable: true,
- writable: true
- });
- return;
- }
+ try {
+ const newCode = executePatch(replacement.match, replacement.replace as string);
+ if (newCode === code) {
+ if (!patch.noWarn) {
+ logger.warn(`Patch by ${patch.plugin} had no effect (Module id is ${id}): ${replacement.match}`);
+ if (IS_DEV) {
+ logger.debug("Function Source:\n", code);
+ }
+ }
- for (const callback of moduleListeners) {
- try {
- callback(exports, id);
- } catch (err) {
- logger.error("Error in Webpack module listener:\n", err, callback);
+ if (patch.group) {
+ logger.warn(`Undoing patch group ${patch.find} by ${patch.plugin} because replacement ${replacement.match} had no effect`);
+ mod = previousMod;
+ code = previousCode;
+ patchedBy.delete(patch.plugin);
+ break;
+ }
+
+ continue;
}
- }
- for (const [filter, callback] of subscriptions) {
- try {
- if (filter(exports)) {
- subscriptions.delete(filter);
- callback(exports, id);
- } else if (exports.default && filter(exports.default)) {
- subscriptions.delete(filter);
- callback(exports.default, id);
+ code = newCode;
+ mod = (0, eval)(`// Webpack Module ${id} - Patched by ${[...patchedBy].join(", ")}\n${newCode}\n//# sourceURL=WebpackModule${id}`);
+ } catch (err) {
+ logger.error(`Patch by ${patch.plugin} errored (Module id is ${id}): ${replacement.match}\n`, err);
+
+ if (IS_DEV) {
+ const changeSize = code.length - lastCode.length;
+ const match = lastCode.match(replacement.match)!;
+
+ // Use 200 surrounding characters of context
+ const start = Math.max(0, match.index! - 200);
+ const end = Math.min(lastCode.length, match.index! + match[0].length + 200);
+ // (changeSize may be negative)
+ const endPatched = end + changeSize;
+
+ const context = lastCode.slice(start, end);
+ const patchedContext = code.slice(start, endPatched);
+
+ // inline require to avoid including it in !IS_DEV builds
+ const diff = (require("diff") as typeof import("diff")).diffWordsWithSpace(context, patchedContext);
+ let fmt = "%c %s ";
+ const elements = [] as string[];
+ for (const d of diff) {
+ const color = d.removed
+ ? "red"
+ : d.added
+ ? "lime"
+ : "grey";
+ fmt += "%c%s";
+ elements.push("color:" + color, d.value);
}
- } catch (err) {
- logger.error("Error while firing callback for Webpack subscription:\n", err, filter, callback);
+
+ logger.errorCustomFmt(...Logger.makeTitle("white", "Before"), context);
+ logger.errorCustomFmt(...Logger.makeTitle("white", "After"), patchedContext);
+ const [titleFmt, ...titleElements] = Logger.makeTitle("white", "Diff");
+ logger.errorCustomFmt(titleFmt + fmt, ...titleElements, ...elements);
}
- }
- } as any as { toString: () => string, original: any, (...args: any[]): void; };
- factory.toString = originalMod.toString.bind(originalMod);
- factory.original = originalMod;
+ patchedBy.delete(patch.plugin);
- for (const factoryListener of factoryListeners) {
- try {
- factoryListener(originalMod);
- } catch (err) {
- logger.error("Error in Webpack factory listener:\n", err, factoryListener);
+ if (patch.group) {
+ logger.warn(`Undoing patch group ${patch.find} by ${patch.plugin} because replacement ${replacement.match} errored`);
+ mod = previousMod;
+ code = previousCode;
+ break;
+ }
+
+ mod = lastMod;
+ code = lastCode;
}
}
- // Discords Webpack chunks for some ungodly reason contain random
- // newlines. Cyn recommended this workaround and it seems to work fine,
- // however this could potentially break code, so if anything goes weird,
- // this is probably why.
- // Additionally, `[actual newline]` is one less char than "\n", so if Discord
- // ever targets newer browsers, the minifier could potentially use this trick and
- // cause issues.
- //
- // 0, prefix is to turn it into an expression: 0,function(){} would be invalid syntax without the 0,
- let code: string = "0," + mod.toString().replaceAll("\n", "");
-
- for (let i = 0; i < patches.length; i++) {
- const patch = patches[i];
- if (patch.predicate && !patch.predicate()) continue;
-
- const moduleMatches = typeof patch.find === "string"
- ? code.includes(patch.find)
- : patch.find.test(code);
-
- if (!moduleMatches) continue;
-
- patchedBy.add(patch.plugin);
-
- const executePatch = traceFunction(`patch by ${patch.plugin}`, (match: string | RegExp, replace: string) => code.replace(match, replace));
- const previousMod = mod;
- const previousCode = code;
-
- // We change all patch.replacement to array in plugins/index
- for (const replacement of patch.replacement as PatchReplacement[]) {
- if (replacement.predicate && !replacement.predicate()) continue;
-
- const lastMod = mod;
- const lastCode = code;
-
- canonicalizeReplacement(replacement, patch.plugin);
-
- try {
- const newCode = executePatch(replacement.match, replacement.replace as string);
- if (newCode === code) {
- if (!patch.noWarn) {
- logger.warn(`Patch by ${patch.plugin} had no effect (Module id is ${id}): ${replacement.match}`);
- if (IS_DEV) {
- logger.debug("Function Source:\n", code);
- }
- }
+ if (!patch.all) patches.splice(i--, 1);
+ }
- if (patch.group) {
- logger.warn(`Undoing patch group ${patch.find} by ${patch.plugin} because replacement ${replacement.match} had no effect`);
- mod = previousMod;
- code = previousCode;
- patchedBy.delete(patch.plugin);
- break;
- }
+ function patchedFactory(module: any, exports: any, require: WebpackInstance) {
+ if (wreq == null && IS_DEV) {
+ if (!webpackNotInitializedLogged) {
+ webpackNotInitializedLogged = true;
+ logger.error("WebpackRequire was not initialized, running modules without patches instead.");
+ }
- continue;
- }
+ return void originalMod(module, exports, require);
+ }
- code = newCode;
- mod = (0, eval)(`// Webpack Module ${id} - Patched by ${[...patchedBy].join(", ")}\n${newCode}\n//# sourceURL=WebpackModule${id}`);
- } catch (err) {
- logger.error(`Patch by ${patch.plugin} errored (Module id is ${id}): ${replacement.match}\n`, err);
-
- if (IS_DEV) {
- const changeSize = code.length - lastCode.length;
- const match = lastCode.match(replacement.match)!;
-
- // Use 200 surrounding characters of context
- const start = Math.max(0, match.index! - 200);
- const end = Math.min(lastCode.length, match.index! + match[0].length + 200);
- // (changeSize may be negative)
- const endPatched = end + changeSize;
-
- const context = lastCode.slice(start, end);
- const patchedContext = code.slice(start, endPatched);
-
- // inline require to avoid including it in !IS_DEV builds
- const diff = (require("diff") as typeof import("diff")).diffWordsWithSpace(context, patchedContext);
- let fmt = "%c %s ";
- const elements = [] as string[];
- for (const d of diff) {
- const color = d.removed
- ? "red"
- : d.added
- ? "lime"
- : "grey";
- fmt += "%c%s";
- elements.push("color:" + color, d.value);
- }
+ try {
+ mod(module, exports, require);
+ } catch (err) {
+ // Just rethrow Discord errors
+ if (mod === originalMod) throw err;
- logger.errorCustomFmt(...Logger.makeTitle("white", "Before"), context);
- logger.errorCustomFmt(...Logger.makeTitle("white", "After"), patchedContext);
- const [titleFmt, ...titleElements] = Logger.makeTitle("white", "Diff");
- logger.errorCustomFmt(titleFmt + fmt, ...titleElements, ...elements);
- }
+ logger.error("Error in patched module", err);
+ return void originalMod(module, exports, require);
+ }
- patchedBy.delete(patch.plugin);
+ // Webpack sometimes sets the value of module.exports directly, so assign exports to it to make sure we properly handle it
+ exports = module.exports;
+ if (exports == null) return;
+
+ // There are (at the time of writing) 11 modules exporting the window
+ // Make these non enumerable to improve webpack search performance
+ if (exports === window && require.c) {
+ Object.defineProperty(require.c, id, {
+ value: require.c[id],
+ configurable: true,
+ writable: true,
+ enumerable: false
+ });
+ return;
+ }
- if (patch.group) {
- logger.warn(`Undoing patch group ${patch.find} by ${patch.plugin} because replacement ${replacement.match} errored`);
- mod = previousMod;
- code = previousCode;
- break;
- }
+ for (const callback of moduleListeners) {
+ try {
+ callback(exports, id);
+ } catch (err) {
+ logger.error("Error in Webpack module listener:\n", err, callback);
+ }
+ }
- mod = lastMod;
- code = lastCode;
+ for (const [filter, callback] of subscriptions) {
+ try {
+ if (filter(exports)) {
+ subscriptions.delete(filter);
+ callback(exports, id);
+ } else if (exports.default && filter(exports.default)) {
+ subscriptions.delete(filter);
+ callback(exports.default, id);
}
+ } catch (err) {
+ logger.error("Error while firing callback for Webpack subscription:\n", err, filter, callback);
}
-
- if (!patch.all) patches.splice(i--, 1);
}
}
+
+ patchedFactory.toString = originalMod.toString.bind(originalMod);
+ // @ts-ignore
+ patchedFactory.$$vencordOriginal = originalMod;
+
+ return patchedFactory;
}
From b938bdf13893747b0307ebd2c55a07966dcde6cb Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sun, 19 May 2024 23:22:48 -0300
Subject: [PATCH 002/125] Clarify about sentry webpack
---
src/webpack/patchWebpack.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index 64b6e331a4..979bf05773 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -99,6 +99,7 @@ Object.defineProperty(Function.prototype, "O", {
// wreq.m is the webpack object containing module factories.
// This is pre-populated with modules, and is also populated via webpackGlobal.push
+// The sentry module also has their own webpack with a pre-populated modules object, so this also targets that
// We replace its prototype with our proxy, which is responsible for returning patched module factories containing our patches
Object.defineProperty(Function.prototype, "m", {
configurable: true,
From cf9bbfc78f274a08c36fca2011c2bb69a1240bf9 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Wed, 22 May 2024 01:30:17 -0300
Subject: [PATCH 003/125] stop annoying me
---
scripts/generateReport.ts | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts
index a83949c88b..5808b4585d 100644
--- a/scripts/generateReport.ts
+++ b/scripts/generateReport.ts
@@ -456,17 +456,18 @@ async function runtime(token: string) {
});
await chunksSearchingDone;
+ wreq = wreq!;
// Require deferred entry points
for (const deferredRequire of deferredRequires) {
- wreq!(deferredRequire as any);
+ wreq(deferredRequire as any);
}
// All chunks Discord has mapped to asset files, even if they are not used anymore
const allChunks = [] as string[];
// Matches "id" or id:
- for (const currentMatch of wreq!.u.toString().matchAll(/(?:"(\d+?)")|(?:(\d+?):)/g)) {
+ for (const currentMatch of wreq.u.toString().matchAll(/(?:"(\d+?)")|(?:(\d+?):)/g)) {
const id = currentMatch[1] ?? currentMatch[2];
if (id == null) continue;
@@ -494,8 +495,8 @@ async function runtime(token: string) {
// Call the getter for all the values in the modules object
// So modules that were not required get patched by our proxy
- for (const id in wreq!.m) {
- wreq!.m[id];
+ for (const id in wreq.m) {
+ wreq.m[id];
}
console.log("[PUP_DEBUG]", "Finished loading all chunks!");
From c4645f79c663b455bbb81301137af7f843d1ab9a Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Wed, 22 May 2024 06:08:28 -0300
Subject: [PATCH 004/125] Make patchedBy a string set
---
src/webpack/patchWebpack.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index 979bf05773..71348b8892 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -141,7 +141,7 @@ function patchFactory(id: string, mod: (module: any, exports: any, require: Webp
}
const originalMod = mod;
- const patchedBy = new Set();
+ const patchedBy = new Set();
// Discords Webpack chunks for some ungodly reason contain random
// newlines. Cyn recommended this workaround and it seems to work fine,
From 4b2734610f88c1ea14262ba9322a544f167fa268 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Wed, 22 May 2024 06:08:40 -0300
Subject: [PATCH 005/125] Add WebpackRequire typings
---
src/webpack/wreq.d.ts | 140 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 140 insertions(+)
create mode 100644 src/webpack/wreq.d.ts
diff --git a/src/webpack/wreq.d.ts b/src/webpack/wreq.d.ts
new file mode 100644
index 0000000000..517ad4b990
--- /dev/null
+++ b/src/webpack/wreq.d.ts
@@ -0,0 +1,140 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2024 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+type AnyRecord = Record;
+
+type ModuleExports = any;
+
+type Module = {
+ id: PropertyKey;
+ loaded: boolean;
+ exports: ModuleExports;
+};
+
+/** exports ({@link ModuleExports}) can be anything, however initially it is always an empty object */
+type ModuleFactory = (module: Module, exports: AnyRecord, require: WebpackRequire) => void;
+
+type AsyncModuleBody = (
+ handleDependencies: (deps: Promise[]) => Promise & (() => void)
+) => Promise;
+
+type ChunkHandlers = {
+ /**
+ * Ensures the js file for this chunk is loaded, or starts to load if it's not
+ * @param chunkId The chunk id
+ * @param promises The promises array to add the loading promise to.
+ */
+ j: (chunkId: string | number, promises: Promise) => void,
+ /**
+ * Ensures the css file for this chunk is loaded, or starts to load if it's not
+ * @param chunkId The chunk id
+ * @param promises The promises array to add the loading promise to. This array will likely contain the promise of the js file too.
+ */
+ css: (chunkId: string | number, promises: Promise) => void,
+};
+
+type ScriptLoadDone = (event: Event) => void;
+
+type OnChunksLoaded = ((result: any, chunkIds: (string | number)[] | undefined, callback: () => any, priority: number) => any) & {
+ /** Check if a chunk has been loaded */
+ j: (chunkId: string | number) => boolean;
+};
+
+type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
+ /** The module factories, where all modules that have been loaded are stored (pre-loaded or loaded by lazy chunks) */
+ m: Record;
+ /** The module cache, where all modules which have been WebpackRequire'd are stored */
+ c: Record;
+ /**
+ * Export star. Sets properties of "fromObject" to "toObject" as getters that return the value from "fromObject", like this:
+ * @example
+ * const fromObject = { a: 1 };
+ * Object.defineProperty(to, "a", {
+ * get: () => fromObject.a
+ * });
+ * @returns fromObject
+ */
+ es: (fromObject: AnyRecord, toObject: AnyRecord) => AnyRecord;
+ /**
+ * Creates an async module. The body function must be a async function.
+ * "module.exports" will be decorated with an AsyncModulePromise.
+ * The body function will be called.
+ * To handle async dependencies correctly do this inside the body: "([a, b, c] = await handleDependencies([a, b, c]));".
+ * If "hasAwaitAfterDependencies" is truthy, "handleDependencies()" must be called at the end of the body function.
+ */
+ a: (module: Module, body: AsyncModuleBody, hasAwaitAfterDependencies?: boolean) => void;
+ /** getDefaultExport function for compatibility with non-harmony modules */
+ n: (module: Module) => () => ModuleExports;
+ /**
+ * Create a fake namespace object, useful for faking an __esModule with a default export.
+ *
+ * mode & 1: Value is a module id, require it
+ *
+ * mode & 2: Merge all properties of value into the namespace
+ *
+ * mode & 4: Return value when already namespace object
+ *
+ * mode & 16: Return value when it's Promise-like
+ *
+ * mode & (8|1): Behave like require
+ */
+ t: (value: any, mode: number) => any;
+ /**
+ * Define property getters. For every prop in "definiton", set a getter in "exports" for the value in "definitiion", like this:
+ * @example
+ * const exports = {};
+ * const definition = { a: 1 };
+ * for (const key in definition) {
+ * Object.defineProperty(exports, key, { get: definition[key] }
+ * }
+ */
+ d: (exports: AnyRecord, definiton: AnyRecord) => void;
+ /** The chunk handlers, which are used to ensure the files of the chunks are loaded, or load if necessary */
+ f: ChunkHandlers;
+ /**
+ * The ensure chunk function, it ensures a chunk is loaded, or loads if needed.
+ * Internally it uses the handlers in {@link WebpackRequire.f} to load/ensure the chunk is loaded.
+ */
+ e: (chunkId: string | number) => Promise;
+ /** Get the filename name for the css part of a chunk */
+ k: (chunkId: string | number) => `${chunkId}.css`;
+ /** Get the filename for the js part of a chunk */
+ u: (chunkId: string | number) => string;
+ /** The global object, will likely always be the window */
+ g: Window;
+ /** Harmony module decorator. Decorates a module as an ES Module, and prevents Node.js "module.exports" from being set */
+ hmd: (module: Module) => any;
+ /** Shorthand for Object.prototype.hasOwnProperty */
+ o: typeof Object.prototype.hasOwnProperty;
+ /**
+ * Function to load a script tag. "done" is called when the loading has finished or a timeout has occurred.
+ * "done" will be attached to existing scripts loading if src === url or data-webpack === `${uniqueName}:${key}`,
+ * so it will be called when that existing script finishes loading.
+ */
+ l: (url: string, done: ScriptLoadDone, key?: string | number, chunkId?: string | number) => void;
+ /** Defines __esModule on the exports, marking ES Modules compatibility as true */
+ r: (exports: AnyRecord) => void;
+ /** Node.js module decorator. Decorates a module as a Node.js module */
+ nmd: (module: Module) => any;
+ /**
+ * Register deferred code which will be executed when the passed chunks are loaded.
+ *
+ * If chunkIds is defined, it defers the execution of the callback and returns undefined.
+ *
+ * If chunkIds is undefined, and no deferred code exists or can be executed, it returns the value of the result argument.
+ *
+ * If chunkIds is undefined, and some deferred code can already be executed, it returns the result of the callback function of the last deferred code.
+ *
+ * When (priority & 1) it will wait for all other handlers with lower priority to be executed before itself is executed.
+ */
+ O: OnChunksLoaded;
+ /** Instantiate a wasm instance with source using "wasmModuleHash", and importObject "importsObj", and then assign the exports of its instance to "exports" */
+ v: (exports: AnyRecord, wasmModuleId: any, wasmModuleHash: string, importsObj?: WebAssembly.Imports) => void;
+ /** Bundle public path, where chunk files are stored. Used by other methods which load chunks to obtain the full asset url */
+ p: string;
+ /** Document baseURI or WebWorker location.href */
+ b: string;
+};
From d5bcbf54f9460e9e3ab6734d7efccc579deab839 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Wed, 22 May 2024 06:17:31 -0300
Subject: [PATCH 006/125] fix
---
src/webpack/wreq.d.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/webpack/wreq.d.ts b/src/webpack/wreq.d.ts
index 517ad4b990..2dbd311500 100644
--- a/src/webpack/wreq.d.ts
+++ b/src/webpack/wreq.d.ts
@@ -98,7 +98,7 @@ type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
* The ensure chunk function, it ensures a chunk is loaded, or loads if needed.
* Internally it uses the handlers in {@link WebpackRequire.f} to load/ensure the chunk is loaded.
*/
- e: (chunkId: string | number) => Promise;
+ e: (chunkId: string | number) => Promise;
/** Get the filename name for the css part of a chunk */
k: (chunkId: string | number) => `${chunkId}.css`;
/** Get the filename for the js part of a chunk */
From f6a7cdc430b0a6b31f729e4dec2f2cbd2c765afc Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Wed, 22 May 2024 06:22:37 -0300
Subject: [PATCH 007/125] more fix
---
src/webpack/wreq.d.ts | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/src/webpack/wreq.d.ts b/src/webpack/wreq.d.ts
index 2dbd311500..25b548caa5 100644
--- a/src/webpack/wreq.d.ts
+++ b/src/webpack/wreq.d.ts
@@ -131,8 +131,11 @@ type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
* When (priority & 1) it will wait for all other handlers with lower priority to be executed before itself is executed.
*/
O: OnChunksLoaded;
- /** Instantiate a wasm instance with source using "wasmModuleHash", and importObject "importsObj", and then assign the exports of its instance to "exports" */
- v: (exports: AnyRecord, wasmModuleId: any, wasmModuleHash: string, importsObj?: WebAssembly.Imports) => void;
+ /**
+ * Instantiate a wasm instance with source using "wasmModuleHash", and importObject "importsObj", and then assign the exports of its instance to "exports"
+ * @returns The exports of the wasm instance
+ */
+ v: (exports: AnyRecord, wasmModuleId: any, wasmModuleHash: string, importsObj?: WebAssembly.Imports) => Promise;
/** Bundle public path, where chunk files are stored. Used by other methods which load chunks to obtain the full asset url */
p: string;
/** Document baseURI or WebWorker location.href */
From bc367b1d2a178d0be0192ba524bf4d64879e3ff7 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Wed, 22 May 2024 06:25:30 -0300
Subject: [PATCH 008/125] and also a fix
---
src/webpack/wreq.d.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/webpack/wreq.d.ts b/src/webpack/wreq.d.ts
index 25b548caa5..c396e615da 100644
--- a/src/webpack/wreq.d.ts
+++ b/src/webpack/wreq.d.ts
@@ -133,7 +133,7 @@ type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
O: OnChunksLoaded;
/**
* Instantiate a wasm instance with source using "wasmModuleHash", and importObject "importsObj", and then assign the exports of its instance to "exports"
- * @returns The exports of the wasm instance
+ * @returns The exports argument, but now assigned with the exports of the wasm instance
*/
v: (exports: AnyRecord, wasmModuleId: any, wasmModuleHash: string, importsObj?: WebAssembly.Imports) => Promise;
/** Bundle public path, where chunk files are stored. Used by other methods which load chunks to obtain the full asset url */
From 40a1f48267679ac242a6a134f6d1a920ec5a9ebb Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Thu, 23 May 2024 03:10:59 -0300
Subject: [PATCH 009/125] boops
---
src/webpack/index.ts | 1 +
src/webpack/patchWebpack.ts | 23 ++++++++--------
src/webpack/webpack.ts | 15 ++++++-----
src/webpack/wreq.d.ts | 54 ++++++++++++++++++-------------------
4 files changed, 47 insertions(+), 46 deletions(-)
diff --git a/src/webpack/index.ts b/src/webpack/index.ts
index 036c2a3fcb..6f1fd25b85 100644
--- a/src/webpack/index.ts
+++ b/src/webpack/index.ts
@@ -18,3 +18,4 @@
export * as Common from "./common";
export * from "./webpack";
+export * from "./wreq.d";
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index 71348b8892..b79bec46e2 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -8,20 +8,19 @@ import { Logger } from "@utils/Logger";
import { UNCONFIGURABLE_PROPERTIES } from "@utils/misc";
import { canonicalizeMatch, canonicalizeReplacement } from "@utils/patches";
import { PatchReplacement } from "@utils/types";
-import { WebpackInstance } from "discord-types/other";
import { traceFunction } from "../debug/Tracer";
import { patches } from "../plugins";
-import { _initWebpack, beforeInitListeners, factoryListeners, moduleListeners, subscriptions, wreq } from ".";
+import { _initWebpack, beforeInitListeners, factoryListeners, ModuleFactory, moduleListeners, subscriptions, WebpackRequire, wreq } from ".";
const logger = new Logger("WebpackInterceptor", "#8caaee");
const initCallbackRegex = canonicalizeMatch(/{return \i\(".+?"\)}/);
-const modulesProxyhandler: ProxyHandler = {
+const modulesProxyhandler: ProxyHandler = {
...Object.fromEntries(Object.getOwnPropertyNames(Reflect).map(propName =>
- [propName, (target: any, ...args: any[]) => Reflect[propName](target, ...args)]
+ [propName, (target: WebpackRequire["m"], ...args: any[]) => Reflect[propName](target, ...args)]
)),
- get: (target, p: string) => {
+ get: (target, p) => {
const mod = Reflect.get(target, p);
// If the property is not a module id, return the value of it without trying to patch
@@ -48,7 +47,7 @@ const modulesProxyhandler: ProxyHandler = {
Object.defineProperty(Function.prototype, "O", {
configurable: true,
- set(onChunksLoaded: any) {
+ set(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
@@ -59,14 +58,14 @@ Object.defineProperty(Function.prototype, "O", {
delete (Function.prototype as any).O;
const originalOnChunksLoaded = onChunksLoaded;
- onChunksLoaded = function (this: unknown, result: any, chunkIds: string[], callback: () => any, priority: number) {
+ onChunksLoaded = function (result, chunkIds, callback, priority) {
if (callback != null && initCallbackRegex.test(callback.toString())) {
Object.defineProperty(this, "O", {
value: originalOnChunksLoaded,
configurable: true
});
- const wreq = this as WebpackInstance;
+ const wreq = this;
const originalCallback = callback;
callback = function (this: unknown) {
@@ -85,7 +84,7 @@ Object.defineProperty(Function.prototype, "O", {
}
originalOnChunksLoaded.apply(this, arguments as any);
- };
+ } as WebpackRequire["O"];
onChunksLoaded.toString = originalOnChunksLoaded.toString.bind(originalOnChunksLoaded);
}
@@ -131,7 +130,7 @@ Object.defineProperty(Function.prototype, "m", {
let webpackNotInitializedLogged = false;
-function patchFactory(id: string, mod: (module: any, exports: any, require: WebpackInstance) => void) {
+function patchFactory(id: string | number, mod: ModuleFactory) {
for (const factoryListener of factoryListeners) {
try {
factoryListener(mod);
@@ -255,7 +254,7 @@ function patchFactory(id: string, mod: (module: any, exports: any, require: Webp
if (!patch.all) patches.splice(i--, 1);
}
- function patchedFactory(module: any, exports: any, require: WebpackInstance) {
+ const patchedFactory: ModuleFactory = (module, exports, require) => {
if (wreq == null && IS_DEV) {
if (!webpackNotInitializedLogged) {
webpackNotInitializedLogged = true;
@@ -312,7 +311,7 @@ function patchFactory(id: string, mod: (module: any, exports: any, require: Webp
logger.error("Error while firing callback for Webpack subscription:\n", err, filter, callback);
}
}
- }
+ };
patchedFactory.toString = originalMod.toString.bind(originalMod);
// @ts-ignore
diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts
index 8548208518..7cd28866ec 100644
--- a/src/webpack/webpack.ts
+++ b/src/webpack/webpack.ts
@@ -20,9 +20,9 @@ import { makeLazy, proxyLazy } from "@utils/lazy";
import { LazyComponent } from "@utils/lazyReact";
import { Logger } from "@utils/Logger";
import { canonicalizeMatch } from "@utils/patches";
-import type { WebpackInstance } from "discord-types/other";
import { traceFunction } from "../debug/Tracer";
+import { ModuleExports, ModuleFactory, WebpackRequire } from "./wreq";
const logger = new Logger("Webpack");
@@ -33,8 +33,8 @@ export let _resolveReady: () => void;
*/
export const onceReady = new Promise(r => _resolveReady = r);
-export let wreq: WebpackInstance;
-export let cache: WebpackInstance["c"];
+export let wreq: WebpackRequire;
+export let cache: WebpackRequire["c"];
export type FilterFn = (mod: any) => boolean;
@@ -68,14 +68,14 @@ export const filters = {
}
};
-export type CallbackFn = (mod: any, id: string) => void;
+export type CallbackFn = (module: ModuleExports, id: PropertyKey) => void;
export const subscriptions = new Map();
export const moduleListeners = new Set();
-export const factoryListeners = new Set<(factory: (module: any, exports: any, require: WebpackInstance) => void) => void>();
-export const beforeInitListeners = new Set<(wreq: WebpackInstance) => void>();
+export const factoryListeners = new Set<(factory: ModuleFactory) => void>();
+export const beforeInitListeners = new Set<(wreq: WebpackRequire) => void>();
-export function _initWebpack(webpackRequire: WebpackInstance) {
+export function _initWebpack(webpackRequire: WebpackRequire) {
wreq = webpackRequire;
cache = webpackRequire.c;
}
@@ -500,6 +500,7 @@ export function search(...filters: Array) {
const factories = wreq.m;
outer:
for (const id in factories) {
+ // @ts-ignore
const factory = factories[id].original ?? factories[id];
const str: string = factory.toString();
for (const filter of filters) {
diff --git a/src/webpack/wreq.d.ts b/src/webpack/wreq.d.ts
index c396e615da..d3d38127f1 100644
--- a/src/webpack/wreq.d.ts
+++ b/src/webpack/wreq.d.ts
@@ -4,46 +4,46 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
-type AnyRecord = Record;
+export type AnyRecord = Record;
-type ModuleExports = any;
+export type ModuleExports = any;
-type Module = {
+export type Module = {
id: PropertyKey;
loaded: boolean;
exports: ModuleExports;
};
/** exports ({@link ModuleExports}) can be anything, however initially it is always an empty object */
-type ModuleFactory = (module: Module, exports: AnyRecord, require: WebpackRequire) => void;
+export type ModuleFactory = (module: Module, exports: AnyRecord, require: WebpackRequire) => void;
-type AsyncModuleBody = (
+export type AsyncModuleBody = (
handleDependencies: (deps: Promise[]) => Promise & (() => void)
) => Promise;
-type ChunkHandlers = {
+export type ChunkHandlers = {
/**
* Ensures the js file for this chunk is loaded, or starts to load if it's not
* @param chunkId The chunk id
* @param promises The promises array to add the loading promise to.
*/
- j: (chunkId: string | number, promises: Promise) => void,
+ j: (this: ChunkHandlers, chunkId: PropertyKey, promises: Promise) => void,
/**
* Ensures the css file for this chunk is loaded, or starts to load if it's not
* @param chunkId The chunk id
* @param promises The promises array to add the loading promise to. This array will likely contain the promise of the js file too.
*/
- css: (chunkId: string | number, promises: Promise) => void,
+ css: (this: ChunkHandlers, chunkId: PropertyKey, promises: Promise) => void,
};
-type ScriptLoadDone = (event: Event) => void;
+export type ScriptLoadDone = (event: Event) => void;
-type OnChunksLoaded = ((result: any, chunkIds: (string | number)[] | undefined, callback: () => any, priority: number) => any) & {
+export type OnChunksLoaded = ((this: WebpackRequire, result: any, chunkIds: PropertyKey[] | undefined | null, callback: () => any, priority: number) => any) & {
/** Check if a chunk has been loaded */
- j: (chunkId: string | number) => boolean;
+ j: (chunkId: PropertyKey) => boolean;
};
-type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
+export type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
/** The module factories, where all modules that have been loaded are stored (pre-loaded or loaded by lazy chunks) */
m: Record;
/** The module cache, where all modules which have been WebpackRequire'd are stored */
@@ -52,12 +52,12 @@ type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
* Export star. Sets properties of "fromObject" to "toObject" as getters that return the value from "fromObject", like this:
* @example
* const fromObject = { a: 1 };
- * Object.defineProperty(to, "a", {
- * get: () => fromObject.a
+ * Object.defineProperty(fromObject, "a", {
+ * get: () => fromObject["a"]
* });
* @returns fromObject
*/
- es: (fromObject: AnyRecord, toObject: AnyRecord) => AnyRecord;
+ es: (this: WebpackRequire, fromObject: AnyRecord, toObject: AnyRecord) => AnyRecord;
/**
* Creates an async module. The body function must be a async function.
* "module.exports" will be decorated with an AsyncModulePromise.
@@ -65,9 +65,9 @@ type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
* To handle async dependencies correctly do this inside the body: "([a, b, c] = await handleDependencies([a, b, c]));".
* If "hasAwaitAfterDependencies" is truthy, "handleDependencies()" must be called at the end of the body function.
*/
- a: (module: Module, body: AsyncModuleBody, hasAwaitAfterDependencies?: boolean) => void;
+ a: (this: WebpackRequire, module: Module, body: AsyncModuleBody, hasAwaitAfterDependencies?: boolean) => void;
/** getDefaultExport function for compatibility with non-harmony modules */
- n: (module: Module) => () => ModuleExports;
+ n: (this: WebpackRequire, module: Module) => () => ModuleExports;
/**
* Create a fake namespace object, useful for faking an __esModule with a default export.
*
@@ -81,7 +81,7 @@ type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
*
* mode & (8|1): Behave like require
*/
- t: (value: any, mode: number) => any;
+ t: (this: WebpackRequire, value: any, mode: number) => any;
/**
* Define property getters. For every prop in "definiton", set a getter in "exports" for the value in "definitiion", like this:
* @example
@@ -91,22 +91,22 @@ type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
* Object.defineProperty(exports, key, { get: definition[key] }
* }
*/
- d: (exports: AnyRecord, definiton: AnyRecord) => void;
+ d: (this: WebpackRequire, exports: AnyRecord, definiton: AnyRecord) => void;
/** The chunk handlers, which are used to ensure the files of the chunks are loaded, or load if necessary */
f: ChunkHandlers;
/**
* The ensure chunk function, it ensures a chunk is loaded, or loads if needed.
* Internally it uses the handlers in {@link WebpackRequire.f} to load/ensure the chunk is loaded.
*/
- e: (chunkId: string | number) => Promise;
+ e: (this: WebpackRequire, chunkId: PropertyKey) => Promise;
/** Get the filename name for the css part of a chunk */
- k: (chunkId: string | number) => `${chunkId}.css`;
+ k: (this: WebpackRequire, chunkId: PropertyKey) => `${chunkId}.css`;
/** Get the filename for the js part of a chunk */
- u: (chunkId: string | number) => string;
+ u: (this: WebpackRequire, chunkId: PropertyKey) => string;
/** The global object, will likely always be the window */
g: Window;
/** Harmony module decorator. Decorates a module as an ES Module, and prevents Node.js "module.exports" from being set */
- hmd: (module: Module) => any;
+ hmd: (this: WebpackRequire, module: Module) => any;
/** Shorthand for Object.prototype.hasOwnProperty */
o: typeof Object.prototype.hasOwnProperty;
/**
@@ -114,11 +114,11 @@ type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
* "done" will be attached to existing scripts loading if src === url or data-webpack === `${uniqueName}:${key}`,
* so it will be called when that existing script finishes loading.
*/
- l: (url: string, done: ScriptLoadDone, key?: string | number, chunkId?: string | number) => void;
+ l: (this: WebpackRequire, url: string, done: ScriptLoadDone, key?: string | number, chunkId?: PropertyKey) => void;
/** Defines __esModule on the exports, marking ES Modules compatibility as true */
- r: (exports: AnyRecord) => void;
+ r: (this: WebpackRequire, exports: AnyRecord) => void;
/** Node.js module decorator. Decorates a module as a Node.js module */
- nmd: (module: Module) => any;
+ nmd: (this: WebpackRequire, module: Module) => any;
/**
* Register deferred code which will be executed when the passed chunks are loaded.
*
@@ -135,7 +135,7 @@ type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
* Instantiate a wasm instance with source using "wasmModuleHash", and importObject "importsObj", and then assign the exports of its instance to "exports"
* @returns The exports argument, but now assigned with the exports of the wasm instance
*/
- v: (exports: AnyRecord, wasmModuleId: any, wasmModuleHash: string, importsObj?: WebAssembly.Imports) => Promise;
+ v: (this: WebpackRequire, exports: AnyRecord, wasmModuleId: any, wasmModuleHash: string, importsObj?: WebAssembly.Imports) => Promise;
/** Bundle public path, where chunk files are stored. Used by other methods which load chunks to obtain the full asset url */
p: string;
/** Document baseURI or WebWorker location.href */
From 5ca4e58fad70fcf1fa6a13e798245d85049c9af3 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Thu, 23 May 2024 03:36:53 -0300
Subject: [PATCH 010/125] Clean up WebpackRequire typings
---
scripts/generateReport.ts | 12 ++++++------
src/webpack/patchWebpack.ts | 17 ++++++++++-------
src/webpack/webpack.ts | 22 +++++++++++-----------
src/webpack/wreq.d.ts | 18 ++++++++----------
4 files changed, 35 insertions(+), 34 deletions(-)
diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts
index 5808b4585d..4ed44f7b45 100644
--- a/scripts/generateReport.ts
+++ b/scripts/generateReport.ts
@@ -41,7 +41,7 @@ const browser = await pup.launch({
const page = await browser.newPage();
await page.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36");
-function maybeGetError(handle: JSHandle) {
+async function maybeGetError(handle: JSHandle) {
return (handle as JSHandle)?.getProperty("message")
.then(m => m.jsonValue());
}
@@ -383,7 +383,7 @@ async function runtime(token: string) {
await Promise.all(
Array.from(validChunkGroups)
.map(([chunkIds]) =>
- Promise.all(chunkIds.map(id => wreq.e(id as any).catch(() => { })))
+ Promise.all(chunkIds.map(id => wreq.e(id).catch(() => { })))
)
);
@@ -395,7 +395,7 @@ async function runtime(token: string) {
continue;
}
- if (wreq.m[entryPoint]) wreq(entryPoint as any);
+ if (wreq.m[entryPoint]) wreq(entryPoint);
} catch (err) {
console.error(err);
}
@@ -460,7 +460,7 @@ async function runtime(token: string) {
// Require deferred entry points
for (const deferredRequire of deferredRequires) {
- wreq(deferredRequire as any);
+ wreq(deferredRequire);
}
// All chunks Discord has mapped to asset files, even if they are not used anymore
@@ -488,8 +488,8 @@ async function runtime(token: string) {
// Loads and requires a chunk
if (!isWasm) {
- await wreq.e(id as any);
- if (wreq.m[id]) wreq(id as any);
+ await wreq.e(id);
+ if (wreq.m[id]) wreq(id);
}
}));
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index a08d74c96a..b57b21aa06 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -24,6 +24,7 @@ const modulesProxyhandler: ProxyHandler = {
const mod = Reflect.get(target, p);
// If the property is not a module id, return the value of it without trying to patch
+ // @ts-ignore
if (mod == null || mod.$$vencordOriginal != null || Number.isNaN(Number(p))) return mod;
const patchedMod = patchFactory(p, mod);
@@ -90,12 +91,14 @@ Object.defineProperty(Function.prototype, "O", {
// Returns whether a chunk has been loaded
Object.defineProperty(onChunksLoaded, "j", {
+ configurable: true,
+
set(v) {
+ // @ts-ignore
delete onChunksLoaded.j;
onChunksLoaded.j = v;
originalOnChunksLoaded.j = v;
- },
- configurable: true
+ }
});
}
@@ -113,7 +116,7 @@ Object.defineProperty(Function.prototype, "O", {
Object.defineProperty(Function.prototype, "m", {
configurable: true,
- set(originalModules: any) {
+ set(originalModules: WebpackRequire["m"]) {
// When using react devtools or other extensions, we may also catch their webpack here.
// This ensures we actually got the right one
const { stack } = new Error();
@@ -140,7 +143,7 @@ Object.defineProperty(Function.prototype, "m", {
let webpackNotInitializedLogged = false;
-function patchFactory(id: string | number, mod: ModuleFactory) {
+function patchFactory(id: PropertyKey, mod: ModuleFactory) {
for (const factoryListener of factoryListeners) {
try {
factoryListener(mod);
@@ -192,7 +195,7 @@ function patchFactory(id: string | number, mod: ModuleFactory) {
const newCode = executePatch(replacement.match, replacement.replace as string);
if (newCode === code) {
if (!patch.noWarn) {
- logger.warn(`Patch by ${patch.plugin} had no effect (Module id is ${id}): ${replacement.match}`);
+ logger.warn(`Patch by ${patch.plugin} had no effect (Module id is ${String(id)}): ${replacement.match}`);
if (IS_DEV) {
logger.debug("Function Source:\n", code);
}
@@ -210,9 +213,9 @@ function patchFactory(id: string | number, mod: ModuleFactory) {
}
code = newCode;
- mod = (0, eval)(`// Webpack Module ${id} - Patched by ${[...patchedBy].join(", ")}\n${newCode}\n//# sourceURL=WebpackModule${id}`);
+ mod = (0, eval)(`// Webpack Module ${String(id)} - Patched by ${[...patchedBy].join(", ")}\n${newCode}\n//# sourceURL=WebpackModule${String(id)}`);
} catch (err) {
- logger.error(`Patch by ${patch.plugin} errored (Module id is ${id}): ${replacement.match}\n`, err);
+ logger.error(`Patch by ${patch.plugin} errored (Module id is ${String(id)}): ${replacement.match}\n`, err);
if (IS_DEV) {
const changeSize = code.length - lastCode.length;
diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts
index 7cd28866ec..052351b793 100644
--- a/src/webpack/webpack.ts
+++ b/src/webpack/webpack.ts
@@ -36,7 +36,7 @@ export const onceReady = new Promise(r => _resolveReady = r);
export let wreq: WebpackRequire;
export let cache: WebpackRequire["c"];
-export type FilterFn = (mod: any) => boolean;
+export type FilterFn = (module: ModuleExports) => boolean;
export const filters = {
byProps: (...props: string[]): FilterFn =>
@@ -129,7 +129,7 @@ export function findAll(filter: FilterFn) {
if (typeof filter !== "function")
throw new Error("Invalid filter. Expected a function got " + typeof filter);
- const ret = [] as any[];
+ const ret: ModuleExports[] = [];
for (const key in cache) {
const mod = cache[key];
if (!mod?.exports) continue;
@@ -169,7 +169,7 @@ export const findBulk = traceFunction("findBulk", function findBulk(...filterFns
const filters = filterFns as Array;
let found = 0;
- const results = Array(length);
+ const results: ModuleExports[] = Array(length);
outer:
for (const key in cache) {
@@ -496,12 +496,12 @@ export function waitFor(filter: string | string[] | FilterFn, callback: Callback
* @returns Mapping of found modules
*/
export function search(...filters: Array) {
- const results = {} as Record;
+ const results: WebpackRequire["m"] = {};
const factories = wreq.m;
outer:
for (const id in factories) {
// @ts-ignore
- const factory = factories[id].original ?? factories[id];
+ const factory = factories[id].$$vencordOriginal ?? factories[id];
const str: string = factory.toString();
for (const filter of filters) {
if (typeof filter === "string" && !str.includes(filter)) continue outer;
@@ -521,18 +521,18 @@ export function search(...filters: Array) {
* so putting breakpoints or similar will have no effect.
* @param id The id of the module to extract
*/
-export function extract(id: string | number) {
- const mod = wreq.m[id] as Function;
+export function extract(id: PropertyKey) {
+ const mod = wreq.m[id];
if (!mod) return null;
const code = `
-// [EXTRACTED] WebpackModule${id}
+// [EXTRACTED] WebpackModule${String(id)}
// WARNING: This module was extracted to be more easily readable.
// This module is NOT ACTUALLY USED! This means putting breakpoints will have NO EFFECT!!
0,${mod.toString()}
-//# sourceURL=ExtractedWebpackModule${id}
+//# sourceURL=ExtractedWebpackModule${String(id)}
`;
- const extracted = (0, eval)(code);
- return extracted as Function;
+ const extracted: ModuleFactory = (0, eval)(code);
+ return extracted;
}
diff --git a/src/webpack/wreq.d.ts b/src/webpack/wreq.d.ts
index d3d38127f1..c86fa1c49d 100644
--- a/src/webpack/wreq.d.ts
+++ b/src/webpack/wreq.d.ts
@@ -4,8 +4,6 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
-export type AnyRecord = Record;
-
export type ModuleExports = any;
export type Module = {
@@ -14,8 +12,8 @@ export type Module = {
exports: ModuleExports;
};
-/** exports ({@link ModuleExports}) can be anything, however initially it is always an empty object */
-export type ModuleFactory = (module: Module, exports: AnyRecord, require: WebpackRequire) => void;
+/** exports can be anything, however initially it is always an empty object */
+export type ModuleFactory = (module: Module, exports: ModuleExports, require: WebpackRequire) => void;
export type AsyncModuleBody = (
handleDependencies: (deps: Promise[]) => Promise & (() => void)
@@ -40,7 +38,7 @@ export type ScriptLoadDone = (event: Event) => void;
export type OnChunksLoaded = ((this: WebpackRequire, result: any, chunkIds: PropertyKey[] | undefined | null, callback: () => any, priority: number) => any) & {
/** Check if a chunk has been loaded */
- j: (chunkId: PropertyKey) => boolean;
+ j: (this: OnChunksLoaded, chunkId: PropertyKey) => boolean;
};
export type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
@@ -57,7 +55,7 @@ export type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
* });
* @returns fromObject
*/
- es: (this: WebpackRequire, fromObject: AnyRecord, toObject: AnyRecord) => AnyRecord;
+ es: (this: WebpackRequire, fromObject: Record, toObject: Record) => Record;
/**
* Creates an async module. The body function must be a async function.
* "module.exports" will be decorated with an AsyncModulePromise.
@@ -91,7 +89,7 @@ export type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
* Object.defineProperty(exports, key, { get: definition[key] }
* }
*/
- d: (this: WebpackRequire, exports: AnyRecord, definiton: AnyRecord) => void;
+ d: (this: WebpackRequire, exports: Record, definiton: Record) => void;
/** The chunk handlers, which are used to ensure the files of the chunks are loaded, or load if necessary */
f: ChunkHandlers;
/**
@@ -100,7 +98,7 @@ export type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
*/
e: (this: WebpackRequire, chunkId: PropertyKey) => Promise;
/** Get the filename name for the css part of a chunk */
- k: (this: WebpackRequire, chunkId: PropertyKey) => `${chunkId}.css`;
+ k: (this: WebpackRequire, chunkId: PropertyKey) => string;
/** Get the filename for the js part of a chunk */
u: (this: WebpackRequire, chunkId: PropertyKey) => string;
/** The global object, will likely always be the window */
@@ -116,7 +114,7 @@ export type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
*/
l: (this: WebpackRequire, url: string, done: ScriptLoadDone, key?: string | number, chunkId?: PropertyKey) => void;
/** Defines __esModule on the exports, marking ES Modules compatibility as true */
- r: (this: WebpackRequire, exports: AnyRecord) => void;
+ r: (this: WebpackRequire, exports: ModuleExports) => void;
/** Node.js module decorator. Decorates a module as a Node.js module */
nmd: (this: WebpackRequire, module: Module) => any;
/**
@@ -135,7 +133,7 @@ export type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
* Instantiate a wasm instance with source using "wasmModuleHash", and importObject "importsObj", and then assign the exports of its instance to "exports"
* @returns The exports argument, but now assigned with the exports of the wasm instance
*/
- v: (this: WebpackRequire, exports: AnyRecord, wasmModuleId: any, wasmModuleHash: string, importsObj?: WebAssembly.Imports) => Promise;
+ v: (this: WebpackRequire, exports: ModuleExports, wasmModuleId: any, wasmModuleHash: string, importsObj?: WebAssembly.Imports) => Promise;
/** Bundle public path, where chunk files are stored. Used by other methods which load chunks to obtain the full asset url */
p: string;
/** Document baseURI or WebWorker location.href */
From 688449ed62980e749907a0b474b642f8e8e140fa Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Thu, 23 May 2024 03:38:06 -0300
Subject: [PATCH 011/125] forgot this!
---
src/webpack/patchWebpack.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index b57b21aa06..86ebbb2dd9 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -11,7 +11,7 @@ import { PatchReplacement } from "@utils/types";
import { traceFunction } from "../debug/Tracer";
import { patches } from "../plugins";
-import { _initWebpack, beforeInitListeners, factoryListeners, ModuleFactory, moduleListeners, subscriptions, WebpackRequire, wreq } from ".";
+import { _initWebpack, beforeInitListeners, factoryListeners, ModuleFactory, moduleListeners, OnChunksLoaded, subscriptions, WebpackRequire, wreq } from ".";
const logger = new Logger("WebpackInterceptor", "#8caaee");
const initCallbackRegex = canonicalizeMatch(/{return \i\(".+?"\)}/);
@@ -93,7 +93,7 @@ Object.defineProperty(Function.prototype, "O", {
Object.defineProperty(onChunksLoaded, "j", {
configurable: true,
- set(v) {
+ set(v: OnChunksLoaded["j"]) {
// @ts-ignore
delete onChunksLoaded.j;
onChunksLoaded.j = v;
From 66e1db1fdb9229b81829db7de3678459c0ce0012 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Thu, 23 May 2024 03:39:59 -0300
Subject: [PATCH 012/125] more
---
src/webpack/patchWebpack.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index 86ebbb2dd9..874687c8ab 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -48,7 +48,7 @@ const modulesProxyhandler: ProxyHandler = {
Object.defineProperty(Function.prototype, "O", {
configurable: true,
- set(onChunksLoaded: WebpackRequire["O"]) {
+ 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
@@ -116,7 +116,7 @@ Object.defineProperty(Function.prototype, "O", {
Object.defineProperty(Function.prototype, "m", {
configurable: true,
- set(originalModules: WebpackRequire["m"]) {
+ set(this: WebpackRequire, originalModules: WebpackRequire["m"]) {
// When using react devtools or other extensions, we may also catch their webpack here.
// This ensures we actually got the right one
const { stack } = new Error();
From 01a4ac9c13955539f5741f8bfb8d6588e7167199 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Thu, 23 May 2024 03:42:58 -0300
Subject: [PATCH 013/125] sometimes I'm stupid
---
src/webpack/patchWebpack.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index 874687c8ab..918446935d 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -18,7 +18,7 @@ const initCallbackRegex = canonicalizeMatch(/{return \i\(".+?"\)}/);
const modulesProxyhandler: ProxyHandler = {
...Object.fromEntries(Object.getOwnPropertyNames(Reflect).map(propName =>
- [propName, (target: WebpackRequire["m"], ...args: any[]) => Reflect[propName](target, ...args)]
+ [propName, (...args: any[]) => Reflect[propName](...args)]
)),
get: (target, p) => {
const mod = Reflect.get(target, p);
From 3ab68f929677e129ff3a43c70b2e04b99768c71b Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Thu, 23 May 2024 06:04:21 -0300
Subject: [PATCH 014/125] Option for eager patching
---
scripts/generateReport.ts | 2 +-
src/api/Settings.ts | 4 +-
src/components/VencordSettings/VencordTab.tsx | 5 +
src/plugins/devCompanion.dev/index.tsx | 3 +-
src/webpack/patchWebpack.ts | 125 +++++++++++++-----
5 files changed, 100 insertions(+), 39 deletions(-)
diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts
index 4ed44f7b45..7fc435249f 100644
--- a/scripts/generateReport.ts
+++ b/scripts/generateReport.ts
@@ -285,7 +285,7 @@ async function runtime(token: string) {
Object.defineProperty(navigator, "languages", {
get: function () {
return ["en-US", "en"];
- },
+ }
});
// Monkey patch Logger to not log with custom css
diff --git a/src/api/Settings.ts b/src/api/Settings.ts
index 490e6ef7f3..a96e6ca4e3 100644
--- a/src/api/Settings.ts
+++ b/src/api/Settings.ts
@@ -32,9 +32,10 @@ export interface Settings {
autoUpdate: boolean;
autoUpdateNotification: boolean,
useQuickCss: boolean;
- enableReactDevtools: boolean;
themeLinks: string[];
+ eagerPatches: boolean;
enabledThemes: string[];
+ enableReactDevtools: boolean;
frameless: boolean;
transparent: boolean;
winCtrlQ: boolean;
@@ -81,6 +82,7 @@ const DefaultSettings: Settings = {
autoUpdateNotification: true,
useQuickCss: true,
themeLinks: [],
+ eagerPatches: false,
enabledThemes: [],
enableReactDevtools: false,
frameless: false,
diff --git a/src/components/VencordSettings/VencordTab.tsx b/src/components/VencordSettings/VencordTab.tsx
index c0a66fdc79..c9702b4350 100644
--- a/src/components/VencordSettings/VencordTab.tsx
+++ b/src/components/VencordSettings/VencordTab.tsx
@@ -66,6 +66,11 @@ function VencordSettings() {
title: "Enable React Developer Tools",
note: "Requires a full restart"
},
+ {
+ key: "eagerPatches",
+ title: "Apply Vencord patches before they are needed",
+ note: "Increases startup timing, but may make app usage more fluid. Note that the difference of having this on or off is minimal."
+ },
!IS_WEB && (!IS_DISCORD_DESKTOP || !isWindows ? {
key: "frameless",
title: "Disable the window frame",
diff --git a/src/plugins/devCompanion.dev/index.tsx b/src/plugins/devCompanion.dev/index.tsx
index 25fd563e46..37834e6a2e 100644
--- a/src/plugins/devCompanion.dev/index.tsx
+++ b/src/plugins/devCompanion.dev/index.tsx
@@ -160,7 +160,8 @@ function initWs(isManual = false) {
return reply("Expected exactly one 'find' matches, found " + keys.length);
const mod = candidates[keys[0]];
- let src = String(mod.original ?? mod).replaceAll("\n", "");
+ // @ts-ignore
+ let src = String(mod.$$vencordOriginal ?? mod).replaceAll("\n", "");
if (src.startsWith("function(")) {
src = "0," + src;
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index 918446935d..f5ebea7e17 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -4,6 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
+import { Settings } from "@api/Settings";
import { Logger } from "@utils/Logger";
import { UNCONFIGURABLE_PROPERTIES } from "@utils/misc";
import { canonicalizeMatch, canonicalizeReplacement } from "@utils/patches";
@@ -16,23 +17,53 @@ import { _initWebpack, beforeInitListeners, factoryListeners, ModuleFactory, mod
const logger = new Logger("WebpackInterceptor", "#8caaee");
const initCallbackRegex = canonicalizeMatch(/{return \i\(".+?"\)}/);
+const allProxiedModules = new Set();
+
const modulesProxyhandler: ProxyHandler = {
...Object.fromEntries(Object.getOwnPropertyNames(Reflect).map(propName =>
[propName, (...args: any[]) => Reflect[propName](...args)]
)),
get: (target, p) => {
- const mod = Reflect.get(target, p);
+ const propValue = Reflect.get(target, p);
+
+ // 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
+ if (propValue == null || Number.isNaN(Number(p)) || propValue.$$vencordOriginal != null || propValue.$$vencordRequired === true) {
+ return propValue;
+ }
+
+ // This patches factories if eagerPatches are disabled
+ const patchedFactory = patchFactory(p, propValue);
+ Reflect.set(target, p, patchedFactory);
+
+ return patchedFactory;
+ },
+ set: (target, p, newValue) => {
+ // $$vencordRequired means we are resetting the factory to its original after being required
+ // 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))) {
+ return Reflect.set(target, p, newValue);
+ }
- // If the property is not a module id, return the value of it without trying to patch
+ const existingFactory = Reflect.get(target, p);
+
+ // Check if this factory is already patched
// @ts-ignore
- if (mod == null || mod.$$vencordOriginal != null || Number.isNaN(Number(p))) return mod;
+ if (existingFactory?.$$vencordOriginal === newValue) {
+ return true;
+ }
- const patchedMod = patchFactory(p, mod);
- Reflect.set(target, p, patchedMod);
+ const patchedFactory = patchFactory(p, newValue);
+
+ // Modules are only patched once, so we need to set the patched factory on all the modules
+ for (const proxiedModules of allProxiedModules) {
+ Reflect.set(proxiedModules, p, patchedFactory);
+ }
- return patchedMod;
+ return true;
},
- set: (target, p, newValue) => Reflect.set(target, p, newValue),
ownKeys: target => {
const keys = Reflect.ownKeys(target);
for (const key of UNCONFIGURABLE_PROPERTIES) {
@@ -63,7 +94,9 @@ Object.defineProperty(Function.prototype, "O", {
if (callback != null && initCallbackRegex.test(callback.toString())) {
Object.defineProperty(this, "O", {
value: originalOnChunksLoaded,
- configurable: true
+ configurable: true,
+ enumerable: true,
+ writable: true
});
const wreq = this;
@@ -104,15 +137,17 @@ Object.defineProperty(Function.prototype, "O", {
Object.defineProperty(this, "O", {
value: onChunksLoaded,
- configurable: true
+ configurable: true,
+ enumerable: true,
+ writable: true
});
}
});
// wreq.m is the webpack object containing module factories.
-// This is pre-populated with modules, and is also populated via webpackGlobal.push
-// The sentry module also has their own webpack with a pre-populated modules object, so this also targets that
-// We replace its prototype with our proxy, which is responsible for returning patched module factories containing our patches
+// 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
+// We replace its prototype with our proxy, which is responsible for patching the module factories
Object.defineProperty(Function.prototype, "m", {
configurable: true,
@@ -124,35 +159,47 @@ Object.defineProperty(Function.prototype, "m", {
logger.info("Found Webpack module factory", stack.match(/\/assets\/(.+?\.js)/)?.[1] ?? "");
// The new object which will contain the factories
- const modules = Object.assign({}, originalModules);
+ const proxiedModules: WebpackRequire["m"] = {};
+
+ for (const id in originalModules) {
+ // If we have eagerPatches enabled we have to patch the pre-populated factories
+ if (Settings.eagerPatches) {
+ proxiedModules[id] = patchFactory(id, originalModules[id]);
+ } else {
+ proxiedModules[id] = originalModules[id];
+ }
- // Clear the original object so pre-populated factories are patched
- for (const propName in originalModules) {
- delete originalModules[propName];
+ // Clear the original object so pre-populated factories are patched if eagerPatches are disabled
+ delete originalModules[id];
}
- Object.setPrototypeOf(originalModules, new Proxy(modules, modulesProxyhandler));
+ // @ts-ignore
+ originalModules.$$proxiedModules = proxiedModules;
+ allProxiedModules.add(proxiedModules);
+ Object.setPrototypeOf(originalModules, new Proxy(proxiedModules, modulesProxyhandler));
}
Object.defineProperty(this, "m", {
value: originalModules,
- configurable: true
+ configurable: true,
+ enumerable: true,
+ writable: true
});
}
});
let webpackNotInitializedLogged = false;
-function patchFactory(id: PropertyKey, mod: ModuleFactory) {
+function patchFactory(id: PropertyKey, factory: ModuleFactory) {
for (const factoryListener of factoryListeners) {
try {
- factoryListener(mod);
+ factoryListener(factory);
} catch (err) {
logger.error("Error in Webpack factory listener:\n", err, factoryListener);
}
}
- const originalMod = mod;
+ const originalFactory = factory;
const patchedBy = new Set();
// Discords Webpack chunks for some ungodly reason contain random
@@ -164,7 +211,7 @@ function patchFactory(id: PropertyKey, mod: ModuleFactory) {
// cause issues.
//
// 0, prefix is to turn it into an expression: 0,function(){} would be invalid syntax without the 0,
- let code: string = "0," + mod.toString().replaceAll("\n", "");
+ let code: string = "0," + factory.toString().replaceAll("\n", "");
for (let i = 0; i < patches.length; i++) {
const patch = patches[i];
@@ -179,14 +226,14 @@ function patchFactory(id: PropertyKey, mod: ModuleFactory) {
patchedBy.add(patch.plugin);
const executePatch = traceFunction(`patch by ${patch.plugin}`, (match: string | RegExp, replace: string) => code.replace(match, replace));
- const previousMod = mod;
+ const previousFactory = factory;
const previousCode = code;
// We change all patch.replacement to array in plugins/index
for (const replacement of patch.replacement as PatchReplacement[]) {
if (replacement.predicate && !replacement.predicate()) continue;
- const lastMod = mod;
+ const lastFactory = factory;
const lastCode = code;
canonicalizeReplacement(replacement, patch.plugin);
@@ -203,7 +250,7 @@ function patchFactory(id: PropertyKey, mod: ModuleFactory) {
if (patch.group) {
logger.warn(`Undoing patch group ${patch.find} by ${patch.plugin} because replacement ${replacement.match} had no effect`);
- mod = previousMod;
+ factory = previousFactory;
code = previousCode;
patchedBy.delete(patch.plugin);
break;
@@ -213,7 +260,7 @@ function patchFactory(id: PropertyKey, mod: ModuleFactory) {
}
code = newCode;
- mod = (0, eval)(`// Webpack Module ${String(id)} - Patched by ${[...patchedBy].join(", ")}\n${newCode}\n//# sourceURL=WebpackModule${String(id)}`);
+ factory = (0, eval)(`// Webpack Module ${String(id)} - Patched by ${[...patchedBy].join(", ")}\n${newCode}\n//# sourceURL=WebpackModule${String(id)}`);
} catch (err) {
logger.error(`Patch by ${patch.plugin} errored (Module id is ${String(id)}): ${replacement.match}\n`, err);
@@ -254,12 +301,12 @@ function patchFactory(id: PropertyKey, mod: ModuleFactory) {
if (patch.group) {
logger.warn(`Undoing patch group ${patch.find} by ${patch.plugin} because replacement ${replacement.match} errored`);
- mod = previousMod;
+ factory = previousFactory;
code = previousCode;
break;
}
- mod = lastMod;
+ factory = lastFactory;
code = lastCode;
}
}
@@ -268,23 +315,29 @@ function patchFactory(id: PropertyKey, mod: ModuleFactory) {
}
const patchedFactory: ModuleFactory = (module, exports, require) => {
+ // @ts-ignore
+ originalFactory.$$vencordRequired = true;
+ for (const proxiedModules of allProxiedModules) {
+ proxiedModules[id] = originalFactory;
+ }
+
if (wreq == null && IS_DEV) {
if (!webpackNotInitializedLogged) {
webpackNotInitializedLogged = true;
logger.error("WebpackRequire was not initialized, running modules without patches instead.");
}
- return void originalMod(module, exports, require);
+ return void originalFactory(module, exports, require);
}
try {
- mod(module, exports, require);
+ factory(module, exports, require);
} catch (err) {
// Just rethrow Discord errors
- if (mod === originalMod) throw err;
+ if (factory === originalFactory) throw err;
logger.error("Error in patched module", err);
- return void originalMod(module, exports, require);
+ return void originalFactory(module, exports, require);
}
// Webpack sometimes sets the value of module.exports directly, so assign exports to it to make sure we properly handle it
@@ -297,8 +350,8 @@ function patchFactory(id: PropertyKey, mod: ModuleFactory) {
Object.defineProperty(require.c, id, {
value: require.c[id],
configurable: true,
- writable: true,
- enumerable: false
+ enumerable: false,
+ writable: true
});
return;
}
@@ -326,9 +379,9 @@ function patchFactory(id: PropertyKey, mod: ModuleFactory) {
}
};
- patchedFactory.toString = originalMod.toString.bind(originalMod);
+ patchedFactory.toString = originalFactory.toString.bind(originalFactory);
// @ts-ignore
- patchedFactory.$$vencordOriginal = originalMod;
+ patchedFactory.$$vencordOriginal = originalFactory;
return patchedFactory;
}
From ac61a0377fd49317f22802d31d9653f7641f6d24 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Thu, 23 May 2024 06:09:47 -0300
Subject: [PATCH 015/125] clean up
---
src/plugins/devCompanion.dev/index.tsx | 3 +--
src/webpack/webpack.ts | 2 +-
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/src/plugins/devCompanion.dev/index.tsx b/src/plugins/devCompanion.dev/index.tsx
index 37834e6a2e..819360cfec 100644
--- a/src/plugins/devCompanion.dev/index.tsx
+++ b/src/plugins/devCompanion.dev/index.tsx
@@ -160,8 +160,7 @@ function initWs(isManual = false) {
return reply("Expected exactly one 'find' matches, found " + keys.length);
const mod = candidates[keys[0]];
- // @ts-ignore
- let src = String(mod.$$vencordOriginal ?? mod).replaceAll("\n", "");
+ let src = mod.toString().replaceAll("\n", "");
if (src.startsWith("function(")) {
src = "0," + src;
diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts
index 052351b793..a428b3b557 100644
--- a/src/webpack/webpack.ts
+++ b/src/webpack/webpack.ts
@@ -501,7 +501,7 @@ export function search(...filters: Array) {
outer:
for (const id in factories) {
// @ts-ignore
- const factory = factories[id].$$vencordOriginal ?? factories[id];
+ const factory = factories[id];
const str: string = factory.toString();
for (const filter of filters) {
if (typeof filter === "string" && !str.includes(filter)) continue outer;
From cfb493c593b2aadb1c5681ca52c66231deb8a36a Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Thu, 23 May 2024 06:22:01 -0300
Subject: [PATCH 016/125] make reporter use eagerPatches
---
scripts/generateReport.ts | 9 +++------
1 file changed, 3 insertions(+), 6 deletions(-)
diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts
index 7fc435249f..7ef99b9db6 100644
--- a/scripts/generateReport.ts
+++ b/scripts/generateReport.ts
@@ -324,6 +324,9 @@ async function runtime(token: string) {
});
});
+ // Enable eagerPatches to make all patches apply regardless of the module being required
+ Vencord.Settings.eagerPatches = false;
+
let wreq: typeof Vencord.Webpack.wreq;
const { canonicalizeMatch, Logger } = Vencord.Util;
@@ -493,12 +496,6 @@ async function runtime(token: string) {
}
}));
- // Call the getter for all the values in the modules object
- // So modules that were not required get patched by our proxy
- for (const id in wreq.m) {
- wreq.m[id];
- }
-
console.log("[PUP_DEBUG]", "Finished loading all chunks!");
for (const patch of Vencord.Plugins.patches) {
From 7371abbaec9a5af3e79bcba58a21c112c3c0d122 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Thu, 23 May 2024 06:29:07 -0300
Subject: [PATCH 017/125] lmao
---
scripts/generateReport.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts
index 7ef99b9db6..9026ee788f 100644
--- a/scripts/generateReport.ts
+++ b/scripts/generateReport.ts
@@ -325,7 +325,7 @@ async function runtime(token: string) {
});
// Enable eagerPatches to make all patches apply regardless of the module being required
- Vencord.Settings.eagerPatches = false;
+ Vencord.Settings.eagerPatches = true;
let wreq: typeof Vencord.Webpack.wreq;
From 1e96638219c0b1d96a754e36021549d6ff48b1f5 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Thu, 23 May 2024 06:38:22 -0300
Subject: [PATCH 018/125] oops!
---
src/webpack/webpack.ts | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts
index a428b3b557..20bd93e3bd 100644
--- a/src/webpack/webpack.ts
+++ b/src/webpack/webpack.ts
@@ -500,7 +500,6 @@ export function search(...filters: Array) {
const factories = wreq.m;
outer:
for (const id in factories) {
- // @ts-ignore
const factory = factories[id];
const str: string = factory.toString();
for (const filter of filters) {
From 7b7b87316d71ff3a0e3cea36b6897f9b203e14c6 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Thu, 23 May 2024 07:09:03 -0300
Subject: [PATCH 019/125] HORROR
---
src/webpack/patchWebpack.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index f5ebea7e17..e00ae0b134 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -19,7 +19,7 @@ const initCallbackRegex = canonicalizeMatch(/{return \i\(".+?"\)}/);
const allProxiedModules = new Set();
-const modulesProxyhandler: ProxyHandler = {
+const modulesProxyHandler: ProxyHandler = {
...Object.fromEntries(Object.getOwnPropertyNames(Reflect).map(propName =>
[propName, (...args: any[]) => Reflect[propName](...args)]
)),
@@ -176,7 +176,7 @@ Object.defineProperty(Function.prototype, "m", {
// @ts-ignore
originalModules.$$proxiedModules = proxiedModules;
allProxiedModules.add(proxiedModules);
- Object.setPrototypeOf(originalModules, new Proxy(proxiedModules, modulesProxyhandler));
+ Object.setPrototypeOf(originalModules, new Proxy(proxiedModules, modulesProxyHandler));
}
Object.defineProperty(this, "m", {
From bdd4b0f14301abd0c3daa11ff664071f6e7f9aaa Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Thu, 23 May 2024 19:53:19 -0300
Subject: [PATCH 020/125] Make eagerPatches an internal setting
---
src/components/VencordSettings/VencordTab.tsx | 5 -----
src/webpack/patchWebpack.ts | 17 ++++++++++++-----
2 files changed, 12 insertions(+), 10 deletions(-)
diff --git a/src/components/VencordSettings/VencordTab.tsx b/src/components/VencordSettings/VencordTab.tsx
index c9702b4350..c0a66fdc79 100644
--- a/src/components/VencordSettings/VencordTab.tsx
+++ b/src/components/VencordSettings/VencordTab.tsx
@@ -66,11 +66,6 @@ function VencordSettings() {
title: "Enable React Developer Tools",
note: "Requires a full restart"
},
- {
- key: "eagerPatches",
- title: "Apply Vencord patches before they are needed",
- note: "Increases startup timing, but may make app usage more fluid. Note that the difference of having this on or off is minimal."
- },
!IS_WEB && (!IS_DISCORD_DESKTOP || !isWindows ? {
key: "frameless",
title: "Disable the window frame",
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index e00ae0b134..31aaa911c7 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -127,10 +127,17 @@ Object.defineProperty(Function.prototype, "O", {
configurable: true,
set(v: OnChunksLoaded["j"]) {
- // @ts-ignore
- delete onChunksLoaded.j;
- onChunksLoaded.j = v;
- originalOnChunksLoaded.j = v;
+ function setValue(target: any) {
+ Object.defineProperty(target, "j", {
+ value: v,
+ configurable: true,
+ enumerable: true,
+ writable: true
+ });
+ }
+
+ setValue(onChunksLoaded);
+ setValue(originalOnChunksLoaded);
}
});
}
@@ -318,7 +325,7 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
// @ts-ignore
originalFactory.$$vencordRequired = true;
for (const proxiedModules of allProxiedModules) {
- proxiedModules[id] = originalFactory;
+ Reflect.set(proxiedModules, id, originalFactory);
}
if (wreq == null && IS_DEV) {
From bd95cc449f7e6e39fb37449520418f2c1f1e8238 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Thu, 23 May 2024 19:54:40 -0300
Subject: [PATCH 021/125] Exit script if a chunk failed to load
---
scripts/generateReport.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts
index 9026ee788f..2035ead096 100644
--- a/scripts/generateReport.ts
+++ b/scripts/generateReport.ts
@@ -386,7 +386,7 @@ async function runtime(token: string) {
await Promise.all(
Array.from(validChunkGroups)
.map(([chunkIds]) =>
- Promise.all(chunkIds.map(id => wreq.e(id).catch(() => { })))
+ Promise.all(chunkIds.map(id => wreq.e(id)))
)
);
From f5be78d1017afb0fbfd160ec3991515b93f6ca91 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Thu, 23 May 2024 20:02:41 -0300
Subject: [PATCH 022/125] Patching toString -> String
---
scripts/generateReport.ts | 12 ++++++------
src/components/VencordSettings/PatchHelperTab.tsx | 2 +-
src/plugins/devCompanion.dev/index.tsx | 2 +-
src/webpack/patchWebpack.ts | 4 ++--
src/webpack/webpack.ts | 8 ++++----
5 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts
index 2035ead096..32e6a3262f 100644
--- a/scripts/generateReport.ts
+++ b/scripts/generateReport.ts
@@ -441,7 +441,7 @@ async function runtime(token: string) {
Vencord.Webpack.factoryListeners.add(factory => {
let isResolved = false;
- searchAndLoadLazyChunks(factory.toString()).then(() => isResolved = true);
+ searchAndLoadLazyChunks(String(factory)).then(() => isResolved = true);
chunksSearchPromises.push(() => isResolved);
});
@@ -451,7 +451,7 @@ async function runtime(token: string) {
setTimeout(() => {
for (const factoryId in wreq.m) {
let isResolved = false;
- searchAndLoadLazyChunks(wreq.m[factoryId].toString()).then(() => isResolved = true);
+ searchAndLoadLazyChunks(String(wreq.m[factoryId])).then(() => isResolved = true);
chunksSearchPromises.push(() => isResolved);
}
@@ -470,7 +470,7 @@ async function runtime(token: string) {
const allChunks = [] as string[];
// Matches "id" or id:
- for (const currentMatch of wreq.u.toString().matchAll(/(?:"(\d+?)")|(?:(\d+?):)/g)) {
+ for (const currentMatch of String(wreq.u).matchAll(/(?:"(\d+?)")|(?:(\d+?):)/g)) {
const id = currentMatch[1] ?? currentMatch[2];
if (id == null) continue;
@@ -525,7 +525,7 @@ async function runtime(token: string) {
const [code, matcher] = args;
const module = Vencord.Webpack.findModuleFactory(...code);
- if (module) result = module.toString().match(canonicalizeMatch(matcher));
+ if (module) result = String(module).match(canonicalizeMatch(matcher));
} else {
// @ts-ignore
result = Vencord.Webpack[method](...args);
@@ -534,8 +534,8 @@ async function runtime(token: string) {
if (result == null || ("$$vencordInternal" in result && result.$$vencordInternal() == null)) throw "a rock at ben shapiro";
} catch (e) {
let logMessage = searchType;
- if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") logMessage += `(${args[0].toString().slice(0, 147)}...)`;
- else if (method === "extractAndLoadChunks") logMessage += `([${args[0].map(arg => `"${arg}"`).join(", ")}], ${args[1].toString()})`;
+ if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") logMessage += `(${String(args[0]).slice(0, 147)}...)`;
+ else if (method === "extractAndLoadChunks") logMessage += `([${args[0].map(arg => `"${arg}"`).join(", ")}], ${String(args[1])})`;
else logMessage += `(${args.map(arg => `"${arg}"`).join(", ")})`;
console.log("[PUP_WEBPACK_FIND_FAIL]", logMessage);
diff --git a/src/components/VencordSettings/PatchHelperTab.tsx b/src/components/VencordSettings/PatchHelperTab.tsx
index e09a1dbf37..f9204669b2 100644
--- a/src/components/VencordSettings/PatchHelperTab.tsx
+++ b/src/components/VencordSettings/PatchHelperTab.tsx
@@ -56,7 +56,7 @@ function ReplacementComponent({ module, match, replacement, setReplacementError
const [compileResult, setCompileResult] = React.useState<[boolean, string]>();
const [patchedCode, matchResult, diff] = React.useMemo(() => {
- const src: string = fact.toString().replaceAll("\n", "");
+ const src: string = String(fact).replaceAll("\n", "");
try {
new RegExp(match);
diff --git a/src/plugins/devCompanion.dev/index.tsx b/src/plugins/devCompanion.dev/index.tsx
index 819360cfec..3842925f2b 100644
--- a/src/plugins/devCompanion.dev/index.tsx
+++ b/src/plugins/devCompanion.dev/index.tsx
@@ -160,7 +160,7 @@ function initWs(isManual = false) {
return reply("Expected exactly one 'find' matches, found " + keys.length);
const mod = candidates[keys[0]];
- let src = mod.toString().replaceAll("\n", "");
+ let src = String(mod).replaceAll("\n", "");
if (src.startsWith("function(")) {
src = "0," + src;
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index 31aaa911c7..27db709705 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -91,7 +91,7 @@ Object.defineProperty(Function.prototype, "O", {
const originalOnChunksLoaded = onChunksLoaded;
onChunksLoaded = function (result, chunkIds, callback, priority) {
- if (callback != null && initCallbackRegex.test(callback.toString())) {
+ if (callback != null && initCallbackRegex.test(String(callback))) {
Object.defineProperty(this, "O", {
value: originalOnChunksLoaded,
configurable: true,
@@ -218,7 +218,7 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
// cause issues.
//
// 0, prefix is to turn it into an expression: 0,function(){} would be invalid syntax without the 0,
- let code: string = "0," + factory.toString().replaceAll("\n", "");
+ let code: string = "0," + String(factory).replaceAll("\n", "");
for (let i = 0; i < patches.length; i++) {
const patch = patches[i];
diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts
index 20bd93e3bd..adc88ef834 100644
--- a/src/webpack/webpack.ts
+++ b/src/webpack/webpack.ts
@@ -218,7 +218,7 @@ export const findBulk = traceFunction("findBulk", function findBulk(...filterFns
export const findModuleId = traceFunction("findModuleId", function findModuleId(...code: string[]) {
outer:
for (const id in wreq.m) {
- const str = wreq.m[id].toString();
+ const str = String(wreq.m[id]);
for (const c of code) {
if (!str.includes(c)) continue outer;
@@ -420,7 +420,7 @@ export async function extractAndLoadChunks(code: string[], matcher: RegExp = Def
return;
}
- const match = module.toString().match(canonicalizeMatch(matcher));
+ const match = String(module).match(canonicalizeMatch(matcher));
if (!match) {
const err = new Error("extractAndLoadChunks: Couldn't find chunk loading in module factory code");
logger.warn(err, "Code:", code, "Matcher:", matcher);
@@ -501,7 +501,7 @@ export function search(...filters: Array) {
outer:
for (const id in factories) {
const factory = factories[id];
- const str: string = factory.toString();
+ const str: string = String(factory);
for (const filter of filters) {
if (typeof filter === "string" && !str.includes(filter)) continue outer;
if (filter instanceof RegExp && !filter.test(str)) continue outer;
@@ -529,7 +529,7 @@ export function extract(id: PropertyKey) {
// WARNING: This module was extracted to be more easily readable.
// This module is NOT ACTUALLY USED! This means putting breakpoints will have NO EFFECT!!
-0,${mod.toString()}
+0,${String(mod)}
//# sourceURL=ExtractedWebpackModule${String(id)}
`;
const extracted: ModuleFactory = (0, eval)(code);
From 8e9434cdd5bb99e8171f80126960141dd4b8f85e Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Thu, 23 May 2024 21:57:12 -0300
Subject: [PATCH 023/125] Make reporter faster
---
scripts/generateReport.ts | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts
index 32e6a3262f..bb5c39ac75 100644
--- a/scripts/generateReport.ts
+++ b/scripts/generateReport.ts
@@ -440,10 +440,13 @@ async function runtime(token: string) {
wreq = webpackRequire;
Vencord.Webpack.factoryListeners.add(factory => {
- let isResolved = false;
- searchAndLoadLazyChunks(String(factory)).then(() => isResolved = true);
+ // 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);
+ chunksSearchPromises.push(() => isResolved);
+ }, 0);
});
// setImmediate to only search the initial factories after Discord initialized the app
From 9740d28530fda6957683cd115adfb3bc73774738 Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Fri, 24 May 2024 01:05:17 +0200
Subject: [PATCH 024/125] discord why tf would u roll back to 10 days old
build???
---
src/plugins/_core/settings.tsx | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/src/plugins/_core/settings.tsx b/src/plugins/_core/settings.tsx
index b743b00666..fd221d27e4 100644
--- a/src/plugins/_core/settings.tsx
+++ b/src/plugins/_core/settings.tsx
@@ -56,6 +56,26 @@ export default definePlugin({
}
]
},
+ // Discord Stable
+ // FIXME: remove once change merged to stable
+ {
+ find: "Messages.ACTIVITY_SETTINGS",
+ replacement: {
+ get match() {
+ switch (Settings.plugins.Settings.settingsLocation) {
+ case "top": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.USER_SETTINGS/;
+ case "aboveNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.BILLING_SETTINGS/;
+ case "belowNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.APP_SETTINGS/;
+ case "belowActivity": return /(?<=\{section:(\i\.\i)\.DIVIDER},)\{section:"changelog"/;
+ case "bottom": return /\{section:(\i\.\i)\.CUSTOM,\s*element:.+?}/;
+ case "aboveActivity":
+ default:
+ return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.ACTIVITY_SETTINGS/;
+ }
+ },
+ replace: "...$self.makeSettingsCategories($1),$&"
+ }
+ },
{
find: "Messages.ACTIVITY_SETTINGS",
replacement: {
From a120b35f4f6d14313f1b476e8a6e5091676c2df8 Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Fri, 24 May 2024 01:07:07 +0200
Subject: [PATCH 025/125] Bump to v1.8.6
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index a99b0ad706..04b811e9c4 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "vencord",
"private": "true",
- "version": "1.8.5",
+ "version": "1.8.6",
"description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": {
From c5541d297d37fd4a737b4d4e1fa2ecf348e1a9d5 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Thu, 23 May 2024 21:48:12 -0300
Subject: [PATCH 026/125] Optimize slowest patches
---
src/plugins/_api/notices.ts | 2 +-
src/plugins/alwaysAnimate/index.ts | 4 ++--
src/plugins/betterFolders/index.tsx | 4 ++--
src/plugins/betterSettings/index.tsx | 4 ++--
src/plugins/colorSighted/index.ts | 4 ++--
src/plugins/fakeNitro/index.tsx | 4 ++--
src/plugins/ignoreActivities/index.tsx | 8 ++++----
src/plugins/memberCount/index.tsx | 4 ++--
src/plugins/pinDms/index.tsx | 4 ++--
src/plugins/resurrectHome/index.tsx | 4 ++--
src/plugins/showHiddenChannels/index.tsx | 12 ++++++------
src/plugins/spotifyCrack/index.ts | 4 ++--
src/plugins/superReactionTweaks/index.ts | 4 ++--
src/plugins/viewIcons/index.tsx | 4 ++--
14 files changed, 33 insertions(+), 33 deletions(-)
diff --git a/src/plugins/_api/notices.ts b/src/plugins/_api/notices.ts
index 90ae6ded9a..0c6f6e1db8 100644
--- a/src/plugins/_api/notices.ts
+++ b/src/plugins/_api/notices.ts
@@ -29,7 +29,7 @@ export default definePlugin({
find: '"NoticeStore"',
replacement: [
{
- match: /\i=null;(?=.{0,80}getPremiumSubscription\(\))/g,
+ match: /(?<=!1;)\i=null;(?=.{0,80}getPremiumSubscription\(\))/g,
replace: "if(Vencord.Api.Notices.currentNotice)return false;$&"
},
{
diff --git a/src/plugins/alwaysAnimate/index.ts b/src/plugins/alwaysAnimate/index.ts
index dbec3b4e32..20cb4f974b 100644
--- a/src/plugins/alwaysAnimate/index.ts
+++ b/src/plugins/alwaysAnimate/index.ts
@@ -31,10 +31,10 @@ export default definePlugin({
// Some modules match the find but the replacement is returned untouched
noWarn: true,
replacement: {
- match: /canAnimate:.+?(?=([,}].*?\)))/g,
+ match: /canAnimate:.+?([,}].*?\))/g,
replace: (m, rest) => {
const destructuringMatch = rest.match(/}=.+/);
- if (destructuringMatch == null) return "canAnimate:!0";
+ if (destructuringMatch == null) return `canAnimate:!0${rest}`;
return m;
}
}
diff --git a/src/plugins/betterFolders/index.tsx b/src/plugins/betterFolders/index.tsx
index 795f19901b..38e1b84127 100644
--- a/src/plugins/betterFolders/index.tsx
+++ b/src/plugins/betterFolders/index.tsx
@@ -112,8 +112,8 @@ export default definePlugin({
replacement: [
// Create the isBetterFolders variable in the GuildsBar component
{
- match: /(?<=let{disableAppDownload:\i=\i\.isPlatformEmbedded,isOverlay:.+?)(?=}=\i,)/,
- replace: ",isBetterFolders"
+ match: /let{disableAppDownload:\i=\i\.isPlatformEmbedded,isOverlay:.+?(?=}=\i,)/,
+ replace: "$&,isBetterFolders"
},
// If we are rendering the Better Folders sidebar, we filter out guilds that are not in folders and unexpanded folders
{
diff --git a/src/plugins/betterSettings/index.tsx b/src/plugins/betterSettings/index.tsx
index e90e5c82a2..e0267e4b09 100644
--- a/src/plugins/betterSettings/index.tsx
+++ b/src/plugins/betterSettings/index.tsx
@@ -111,8 +111,8 @@ export default definePlugin({
{ // Load menu TOC eagerly
find: "Messages.USER_SETTINGS_WITH_BUILD_OVERRIDE.format",
replacement: {
- match: /(?<=(\i)\(this,"handleOpenSettingsContextMenu",.{0,100}?openContextMenuLazy.{0,100}?(await Promise\.all[^};]*?\)\)).*?,)(?=\1\(this)/,
- replace: "(async ()=>$2)(),"
+ match: /(\i)\(this,"handleOpenSettingsContextMenu",.{0,100}?openContextMenuLazy.{0,100}?(await Promise\.all[^};]*?\)\)).*?,(?=\1\(this)/,
+ replace: "$&(async ()=>$2)(),"
},
predicate: () => settings.store.eagerLoad
},
diff --git a/src/plugins/colorSighted/index.ts b/src/plugins/colorSighted/index.ts
index d741aaae65..025cfb94e6 100644
--- a/src/plugins/colorSighted/index.ts
+++ b/src/plugins/colorSighted/index.ts
@@ -34,9 +34,9 @@ export default definePlugin({
{
find: ".AVATAR_STATUS_MOBILE_16;",
replacement: {
- match: /(?<=fromIsMobile:\i=!0,.+?)status:(\i)/,
+ match: /(fromIsMobile:\i=!0,.+?)status:(\i)/,
// Rename field to force it to always use "online"
- replace: 'status_$:$1="online"'
+ replace: '$1status_$:$2="online"'
}
}
]
diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx
index 9c8af1e7ce..4ab0e18ee8 100644
--- a/src/plugins/fakeNitro/index.tsx
+++ b/src/plugins/fakeNitro/index.tsx
@@ -344,8 +344,8 @@ export default definePlugin({
{
// Patch the stickers array to add fake nitro stickers
predicate: () => settings.store.transformStickers,
- match: /(?<=renderStickersAccessories\((\i)\){let (\i)=\(0,\i\.\i\)\(\i\).+?;)/,
- replace: (_, message, stickers) => `${stickers}=$self.patchFakeNitroStickers(${stickers},${message});`
+ match: /renderStickersAccessories\((\i)\){let (\i)=\(0,\i\.\i\)\(\i\).+?;/,
+ replace: (m, message, stickers) => `${m}${stickers}=$self.patchFakeNitroStickers(${stickers},${message});`
},
{
// Filter attachments to remove fake nitro stickers or emojis
diff --git a/src/plugins/ignoreActivities/index.tsx b/src/plugins/ignoreActivities/index.tsx
index e2262129d8..f687a0cafb 100644
--- a/src/plugins/ignoreActivities/index.tsx
+++ b/src/plugins/ignoreActivities/index.tsx
@@ -228,15 +228,15 @@ export default definePlugin({
{
find: ".activityTitleText,variant",
replacement: {
- match: /(?<=\i\.activityTitleText.+?children:(\i)\.name.*?}\),)/,
- replace: (_, props) => `$self.renderToggleActivityButton(${props}),`
+ match: /\.activityTitleText.+?children:(\i)\.name.*?}\),/,
+ replace: (m, props) => `${m}$self.renderToggleActivityButton(${props}),`
},
},
{
find: ".activityCardDetails,children",
replacement: {
- match: /(?<=\i\.activityCardDetails.+?children:(\i\.application)\.name.*?}\),)/,
- replace: (_, props) => `$self.renderToggleActivityButton(${props}),`
+ match: /\.activityCardDetails.+?children:(\i\.application)\.name.*?}\),/,
+ replace: (m, props) => `${m}$self.renderToggleActivityButton(${props}),`
}
}
],
diff --git a/src/plugins/memberCount/index.tsx b/src/plugins/memberCount/index.tsx
index 92e9a2057e..28ecb9db73 100644
--- a/src/plugins/memberCount/index.tsx
+++ b/src/plugins/memberCount/index.tsx
@@ -70,8 +70,8 @@ export default definePlugin({
{
find: ".invitesDisabledTooltip",
replacement: {
- match: /(?<=\.VIEW_AS_ROLES_MENTIONS_WARNING.{0,100})]/,
- replace: ",$self.renderTooltip(arguments[0].guild)]"
+ match: /\.VIEW_AS_ROLES_MENTIONS_WARNING.{0,100}(?=])/,
+ replace: "$&,$self.renderTooltip(arguments[0].guild)"
},
predicate: () => settings.store.toolTip
}
diff --git a/src/plugins/pinDms/index.tsx b/src/plugins/pinDms/index.tsx
index 60484561a4..033552593d 100644
--- a/src/plugins/pinDms/index.tsx
+++ b/src/plugins/pinDms/index.tsx
@@ -111,8 +111,8 @@ export default definePlugin({
replace: "$self.getScrollOffset(arguments[0],$1,this.props.padding,this.state.preRenderedChildren,$&)"
},
{
- match: /(?<=scrollToChannel\(\i\){.{1,300})this\.props\.privateChannelIds/,
- replace: "[...$&,...$self.getAllUncollapsedChannels()]"
+ match: /(scrollToChannel\(\i\){.{1,300})(this\.props\.privateChannelIds)/,
+ replace: "$1[...$2,...$self.getAllUncollapsedChannels()]"
},
]
diff --git a/src/plugins/resurrectHome/index.tsx b/src/plugins/resurrectHome/index.tsx
index 70827e08f5..5193090ea6 100644
--- a/src/plugins/resurrectHome/index.tsx
+++ b/src/plugins/resurrectHome/index.tsx
@@ -134,8 +134,8 @@ export default definePlugin({
{
find: '"MessageActionCreators"',
replacement: {
- match: /(?<=focusMessage\(\i\){.+?)(?=focus:{messageId:(\i)})/,
- replace: "after:$1,"
+ match: /focusMessage\(\i\){.+?(?=focus:{messageId:(\i)})/,
+ replace: "$&after:$1,"
}
},
// Force Server Home instead of Server Guide
diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx
index f08bc2d1d8..c120d72d8d 100644
--- a/src/plugins/showHiddenChannels/index.tsx
+++ b/src/plugins/showHiddenChannels/index.tsx
@@ -89,8 +89,8 @@ export default definePlugin({
},
// Remove permission checking for getRenderLevel function
{
- match: /(?<=getRenderLevel\(\i\){.+?return)!\i\.\i\.can\(\i\.\i\.VIEW_CHANNEL,this\.record\)\|\|/,
- replace: " "
+ match: /(getRenderLevel\(\i\){.+?return)!\i\.\i\.can\(\i\.\i\.VIEW_CHANNEL,this\.record\)\|\|/,
+ replace: (_, rest) => `${rest} `
}
]
},
@@ -159,8 +159,8 @@ export default definePlugin({
replacement: [
// Make the channel appear as muted if it's hidden
{
- match: /(?<={channel:(\i),name:\i,muted:(\i).+?;)/,
- replace: (_, channel, muted) => `${muted}=$self.isHiddenChannel(${channel})?true:${muted};`
+ match: /{channel:(\i),name:\i,muted:(\i).+?;/,
+ replace: (m, channel, muted) => `${m}${muted}=$self.isHiddenChannel(${channel})?true:${muted};`
},
// Add the hidden eye icon if the channel is hidden
{
@@ -186,8 +186,8 @@ export default definePlugin({
{
// Hide unreads
predicate: () => settings.store.hideUnreads === true,
- match: /(?<={channel:(\i),name:\i,.+?unread:(\i).+?;)/,
- replace: (_, channel, unread) => `${unread}=$self.isHiddenChannel(${channel})?false:${unread};`
+ match: /{channel:(\i),name:\i,.+?unread:(\i).+?;/,
+ replace: (m, channel, unread) => `${m}${unread}=$self.isHiddenChannel(${channel})?false:${unread};`
}
]
},
diff --git a/src/plugins/spotifyCrack/index.ts b/src/plugins/spotifyCrack/index.ts
index 1beccad608..37504be2e3 100644
--- a/src/plugins/spotifyCrack/index.ts
+++ b/src/plugins/spotifyCrack/index.ts
@@ -60,8 +60,8 @@ export default definePlugin({
},
{
predicate: () => settings.store.keepSpotifyActivityOnIdle,
- match: /(?<=shouldShowActivity\(\){.{0,50})&&!\i\.\i\.isIdle\(\)/,
- replace: ""
+ match: /(shouldShowActivity\(\){.{0,50})&&!\i\.\i\.isIdle\(\)/,
+ replace: "$1"
}
]
}
diff --git a/src/plugins/superReactionTweaks/index.ts b/src/plugins/superReactionTweaks/index.ts
index 89197b4c3a..7878ba630c 100644
--- a/src/plugins/superReactionTweaks/index.ts
+++ b/src/plugins/superReactionTweaks/index.ts
@@ -42,8 +42,8 @@ export default definePlugin({
{
find: ",BURST_REACTION_EFFECT_PLAY",
replacement: {
- match: /(?<=BURST_REACTION_EFFECT_PLAY:\i=>{.{50,100})(\i\(\i,\i\))>=\d+/,
- replace: "!$self.shouldPlayBurstReaction($1)"
+ match: /(BURST_REACTION_EFFECT_PLAY:\i=>{.{50,100})(\i\(\i,\i\))>=\d+/,
+ replace: "$1!$self.shouldPlayBurstReaction($2)"
}
},
{
diff --git a/src/plugins/viewIcons/index.tsx b/src/plugins/viewIcons/index.tsx
index 359365ee4d..09254d511d 100644
--- a/src/plugins/viewIcons/index.tsx
+++ b/src/plugins/viewIcons/index.tsx
@@ -206,8 +206,8 @@ export default definePlugin({
{
find: ".avatarPositionPanel",
replacement: {
- match: /(?<=avatarWrapperNonUserBot.{0,50})onClick:(\i\|\|\i)\?void 0(?<=,avatarSrc:(\i).+?)/,
- replace: "style:($1)?{cursor:\"pointer\"}:{},onClick:$1?()=>{$self.openImage($2)}"
+ match: /(avatarWrapperNonUserBot.{0,50})onClick:(\i\|\|\i)\?void 0(?<=,avatarSrc:(\i).+?)/,
+ replace: "$1style:($2)?{cursor:\"pointer\"}:{},onClick:$2?()=>{$self.openImage($3)}"
}
},
// Group DMs top small & large icon
From 5e762ddd044afb5d770f47b21044926e557e827f Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Thu, 23 May 2024 23:18:43 -0300
Subject: [PATCH 027/125] Add names to Modules objects
---
src/webpack/patchWebpack.ts | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index 27db709705..51d0b9a938 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -167,6 +167,8 @@ Object.defineProperty(Function.prototype, "m", {
// The new object which will contain the factories
const proxiedModules: WebpackRequire["m"] = {};
+ // @ts-ignore
+ proxiedModules[Symbol.toStringTag] = "ProxiedModules";
for (const id in originalModules) {
// If we have eagerPatches enabled we have to patch the pre-populated factories
@@ -180,9 +182,10 @@ Object.defineProperty(Function.prototype, "m", {
delete originalModules[id];
}
- // @ts-ignore
- originalModules.$$proxiedModules = proxiedModules;
allProxiedModules.add(proxiedModules);
+
+ // @ts-ignore
+ originalModules[Symbol.toStringTag] = "OriginalModules";
Object.setPrototypeOf(originalModules, new Proxy(proxiedModules, modulesProxyHandler));
}
From d8c65599672a0fb4f67b63b3e7f4226a99a8ed86 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Thu, 23 May 2024 23:56:29 -0300
Subject: [PATCH 028/125] I forgot this
---
src/webpack/patchWebpack.ts | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index 51d0b9a938..4aa468fe8a 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -24,7 +24,7 @@ const modulesProxyHandler: ProxyHandler = {
[propName, (...args: any[]) => Reflect[propName](...args)]
)),
get: (target, p) => {
- const propValue = Reflect.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
@@ -36,7 +36,7 @@ const modulesProxyHandler: ProxyHandler = {
// This patches factories if eagerPatches are disabled
const patchedFactory = patchFactory(p, propValue);
- Reflect.set(target, p, patchedFactory);
+ Reflect.set(target, p, patchedFactory, target);
return patchedFactory;
},
@@ -44,10 +44,10 @@ const modulesProxyHandler: ProxyHandler = {
// $$vencordRequired means we are resetting the factory to its original after being required
// 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))) {
- return Reflect.set(target, p, newValue);
+ return Reflect.set(target, p, newValue, target);
}
- const existingFactory = Reflect.get(target, p);
+ const existingFactory = Reflect.get(target, p, target);
// Check if this factory is already patched
// @ts-ignore
@@ -59,7 +59,7 @@ const modulesProxyHandler: ProxyHandler = {
// Modules are only patched once, so we need to set the patched factory on all the modules
for (const proxiedModules of allProxiedModules) {
- Reflect.set(proxiedModules, p, patchedFactory);
+ Reflect.set(proxiedModules, p, patchedFactory, proxiedModules);
}
return true;
@@ -328,7 +328,7 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
// @ts-ignore
originalFactory.$$vencordRequired = true;
for (const proxiedModules of allProxiedModules) {
- Reflect.set(proxiedModules, id, originalFactory);
+ Reflect.set(proxiedModules, id, originalFactory, proxiedModules);
}
if (wreq == null && IS_DEV) {
From b38ab066d9a8109b3fa5631b1a6c05ac4a728e5e Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Fri, 24 May 2024 02:02:28 -0300
Subject: [PATCH 029/125] Preserve original modules prototype
---
src/webpack/patchWebpack.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index 4aa468fe8a..d27d450b31 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -166,7 +166,7 @@ Object.defineProperty(Function.prototype, "m", {
logger.info("Found Webpack module factory", stack.match(/\/assets\/(.+?\.js)/)?.[1] ?? "");
// The new object which will contain the factories
- const proxiedModules: WebpackRequire["m"] = {};
+ const proxiedModules: WebpackRequire["m"] = Object.create(Object.getPrototypeOf(originalModules));
// @ts-ignore
proxiedModules[Symbol.toStringTag] = "ProxiedModules";
From 9bbec66ea5e76dc46fc63d2346262ddf176edb35 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Fri, 24 May 2024 03:08:35 -0300
Subject: [PATCH 030/125] Take a different approach which requires less cursed
code
---
src/webpack/patchWebpack.ts | 104 +++++++++++++++++-------------------
1 file changed, 49 insertions(+), 55 deletions(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index d27d450b31..be9dfc9740 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -6,7 +6,6 @@
import { Settings } from "@api/Settings";
import { Logger } from "@utils/Logger";
-import { UNCONFIGURABLE_PROPERTIES } from "@utils/misc";
import { canonicalizeMatch, canonicalizeReplacement } from "@utils/patches";
import { PatchReplacement } from "@utils/types";
@@ -17,34 +16,36 @@ import { _initWebpack, beforeInitListeners, factoryListeners, ModuleFactory, mod
const logger = new Logger("WebpackInterceptor", "#8caaee");
const initCallbackRegex = canonicalizeMatch(/{return \i\(".+?"\)}/);
-const allProxiedModules = new Set();
+/** A set with all the module factories objects */
+const allModuleFactories = new Set();
-const modulesProxyHandler: ProxyHandler = {
- ...Object.fromEntries(Object.getOwnPropertyNames(Reflect).map(propName =>
- [propName, (...args: any[]) => Reflect[propName](...args)]
- )),
- get: (target, p) => {
- const propValue = Reflect.get(target, p, target);
+function defineModuleFactoryGetter(modules: WebpackRequire["m"], id: PropertyKey, factory: ModuleFactory) {
+ Object.defineProperty(modules, id, {
+ get: () => {
+ // $$vencordOriginal means the factory is already patched
+ // @ts-ignore
+ if (factory.$$vencordOriginal != null) {
+ return factory;
+ }
+ // This patches factories if eagerPatches are disabled
+ return (factory = patchFactory(id, factory));
+ },
+ configurable: true,
+ enumerable: true
+ });
+}
+
+const moduleFactoriesHandler: ProxyHandler = {
+ set: (target, p, newValue, receiver) => {
// 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
- if (propValue == null || Number.isNaN(Number(p)) || propValue.$$vencordOriginal != null || propValue.$$vencordRequired === true) {
- return propValue;
+ if (Number.isNaN(Number(p))) {
+ return Reflect.set(target, p, newValue, receiver);
}
- // This patches factories if eagerPatches are disabled
- const patchedFactory = patchFactory(p, propValue);
- Reflect.set(target, p, patchedFactory, target);
-
- return patchedFactory;
- },
- set: (target, p, newValue) => {
- // $$vencordRequired means we are resetting the factory to its original after being required
- // 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))) {
- return Reflect.set(target, p, newValue, target);
+ if (!Settings.eagerPatches) {
+ defineModuleFactoryGetter(target, p, newValue);
+ return true;
}
const existingFactory = Reflect.get(target, p, target);
@@ -58,18 +59,16 @@ const modulesProxyHandler: ProxyHandler = {
const patchedFactory = patchFactory(p, newValue);
// Modules are only patched once, so we need to set the patched factory on all the modules
- for (const proxiedModules of allProxiedModules) {
- Reflect.set(proxiedModules, p, patchedFactory, proxiedModules);
+ for (const moduleFactories of allModuleFactories) {
+ Object.defineProperty(moduleFactories, p, {
+ value: patchedFactory,
+ configurable: true,
+ enumerable: true,
+ writable: 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.
// 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
-// 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", {
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.
// This ensures we actually got the right one
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] ?? "");
- // The new object which will contain the factories
- const proxiedModules: WebpackRequire["m"] = Object.create(Object.getPrototypeOf(originalModules));
- // @ts-ignore
- proxiedModules[Symbol.toStringTag] = "ProxiedModules";
-
- for (const id in originalModules) {
+ for (const id in moduleFactories) {
// If we have eagerPatches enabled we have to patch the pre-populated factories
if (Settings.eagerPatches) {
- proxiedModules[id] = patchFactory(id, originalModules[id]);
+ moduleFactories[id] = patchFactory(id, moduleFactories[id]);
} else {
- proxiedModules[id] = originalModules[id];
+ defineModuleFactoryGetter(moduleFactories, id, moduleFactories[id]);
}
-
- // Clear the original object so pre-populated factories are patched if eagerPatches are disabled
- delete originalModules[id];
}
- allProxiedModules.add(proxiedModules);
+ allModuleFactories.add(moduleFactories);
// @ts-ignore
- originalModules[Symbol.toStringTag] = "OriginalModules";
- Object.setPrototypeOf(originalModules, new Proxy(proxiedModules, modulesProxyHandler));
+ moduleFactories[Symbol.toStringTag] = "ModuleFactories";
+ moduleFactories = new Proxy(moduleFactories, moduleFactoriesHandler);
}
Object.defineProperty(this, "m", {
- value: originalModules,
+ value: moduleFactories,
configurable: true,
enumerable: true,
writable: true
@@ -325,10 +316,13 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
}
const patchedFactory: ModuleFactory = (module, exports, require) => {
- // @ts-ignore
- originalFactory.$$vencordRequired = true;
- for (const proxiedModules of allProxiedModules) {
- Reflect.set(proxiedModules, id, originalFactory, proxiedModules);
+ for (const moduleFactories of allModuleFactories) {
+ Object.defineProperty(moduleFactories, id, {
+ value: originalFactory,
+ configurable: true,
+ enumerable: true,
+ writable: true
+ });
}
if (wreq == null && IS_DEV) {
From f727d101fccc7032b41b4652367fe5a9fdc6165c Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Fri, 24 May 2024 03:09:25 -0300
Subject: [PATCH 031/125] I can never not forget something
---
src/webpack/patchWebpack.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index be9dfc9740..b8f1f32a51 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -19,8 +19,8 @@ const initCallbackRegex = canonicalizeMatch(/{return \i\(".+?"\)}/);
/** A set with all the module factories objects */
const allModuleFactories = new Set();
-function defineModuleFactoryGetter(modules: WebpackRequire["m"], id: PropertyKey, factory: ModuleFactory) {
- Object.defineProperty(modules, id, {
+function defineModuleFactoryGetter(modulesFactories: WebpackRequire["m"], id: PropertyKey, factory: ModuleFactory) {
+ Object.defineProperty(modulesFactories, id, {
get: () => {
// $$vencordOriginal means the factory is already patched
// @ts-ignore
From f64b228cb9506f47426ba9b9f9004bf5aa714feb Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Fri, 24 May 2024 03:13:41 -0300
Subject: [PATCH 032/125] better log
---
src/webpack/patchWebpack.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index b8f1f32a51..af27e2294a 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -162,7 +162,7 @@ Object.defineProperty(Function.prototype, "m", {
// This ensures we actually got the right one
const { stack } = new Error();
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 factories", stack.match(/\/assets\/(.+?\.js)/)?.[1] ?? "");
for (const id in moduleFactories) {
// If we have eagerPatches enabled we have to patch the pre-populated factories
From 488f133a90500947f26b6311b0c4e2fbc8e2d398 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Fri, 24 May 2024 03:44:40 -0300
Subject: [PATCH 033/125] Handle if for some reason modules are set again
---
src/webpack/patchWebpack.ts | 28 +++++++++++++++++++++++-----
1 file changed, 23 insertions(+), 5 deletions(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index af27e2294a..e0f8ce12c3 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -21,7 +21,7 @@ const allModuleFactories = new Set();
function defineModuleFactoryGetter(modulesFactories: WebpackRequire["m"], id: PropertyKey, factory: ModuleFactory) {
Object.defineProperty(modulesFactories, id, {
- get: () => {
+ get() {
// $$vencordOriginal means the factory is already patched
// @ts-ignore
if (factory.$$vencordOriginal != null) {
@@ -31,6 +31,15 @@ function defineModuleFactoryGetter(modulesFactories: WebpackRequire["m"], id: Pr
// This patches factories if eagerPatches are disabled
return (factory = patchFactory(id, factory));
},
+ set(v: ModuleFactory) {
+ // @ts-ignore
+ if (factory.$$vencordOriginal != null) {
+ // @ts-ignore
+ factory.$$vencordOriginal = v;
+ } else {
+ factory = v;
+ }
+ },
configurable: true,
enumerable: true
});
@@ -43,16 +52,24 @@ const moduleFactoriesHandler: ProxyHandler = {
return Reflect.set(target, p, newValue, receiver);
}
+ const existingFactory = Reflect.get(target, p, target);
+
if (!Settings.eagerPatches) {
+ // If existingFactory exists, its either wrapped in defineModuleFactoryGetter, or it has already been required
+ // so call Reflect.set with the new original and let the correct logic apply (normal set, or defineModuleFactoryGetter setter)
+ if (existingFactory != null) {
+ return Reflect.set(target, p, newValue, receiver);
+ }
+
defineModuleFactoryGetter(target, p, newValue);
return true;
}
- const existingFactory = Reflect.get(target, p, target);
-
// Check if this factory is already patched
// @ts-ignore
- if (existingFactory?.$$vencordOriginal === newValue) {
+ if (existingFactory.$$vencordOriginal != null) {
+ // @ts-ignore
+ existingFactory.$$vencordOriginal = newValue;
return true;
}
@@ -318,7 +335,8 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
const patchedFactory: ModuleFactory = (module, exports, require) => {
for (const moduleFactories of allModuleFactories) {
Object.defineProperty(moduleFactories, id, {
- value: originalFactory,
+ // @ts-ignore
+ value: patchFactory.$$vencordOriginal,
configurable: true,
enumerable: true,
writable: true
From 86f69e84c17848ff16b071bf2e3b1125df066f62 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Fri, 24 May 2024 03:45:46 -0300
Subject: [PATCH 034/125] I'm stupid
---
src/webpack/patchWebpack.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index e0f8ce12c3..9b1fcdb831 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -67,7 +67,7 @@ const moduleFactoriesHandler: ProxyHandler = {
// Check if this factory is already patched
// @ts-ignore
- if (existingFactory.$$vencordOriginal != null) {
+ if (existingFactory?.$$vencordOriginal != null) {
// @ts-ignore
existingFactory.$$vencordOriginal = newValue;
return true;
From d6b5bc58ce0f6123fd401e6b6fbe63425fa0a552 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Fri, 24 May 2024 03:49:14 -0300
Subject: [PATCH 035/125] how many times
---
src/webpack/patchWebpack.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index 9b1fcdb831..8768a4718f 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -336,7 +336,7 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
for (const moduleFactories of allModuleFactories) {
Object.defineProperty(moduleFactories, id, {
// @ts-ignore
- value: patchFactory.$$vencordOriginal,
+ value: patchedFactory.$$vencordOriginal,
configurable: true,
enumerable: true,
writable: true
From bacf021a281cb6d65af9b8dc9a5d9025d64b2e44 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Fri, 24 May 2024 03:57:54 -0300
Subject: [PATCH 036/125] aaaaa
---
src/webpack/patchWebpack.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index 8768a4718f..2f1deafb04 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -170,7 +170,7 @@ Object.defineProperty(Function.prototype, "O", {
// wreq.m is the webpack object containing module factories.
// 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
-// We replace wrap it with our proxy, which is responsible for patching the module factories, or setting up getters for them
+// We wrap it with our proxy, which is responsible for patching the module factories, or setting up getters for them
Object.defineProperty(Function.prototype, "m", {
configurable: true,
From 3296ee1c4ba22dbe8aaa8a599951469043a92986 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Fri, 24 May 2024 05:14:27 -0300
Subject: [PATCH 037/125] Remove onChunksLoaded patch
---
scripts/generateReport.ts | 81 +++++++++++----------
src/webpack/patchWebpack.ts | 140 +++++++++++-------------------------
src/webpack/webpack.ts | 1 -
3 files changed, 86 insertions(+), 136 deletions(-)
diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts
index bb5c39ac75..89632addf1 100644
--- a/scripts/generateReport.ts
+++ b/scripts/generateReport.ts
@@ -327,8 +327,51 @@ async function runtime(token: string) {
// Enable eagerPatches to make all patches apply regardless of the module being required
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(
+ "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 validChunks = new Set();
@@ -426,43 +469,7 @@ async function runtime(token: string) {
}, 0);
}
- Vencord.Webpack.waitFor(
- "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;
- wreq = wreq!;
// Require deferred entry points
for (const deferredRequire of deferredRequires) {
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index 2f1deafb04..2e53a544dc 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -6,24 +6,33 @@
import { Settings } from "@api/Settings";
import { Logger } from "@utils/Logger";
-import { canonicalizeMatch, canonicalizeReplacement } from "@utils/patches";
+import { canonicalizeReplacement } from "@utils/patches";
import { PatchReplacement } from "@utils/types";
import { traceFunction } from "../debug/Tracer";
import { patches } from "../plugins";
-import { _initWebpack, beforeInitListeners, factoryListeners, ModuleFactory, moduleListeners, OnChunksLoaded, subscriptions, WebpackRequire, wreq } from ".";
+import { _initWebpack, factoryListeners, ModuleFactory, moduleListeners, subscriptions, WebpackRequire, wreq } from ".";
+
+type PatchedModuleFactory = ModuleFactory & {
+ $$vencordOriginal?: ModuleFactory;
+};
+
+type PatchedModuleFactories = Record & {
+ [Symbol.toStringTag]?: "ModuleFactories";
+};
const logger = new Logger("WebpackInterceptor", "#8caaee");
-const initCallbackRegex = canonicalizeMatch(/{return \i\(".+?"\)}/);
/** A set with all the module factories objects */
-const allModuleFactories = new Set();
+const allModuleFactories = new Set();
-function defineModuleFactoryGetter(modulesFactories: WebpackRequire["m"], id: PropertyKey, factory: ModuleFactory) {
+function defineModuleFactoryGetter(modulesFactories: PatchedModuleFactories, id: PropertyKey, factory: PatchedModuleFactory) {
Object.defineProperty(modulesFactories, id, {
+ configurable: true,
+ enumerable: true,
+
get() {
// $$vencordOriginal means the factory is already patched
- // @ts-ignore
if (factory.$$vencordOriginal != null) {
return factory;
}
@@ -32,20 +41,16 @@ function defineModuleFactoryGetter(modulesFactories: WebpackRequire["m"], id: Pr
return (factory = patchFactory(id, factory));
},
set(v: ModuleFactory) {
- // @ts-ignore
if (factory.$$vencordOriginal != null) {
- // @ts-ignore
factory.$$vencordOriginal = v;
} else {
factory = v;
}
- },
- configurable: true,
- enumerable: true
+ }
});
}
-const moduleFactoriesHandler: ProxyHandler = {
+const moduleFactoriesHandler: ProxyHandler = {
set: (target, p, newValue, receiver) => {
// If the property is not a number, we are not dealing with a module factory
if (Number.isNaN(Number(p))) {
@@ -66,9 +71,7 @@ const moduleFactoriesHandler: ProxyHandler = {
}
// Check if this factory is already patched
- // @ts-ignore
if (existingFactory?.$$vencordOriginal != null) {
- // @ts-ignore
existingFactory.$$vencordOriginal = newValue;
return true;
}
@@ -89,97 +92,41 @@ const moduleFactoriesHandler: ProxyHandler = {
}
};
-// 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", {
+// wreq.m is the webpack object containing module factories.
+// 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
+// We wrap it with our proxy, which is responsible for patching the module factories, or setting up getters for them
+Object.defineProperty(Function.prototype, "m", {
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
+ set(this: WebpackRequire, moduleFactories: PatchedModuleFactories) {
+ // When using React DevTools or other extensions, we may also catch their Webpack here.
+ // This ensures we actually got the right ones
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");
+ if ((stack?.includes("discord.com") || stack?.includes("discordapp.com")) && !Array.isArray(moduleFactories)) {
+ logger.info("Found Webpack module factories", stack.match(/\/assets\/(.+?\.js)/)?.[1] ?? "");
- delete (Function.prototype as any).O;
+ // 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).p, 0);
+ Object.defineProperty(this, "p", {
+ configurable: true,
- const originalOnChunksLoaded = onChunksLoaded;
- onChunksLoaded = function (result, chunkIds, callback, priority) {
- if (callback != null && initCallbackRegex.test(String(callback))) {
- Object.defineProperty(this, "O", {
- value: originalOnChunksLoaded,
+ 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
});
-
- 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.
-// 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
-// We wrap it with our proxy, which is responsible for patching the module factories, or setting up getters for them
-Object.defineProperty(Function.prototype, "m", {
- configurable: true,
-
- set(this: WebpackRequire, moduleFactories: WebpackRequire["m"]) {
- // When using react devtools or other extensions, we may also catch their webpack here.
- // This ensures we actually got the right one
- const { stack } = new Error();
- if ((stack?.includes("discord.com") || stack?.includes("discordapp.com")) && !Array.isArray(moduleFactories)) {
- logger.info("Found Webpack module factories", stack.match(/\/assets\/(.+?\.js)/)?.[1] ?? "");
for (const id in moduleFactories) {
// If we have eagerPatches enabled we have to patch the pre-populated factories
@@ -192,7 +139,6 @@ Object.defineProperty(Function.prototype, "m", {
allModuleFactories.add(moduleFactories);
- // @ts-ignore
moduleFactories[Symbol.toStringTag] = "ModuleFactories";
moduleFactories = new Proxy(moduleFactories, moduleFactoriesHandler);
}
@@ -332,10 +278,9 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
if (!patch.all) patches.splice(i--, 1);
}
- const patchedFactory: ModuleFactory = (module, exports, require) => {
+ const patchedFactory: PatchedModuleFactory = (module, exports, require) => {
for (const moduleFactories of allModuleFactories) {
Object.defineProperty(moduleFactories, id, {
- // @ts-ignore
value: patchedFactory.$$vencordOriginal,
configurable: true,
enumerable: true,
@@ -402,7 +347,6 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
};
patchedFactory.toString = originalFactory.toString.bind(originalFactory);
- // @ts-ignore
patchedFactory.$$vencordOriginal = originalFactory;
return patchedFactory;
diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts
index adc88ef834..3003c6bd83 100644
--- a/src/webpack/webpack.ts
+++ b/src/webpack/webpack.ts
@@ -73,7 +73,6 @@ export type CallbackFn = (module: ModuleExports, id: PropertyKey) => void;
export const subscriptions = new Map();
export const moduleListeners = new Set();
export const factoryListeners = new Set<(factory: ModuleFactory) => void>();
-export const beforeInitListeners = new Set<(wreq: WebpackRequire) => void>();
export function _initWebpack(webpackRequire: WebpackRequire) {
wreq = webpackRequire;
From 3793acd9bb7f857b17cca63eb5aedbefa889dd65 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Fri, 24 May 2024 05:19:12 -0300
Subject: [PATCH 038/125] forgot the comment
---
src/webpack/patchWebpack.ts | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index 2e53a544dc..bd3c45b76b 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -92,10 +92,11 @@ const moduleFactoriesHandler: ProxyHandler = {
}
};
-// 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
-// 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 wrap it with our proxy, which is responsible for patching the module factories, or setting up getters for them
+// If this is the main Webpack, we also set up the internal references to WebpackRequire
Object.defineProperty(Function.prototype, "m", {
configurable: true,
From 5fbabb0b70df8d61e01b0d963707c56f02ae0da5 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Fri, 24 May 2024 05:24:37 -0300
Subject: [PATCH 039/125] sob
---
src/webpack/patchWebpack.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index bd3c45b76b..4f05578179 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -116,7 +116,7 @@ Object.defineProperty(Function.prototype, "m", {
set(this: WebpackRequire, v: WebpackRequire["p"]) {
if (v !== "/assets/") return;
- logger.info("Main Webpack found, initializing internal references to WebpackRequire ");
+ logger.info("Main Webpack found, initializing internal references to WebpackRequire");
_initWebpack(this);
clearTimeout(setterTimeout);
From 8dde496757b6d5072d38d5689516cbfa88179b7c Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Fri, 24 May 2024 07:40:36 -0300
Subject: [PATCH 040/125] one more name cuz why not
---
src/webpack/patchWebpack.ts | 2 +-
src/webpack/webpack.ts | 3 +++
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index 4f05578179..a37169258a 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -18,7 +18,7 @@ type PatchedModuleFactory = ModuleFactory & {
};
type PatchedModuleFactories = Record & {
- [Symbol.toStringTag]?: "ModuleFactories";
+ [Symbol.toStringTag]?: string;
};
const logger = new Logger("WebpackInterceptor", "#8caaee");
diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts
index 3003c6bd83..ab9ded1e00 100644
--- a/src/webpack/webpack.ts
+++ b/src/webpack/webpack.ts
@@ -77,6 +77,9 @@ export const factoryListeners = new Set<(factory: ModuleFactory) => void>();
export function _initWebpack(webpackRequire: WebpackRequire) {
wreq = webpackRequire;
cache = webpackRequire.c;
+
+ // @ts-ignore
+ webpackRequire.c[Symbol.toStringTag] = "ModuleCache";
}
let devToolsOpen = false;
From 2f16cdf77b5ac8ee1ccd1886f48bd33268d734c5 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Fri, 24 May 2024 07:59:56 -0300
Subject: [PATCH 041/125] Make toStringTag non enumerable
---
src/webpack/patchWebpack.ts | 10 ++++++----
src/webpack/webpack.ts | 7 +++++--
2 files changed, 11 insertions(+), 6 deletions(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index a37169258a..7aaa4e329a 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -17,9 +17,7 @@ type PatchedModuleFactory = ModuleFactory & {
$$vencordOriginal?: ModuleFactory;
};
-type PatchedModuleFactories = Record & {
- [Symbol.toStringTag]?: string;
-};
+type PatchedModuleFactories = Record;
const logger = new Logger("WebpackInterceptor", "#8caaee");
@@ -140,7 +138,11 @@ Object.defineProperty(Function.prototype, "m", {
allModuleFactories.add(moduleFactories);
- moduleFactories[Symbol.toStringTag] = "ModuleFactories";
+ Object.defineProperty(moduleFactories, Symbol.toStringTag, {
+ value: "ModuleFactories",
+ configurable: true,
+ writable: true
+ });
moduleFactories = new Proxy(moduleFactories, moduleFactoriesHandler);
}
diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts
index ab9ded1e00..480ccb078f 100644
--- a/src/webpack/webpack.ts
+++ b/src/webpack/webpack.ts
@@ -78,8 +78,11 @@ export function _initWebpack(webpackRequire: WebpackRequire) {
wreq = webpackRequire;
cache = webpackRequire.c;
- // @ts-ignore
- webpackRequire.c[Symbol.toStringTag] = "ModuleCache";
+ Object.defineProperty(webpackRequire.c, Symbol.toStringTag, {
+ value: "ModuleCache",
+ configurable: true,
+ writable: true
+ });
}
let devToolsOpen = false;
From daa1a35fc3a658c2e5bc49174e6ea73f6361c6ed Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Fri, 24 May 2024 08:23:41 -0300
Subject: [PATCH 042/125] Factories are called with a `this`
---
src/webpack/patchWebpack.ts | 8 ++++----
src/webpack/wreq.d.ts | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index 7aaa4e329a..075a5a6248 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -281,7 +281,7 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
if (!patch.all) patches.splice(i--, 1);
}
- const patchedFactory: PatchedModuleFactory = (module, exports, require) => {
+ const patchedFactory: PatchedModuleFactory = function (module, exports, require) {
for (const moduleFactories of allModuleFactories) {
Object.defineProperty(moduleFactories, id, {
value: patchedFactory.$$vencordOriginal,
@@ -297,17 +297,17 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
logger.error("WebpackRequire was not initialized, running modules without patches instead.");
}
- return void originalFactory(module, exports, require);
+ return void originalFactory.call(this, module, exports, require);
}
try {
- factory(module, exports, require);
+ factory.call(this, module, exports, require);
} catch (err) {
// Just rethrow Discord errors
if (factory === originalFactory) throw err;
logger.error("Error in patched module", err);
- return void originalFactory(module, exports, require);
+ return void originalFactory.call(this, module, exports, require);
}
// Webpack sometimes sets the value of module.exports directly, so assign exports to it to make sure we properly handle it
diff --git a/src/webpack/wreq.d.ts b/src/webpack/wreq.d.ts
index c86fa1c49d..c810c5a568 100644
--- a/src/webpack/wreq.d.ts
+++ b/src/webpack/wreq.d.ts
@@ -13,7 +13,7 @@ export type Module = {
};
/** exports can be anything, however initially it is always an empty object */
-export type ModuleFactory = (module: Module, exports: ModuleExports, require: WebpackRequire) => void;
+export type ModuleFactory = (this: ModuleExports, module: Module, exports: ModuleExports, require: WebpackRequire) => void;
export type AsyncModuleBody = (
handleDependencies: (deps: Promise[]) => Promise & (() => void)
From 1e55ae5327d3a29337fc0589fbb831a860bc3fec Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Fri, 24 May 2024 18:46:36 -0300
Subject: [PATCH 043/125] this is more clean
---
src/webpack/patchWebpack.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index 075a5a6248..723271f980 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -107,7 +107,7 @@ Object.defineProperty(Function.prototype, "m", {
// 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).p, 0);
+ const setterTimeout = setTimeout(() => Reflect.deleteProperty(this, "p"), 0);
Object.defineProperty(this, "p", {
configurable: true,
From 9af63b362d7ecbc9bef2b9bca5fe897e7980a7ac Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sun, 26 May 2024 00:27:13 -0300
Subject: [PATCH 044/125] more future proof
---
src/webpack/patchWebpack.ts | 109 +++++++++++++++++++-----------------
src/webpack/webpack.ts | 7 ++-
2 files changed, 63 insertions(+), 53 deletions(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index 723271f980..a35da91776 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -24,28 +24,30 @@ const logger = new Logger("WebpackInterceptor", "#8caaee");
/** A set with all the module factories objects */
const allModuleFactories = new Set();
-function defineModuleFactoryGetter(modulesFactories: PatchedModuleFactories, id: PropertyKey, factory: PatchedModuleFactory) {
- Object.defineProperty(modulesFactories, id, {
- configurable: true,
- enumerable: true,
-
- get() {
- // $$vencordOriginal means the factory is already patched
- if (factory.$$vencordOriginal != null) {
- return factory;
- }
+function defineModulesFactoryGetter(id: PropertyKey, factory: PatchedModuleFactory) {
+ for (const moduleFactories of allModuleFactories) {
+ Reflect.defineProperty(moduleFactories, id, {
+ configurable: true,
+ enumerable: true,
+
+ get() {
+ // $$vencordOriginal means the factory is already patched
+ if (factory.$$vencordOriginal != null) {
+ return factory;
+ }
- // This patches factories if eagerPatches are disabled
- return (factory = patchFactory(id, factory));
- },
- set(v: ModuleFactory) {
- if (factory.$$vencordOriginal != null) {
- factory.$$vencordOriginal = v;
- } else {
- factory = v;
+ // This patches factories if eagerPatches are disabled
+ return (factory = patchFactory(id, factory));
+ },
+ set(v: ModuleFactory) {
+ if (factory.$$vencordOriginal != null) {
+ factory.$$vencordOriginal = v;
+ } else {
+ factory = v;
+ }
}
- }
- });
+ });
+ }
}
const moduleFactoriesHandler: ProxyHandler = {
@@ -64,7 +66,7 @@ const moduleFactoriesHandler: ProxyHandler = {
return Reflect.set(target, p, newValue, receiver);
}
- defineModuleFactoryGetter(target, p, newValue);
+ defineModulesFactoryGetter(p, newValue);
return true;
}
@@ -78,7 +80,7 @@ const moduleFactoriesHandler: ProxyHandler = {
// Modules are only patched once, so we need to set the patched factory on all the modules
for (const moduleFactories of allModuleFactories) {
- Object.defineProperty(moduleFactories, p, {
+ Reflect.defineProperty(moduleFactories, p, {
value: patchedFactory,
configurable: true,
enumerable: true,
@@ -95,7 +97,7 @@ const moduleFactoriesHandler: ProxyHandler = {
// The sentry module also has their own Webpack with a pre-populated module factories object, so this also targets that
// We wrap it with our proxy, which is responsible for patching the module factories, or setting up getters for them
// If this is the main Webpack, we also set up the internal references to WebpackRequire
-Object.defineProperty(Function.prototype, "m", {
+Reflect.defineProperty(Function.prototype, "m", {
configurable: true,
set(this: WebpackRequire, moduleFactories: PatchedModuleFactories) {
@@ -103,23 +105,24 @@ Object.defineProperty(Function.prototype, "m", {
// This ensures we actually got the right ones
const { stack } = new Error();
if ((stack?.includes("discord.com") || stack?.includes("discordapp.com")) && !Array.isArray(moduleFactories)) {
- logger.info("Found Webpack module factories", stack.match(/\/assets\/(.+?\.js)/)?.[1] ?? "");
+ const fileName = stack.match(/\/assets\/(.+?\.js)/)?.[1] ?? "";
+ logger.info("Found Webpack module factories in", fileName);
// 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(() => Reflect.deleteProperty(this, "p"), 0);
- Object.defineProperty(this, "p", {
+ Reflect.defineProperty(this, "p", {
configurable: true,
- set(this: WebpackRequire, v: WebpackRequire["p"]) {
- if (v !== "/assets/") return;
+ set(this: WebpackRequire, bundlePath: WebpackRequire["p"]) {
+ if (bundlePath !== "/assets/") return;
- logger.info("Main Webpack found, initializing internal references to WebpackRequire");
+ logger.info(`Main Webpack found in ${fileName}, initializing internal references to WebpackRequire`);
_initWebpack(this);
clearTimeout(setterTimeout);
- Object.defineProperty(this, "p", {
- value: v,
+ Reflect.defineProperty(this, "p", {
+ value: bundlePath,
configurable: true,
enumerable: true,
writable: true
@@ -127,26 +130,28 @@ Object.defineProperty(Function.prototype, "m", {
}
});
+ // This needs to be added before the loop below
+ allModuleFactories.add(moduleFactories);
+
for (const id in moduleFactories) {
// If we have eagerPatches enabled we have to patch the pre-populated factories
if (Settings.eagerPatches) {
moduleFactories[id] = patchFactory(id, moduleFactories[id]);
} else {
- defineModuleFactoryGetter(moduleFactories, id, moduleFactories[id]);
+ defineModulesFactoryGetter(id, moduleFactories[id]);
}
}
- allModuleFactories.add(moduleFactories);
-
- Object.defineProperty(moduleFactories, Symbol.toStringTag, {
+ Reflect.defineProperty(moduleFactories, Symbol.toStringTag, {
value: "ModuleFactories",
configurable: true,
- writable: true
+ writable: true,
+ enumerable: false
});
moduleFactories = new Proxy(moduleFactories, moduleFactoriesHandler);
}
- Object.defineProperty(this, "m", {
+ Reflect.defineProperty(this, "m", {
value: moduleFactories,
configurable: true,
enumerable: true,
@@ -158,15 +163,16 @@ Object.defineProperty(Function.prototype, "m", {
let webpackNotInitializedLogged = false;
function patchFactory(id: PropertyKey, factory: ModuleFactory) {
+ const originalFactory = factory;
+
for (const factoryListener of factoryListeners) {
try {
- factoryListener(factory);
+ factoryListener(originalFactory);
} catch (err) {
logger.error("Error in Webpack factory listener:\n", err, factoryListener);
}
}
- const originalFactory = factory;
const patchedBy = new Set();
// Discords Webpack chunks for some ungodly reason contain random
@@ -193,15 +199,15 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
patchedBy.add(patch.plugin);
const executePatch = traceFunction(`patch by ${patch.plugin}`, (match: string | RegExp, replace: string) => code.replace(match, replace));
- const previousFactory = factory;
const previousCode = code;
+ const previousFactory = factory;
// We change all patch.replacement to array in plugins/index
for (const replacement of patch.replacement as PatchReplacement[]) {
if (replacement.predicate && !replacement.predicate()) continue;
- const lastFactory = factory;
const lastCode = code;
+ const lastFactory = factory;
canonicalizeReplacement(replacement, patch.plugin);
@@ -217,8 +223,8 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
if (patch.group) {
logger.warn(`Undoing patch group ${patch.find} by ${patch.plugin} because replacement ${replacement.match} had no effect`);
- factory = previousFactory;
code = previousCode;
+ factory = previousFactory;
patchedBy.delete(patch.plugin);
break;
}
@@ -268,13 +274,13 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
if (patch.group) {
logger.warn(`Undoing patch group ${patch.find} by ${patch.plugin} because replacement ${replacement.match} errored`);
- factory = previousFactory;
code = previousCode;
+ factory = previousFactory;
break;
}
- factory = lastFactory;
code = lastCode;
+ factory = lastFactory;
}
}
@@ -283,7 +289,7 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
const patchedFactory: PatchedModuleFactory = function (module, exports, require) {
for (const moduleFactories of allModuleFactories) {
- Object.defineProperty(moduleFactories, id, {
+ Reflect.defineProperty(moduleFactories, id, {
value: patchedFactory.$$vencordOriginal,
configurable: true,
enumerable: true,
@@ -297,33 +303,34 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
logger.error("WebpackRequire was not initialized, running modules without patches instead.");
}
- return void originalFactory.call(this, module, exports, require);
+ return originalFactory.call(this, module, exports, require);
}
+ let factoryReturn: unknown;
try {
- factory.call(this, module, exports, require);
+ factoryReturn = factory.call(this, module, exports, require);
} catch (err) {
// Just rethrow Discord errors
if (factory === originalFactory) throw err;
logger.error("Error in patched module", err);
- return void originalFactory.call(this, module, exports, require);
+ return originalFactory.call(this, module, exports, require);
}
// Webpack sometimes sets the value of module.exports directly, so assign exports to it to make sure we properly handle it
exports = module.exports;
- if (exports == null) return;
+ if (exports == null) return factoryReturn;
// There are (at the time of writing) 11 modules exporting the window
// Make these non enumerable to improve webpack search performance
if (exports === window && require.c) {
- Object.defineProperty(require.c, id, {
+ Reflect.defineProperty(require.c, id, {
value: require.c[id],
configurable: true,
enumerable: false,
writable: true
});
- return;
+ return factoryReturn;
}
for (const callback of moduleListeners) {
@@ -347,6 +354,8 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
logger.error("Error while firing callback for Webpack subscription:\n", err, filter, callback);
}
}
+
+ return factoryReturn;
};
patchedFactory.toString = originalFactory.toString.bind(originalFactory);
diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts
index 480ccb078f..a46a7de459 100644
--- a/src/webpack/webpack.ts
+++ b/src/webpack/webpack.ts
@@ -78,10 +78,11 @@ export function _initWebpack(webpackRequire: WebpackRequire) {
wreq = webpackRequire;
cache = webpackRequire.c;
- Object.defineProperty(webpackRequire.c, Symbol.toStringTag, {
+ Reflect.defineProperty(webpackRequire.c, Symbol.toStringTag, {
value: "ModuleCache",
configurable: true,
- writable: true
+ writable: true,
+ enumerable: false
});
}
@@ -506,7 +507,7 @@ export function search(...filters: Array) {
outer:
for (const id in factories) {
const factory = factories[id];
- const str: string = String(factory);
+ const str = String(factory);
for (const filter of filters) {
if (typeof filter === "string" && !str.includes(filter)) continue outer;
if (filter instanceof RegExp && !filter.test(str)) continue outer;
From affd527bc13503aa906286c0d23f99bc64fe5550 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sun, 26 May 2024 05:19:52 -0300
Subject: [PATCH 045/125] Make factory wrapper a little more future proof
---
src/utils/misc.tsx | 5 ++++
src/webpack/patchWebpack.ts | 47 ++++++++++++++++++++-----------------
2 files changed, 31 insertions(+), 21 deletions(-)
diff --git a/src/utils/misc.tsx b/src/utils/misc.tsx
index 5bf2c23984..089bd541c9 100644
--- a/src/utils/misc.tsx
+++ b/src/utils/misc.tsx
@@ -102,3 +102,8 @@ export function pluralise(amount: number, singular: string, plural = singular +
/** Unconfigurable properties for proxies */
export const UNCONFIGURABLE_PROPERTIES = ["arguments", "caller", "prototype"];
+
+export function interpolateIfDefined(strings: TemplateStringsArray, ...args: any[]) {
+ if (args.some(arg => arg == null)) return "";
+ return strings.reduce((acc, str, i) => `${acc}${str}${args[i] ?? ""}`, "");
+}
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index a35da91776..6aeee236f9 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -6,6 +6,7 @@
import { Settings } from "@api/Settings";
import { Logger } from "@utils/Logger";
+import { interpolateIfDefined } from "@utils/misc";
import { canonicalizeReplacement } from "@utils/patches";
import { PatchReplacement } from "@utils/types";
@@ -105,8 +106,8 @@ Reflect.defineProperty(Function.prototype, "m", {
// This ensures we actually got the right ones
const { stack } = new Error();
if ((stack?.includes("discord.com") || stack?.includes("discordapp.com")) && !Array.isArray(moduleFactories)) {
- const fileName = stack.match(/\/assets\/(.+?\.js)/)?.[1] ?? "";
- logger.info("Found Webpack module factories in", fileName);
+ const fileName = stack.match(/\/assets\/(.+?\.js)/)?.[1];
+ logger.info("Found Webpack module factories" + interpolateIfDefined` in ${fileName}`);
// 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
@@ -117,7 +118,7 @@ Reflect.defineProperty(Function.prototype, "m", {
set(this: WebpackRequire, bundlePath: WebpackRequire["p"]) {
if (bundlePath !== "/assets/") return;
- logger.info(`Main Webpack found in ${fileName}, initializing internal references to WebpackRequire`);
+ logger.info("Main Webpack found" + interpolateIfDefined` in ${fileName}` + ", initializing internal references to WebpackRequire");
_initWebpack(this);
clearTimeout(setterTimeout);
@@ -160,8 +161,6 @@ Reflect.defineProperty(Function.prototype, "m", {
}
});
-let webpackNotInitializedLogged = false;
-
function patchFactory(id: PropertyKey, factory: ModuleFactory) {
const originalFactory = factory;
@@ -287,7 +286,7 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
if (!patch.all) patches.splice(i--, 1);
}
- const patchedFactory: PatchedModuleFactory = function (module, exports, require) {
+ const patchedFactory: PatchedModuleFactory = function (...args: Parameters) {
for (const moduleFactories of allModuleFactories) {
Reflect.defineProperty(moduleFactories, id, {
value: patchedFactory.$$vencordOriginal,
@@ -297,38 +296,44 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
});
}
- if (wreq == null && IS_DEV) {
- if (!webpackNotInitializedLogged) {
- webpackNotInitializedLogged = true;
- logger.error("WebpackRequire was not initialized, running modules without patches instead.");
- }
-
- return originalFactory.call(this, module, exports, require);
+ // eslint-disable-next-line prefer-const
+ let [module, exports, require] = args;
+
+ // Make sure the require argument is actually the WebpackRequire functioin
+ if (wreq == null && String(require).includes("exports:{}")) {
+ const { stack } = new Error();
+ const webpackInstanceFileName = stack?.match(/\/assets\/(.+?\.js)/)?.[1];
+ logger.warn(
+ "WebpackRequire was not initialized, falling back to WebpackRequire passed to the first called patched module factory (" +
+ `id: ${String(id)}` + interpolateIfDefined`, WebpackInstance origin: ${webpackInstanceFileName}` +
+ ")"
+ );
+ _initWebpack(require);
}
let factoryReturn: unknown;
try {
- factoryReturn = factory.call(this, module, exports, require);
+ factoryReturn = factory.apply(this, args);
} catch (err) {
- // Just rethrow Discord errors
+ // Just re-throw Discord errors
if (factory === originalFactory) throw err;
- logger.error("Error in patched module", err);
- return originalFactory.call(this, module, exports, require);
+ logger.error("Error in patched module factory", err);
+ return originalFactory.apply(this, args);
}
// Webpack sometimes sets the value of module.exports directly, so assign exports to it to make sure we properly handle it
- exports = module.exports;
+ exports = module?.exports;
if (exports == null) return factoryReturn;
// There are (at the time of writing) 11 modules exporting the window
// Make these non enumerable to improve webpack search performance
- if (exports === window && require.c) {
+ if (exports === window && require?.c) {
Reflect.defineProperty(require.c, id, {
value: require.c[id],
configurable: true,
- enumerable: false,
- writable: true
+ writable: true,
+ enumerable: false
});
return factoryReturn;
}
From 265cf12d392c8a0bfbf585f77bf76a3acab34c55 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sun, 26 May 2024 05:24:03 -0300
Subject: [PATCH 046/125] I love
---
src/webpack/patchWebpack.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index 6aeee236f9..e756d3d0b1 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -299,8 +299,8 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
// eslint-disable-next-line prefer-const
let [module, exports, require] = args;
- // Make sure the require argument is actually the WebpackRequire functioin
- if (wreq == null && String(require).includes("exports:{}")) {
+ // Make sure the require argument is actually the WebpackRequire function
+ if (wreq == null && typeof require === "function" && require.m != null) {
const { stack } = new Error();
const webpackInstanceFileName = stack?.match(/\/assets\/(.+?\.js)/)?.[1];
logger.warn(
@@ -328,7 +328,7 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
// There are (at the time of writing) 11 modules exporting the window
// Make these non enumerable to improve webpack search performance
- if (exports === window && require?.c) {
+ if (exports === window && typeof require === "function" && require.c != null) {
Reflect.defineProperty(require.c, id, {
value: require.c[id],
configurable: true,
From a8fa685cfac63ceff4d3905d47d4d6683f9dbac6 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sun, 26 May 2024 05:28:34 -0300
Subject: [PATCH 047/125] Add back running modules without patches
---
src/webpack/patchWebpack.ts | 31 +++++++++++++++++++++----------
1 file changed, 21 insertions(+), 10 deletions(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index e756d3d0b1..480234dbdd 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -161,6 +161,8 @@ Reflect.defineProperty(Function.prototype, "m", {
}
});
+let wreqNotInitializedLogged = false;
+
function patchFactory(id: PropertyKey, factory: ModuleFactory) {
const originalFactory = factory;
@@ -299,16 +301,25 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
// eslint-disable-next-line prefer-const
let [module, exports, require] = args;
- // Make sure the require argument is actually the WebpackRequire function
- if (wreq == null && typeof require === "function" && require.m != null) {
- const { stack } = new Error();
- const webpackInstanceFileName = stack?.match(/\/assets\/(.+?\.js)/)?.[1];
- logger.warn(
- "WebpackRequire was not initialized, falling back to WebpackRequire passed to the first called patched module factory (" +
- `id: ${String(id)}` + interpolateIfDefined`, WebpackInstance origin: ${webpackInstanceFileName}` +
- ")"
- );
- _initWebpack(require);
+ if (wreq == null) {
+ // Make sure the require argument is actually the WebpackRequire function
+ if (typeof require === "function" && require.m != null) {
+ const { stack } = new Error();
+ const webpackInstanceFileName = stack?.match(/\/assets\/(.+?\.js)/)?.[1];
+ logger.warn(
+ "WebpackRequire was not initialized, falling back to WebpackRequire passed to the first called patched module factory (" +
+ `id: ${String(id)}` + interpolateIfDefined`, WebpackInstance origin: ${webpackInstanceFileName}` +
+ ")"
+ );
+ _initWebpack(require);
+ } else if (IS_DEV) {
+ if (!wreqNotInitializedLogged) {
+ wreqNotInitializedLogged = true;
+ logger.error("WebpackRequire was not initialized, running modules without patches instead.");
+ }
+
+ return originalFactory.apply(this, args);
+ }
}
let factoryReturn: unknown;
From 1ed956114f82721e905a33ee3c39ca417ecc0a24 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sun, 26 May 2024 05:46:53 -0300
Subject: [PATCH 048/125] fix logic
---
src/webpack/patchWebpack.ts | 31 +++++++++++++++++--------------
1 file changed, 17 insertions(+), 14 deletions(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index 480234dbdd..efebaf6bbe 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -161,7 +161,7 @@ Reflect.defineProperty(Function.prototype, "m", {
}
});
-let wreqNotInitializedLogged = false;
+let wreqFallbackApplied = false;
function patchFactory(id: PropertyKey, factory: ModuleFactory) {
const originalFactory = factory;
@@ -302,22 +302,25 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
let [module, exports, require] = args;
if (wreq == null) {
- // Make sure the require argument is actually the WebpackRequire function
- if (typeof require === "function" && require.m != null) {
- const { stack } = new Error();
- const webpackInstanceFileName = stack?.match(/\/assets\/(.+?\.js)/)?.[1];
- logger.warn(
- "WebpackRequire was not initialized, falling back to WebpackRequire passed to the first called patched module factory (" +
- `id: ${String(id)}` + interpolateIfDefined`, WebpackInstance origin: ${webpackInstanceFileName}` +
- ")"
- );
- _initWebpack(require);
- } else if (IS_DEV) {
- if (!wreqNotInitializedLogged) {
- wreqNotInitializedLogged = true;
+ if (!wreqFallbackApplied) {
+ wreqFallbackApplied = true;
+
+ // Make sure the require argument is actually the WebpackRequire function
+ if (typeof require === "function" && require.m != null) {
+ const { stack } = new Error();
+ const webpackInstanceFileName = stack?.match(/\/assets\/(.+?\.js)/)?.[1];
+ logger.warn(
+ "WebpackRequire was not initialized, falling back to WebpackRequire passed to the first called patched module factory (" +
+ `id: ${String(id)}` + interpolateIfDefined`, WebpackInstance origin: ${webpackInstanceFileName}` +
+ ")"
+ );
+ _initWebpack(require);
+ } else if (IS_DEV) {
logger.error("WebpackRequire was not initialized, running modules without patches instead.");
}
+ }
+ if (IS_DEV) {
return originalFactory.apply(this, args);
}
}
From 3e3201ad0d29b8fc367ce30be5b63f87ac6c8716 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sun, 26 May 2024 07:00:03 -0300
Subject: [PATCH 049/125] improve wreq docs
---
src/webpack/wreq.d.ts | 23 +++++++++++++++++------
1 file changed, 17 insertions(+), 6 deletions(-)
diff --git a/src/webpack/wreq.d.ts b/src/webpack/wreq.d.ts
index c810c5a568..19800e7014 100644
--- a/src/webpack/wreq.d.ts
+++ b/src/webpack/wreq.d.ts
@@ -50,8 +50,13 @@ export type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
* Export star. Sets properties of "fromObject" to "toObject" as getters that return the value from "fromObject", like this:
* @example
* const fromObject = { a: 1 };
- * Object.defineProperty(fromObject, "a", {
- * get: () => fromObject["a"]
+ * Object.keys(fromObject).forEach(key => {
+ * if (key !== "default" && !(key in toObject)) {
+ * Object.defineProperty(toObject, key, {
+ * get: () => fromObject[key],
+ * enumerable: true
+ * });
+ * }
* });
* @returns fromObject
*/
@@ -81,14 +86,20 @@ export type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
*/
t: (this: WebpackRequire, value: any, mode: number) => any;
/**
- * Define property getters. For every prop in "definiton", set a getter in "exports" for the value in "definitiion", like this:
+ * Define getter functions for harmony exports. For every prop in "definiton" (the module exports), set a getter in "exports" for the getter function in the "definition", like this:
* @example
* const exports = {};
- * const definition = { a: 1 };
+ * const definition = { exportName: () => someExportedValue };
* for (const key in definition) {
- * Object.defineProperty(exports, key, { get: definition[key] }
+ * if (key in definition && !(key in exports)) {
+ * Object.defineProperty(exports, key, {
+ * get: definition[key],
+ * enumerable: true
+ * });
+ * }
* }
- */
+ * // exports is now { exportName: someExportedValue } (but each value is actually a getter)
+ */
d: (this: WebpackRequire, exports: Record, definiton: Record) => void;
/** The chunk handlers, which are used to ensure the files of the chunks are loaded, or load if necessary */
f: ChunkHandlers;
From 440cb1f29cb01bc8a5c36a927053e75eaec1d371 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sun, 26 May 2024 19:24:59 -0300
Subject: [PATCH 050/125] Properly document wreq.a
---
src/webpack/wreq.d.ts | 32 ++++++++++++++++++++++++++------
1 file changed, 26 insertions(+), 6 deletions(-)
diff --git a/src/webpack/wreq.d.ts b/src/webpack/wreq.d.ts
index 19800e7014..2e8ece8f19 100644
--- a/src/webpack/wreq.d.ts
+++ b/src/webpack/wreq.d.ts
@@ -16,7 +16,9 @@ export type Module = {
export type ModuleFactory = (this: ModuleExports, module: Module, exports: ModuleExports, require: WebpackRequire) => void;
export type AsyncModuleBody = (
- handleDependencies: (deps: Promise[]) => Promise & (() => void)
+ handleAsyncDependencies: (deps: Promise[]) =>
+ Promise<() => any[]> | (() => any[]),
+ asyncResult: (error?: any) => void
) => Promise;
export type ChunkHandlers = {
@@ -62,11 +64,29 @@ export type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
*/
es: (this: WebpackRequire, fromObject: Record, toObject: Record) => Record;
/**
- * Creates an async module. The body function must be a async function.
- * "module.exports" will be decorated with an AsyncModulePromise.
- * The body function will be called.
- * To handle async dependencies correctly do this inside the body: "([a, b, c] = await handleDependencies([a, b, c]));".
- * If "hasAwaitAfterDependencies" is truthy, "handleDependencies()" must be called at the end of the body function.
+ * Creates an async module. A module that exports something that is a Promise, or requires an export from an async module.
+ * The body function must be an async function. "module.exports" will become a Promise.
+ * The body function will be called with a function to handle requires that import from an async module, and a function to resolve this async module. An example to handle async depedencies:
+ * @example
+ * const factory = (module, exports, wreq) => {
+ * wreq.a(module, async (handleAsyncDependencies, asyncResult) => {
+ * try {
+ * const asyncRequireA = wreq(...);
+ *
+ * const asyncDependencies = handleAsyncDependencies([asyncRequire]);
+ * const [requireAResult] = asyncDependencies.then != null ? (await asyncDependencies)() : asyncDependencies;
+ *
+ * // Use the required module
+ * console.log(requireAResult);
+ *
+ * // Mark this async module as resolved
+ * asyncResult();
+ * } catch(error) {
+ * // Mark this async module as rejected with an error
+ * asyncResult(error);
+ * }
+ * }, false); // false because our module does not have an await after dealing with the async requires
+ * }
*/
a: (this: WebpackRequire, module: Module, body: AsyncModuleBody, hasAwaitAfterDependencies?: boolean) => void;
/** getDefaultExport function for compatibility with non-harmony modules */
From 18142ecccb62b1ced32299fbd797a19fae477cd3 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sun, 26 May 2024 19:26:26 -0300
Subject: [PATCH 051/125] fix doc
---
src/webpack/wreq.d.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/webpack/wreq.d.ts b/src/webpack/wreq.d.ts
index 2e8ece8f19..ab1f43c53e 100644
--- a/src/webpack/wreq.d.ts
+++ b/src/webpack/wreq.d.ts
@@ -65,7 +65,7 @@ export type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
es: (this: WebpackRequire, fromObject: Record, toObject: Record) => Record;
/**
* Creates an async module. A module that exports something that is a Promise, or requires an export from an async module.
- * The body function must be an async function. "module.exports" will become a Promise.
+ * The body function must be an async function. "module.exports" will become a AsyncModulePromise.
* The body function will be called with a function to handle requires that import from an async module, and a function to resolve this async module. An example to handle async depedencies:
* @example
* const factory = (module, exports, wreq) => {
From c2047e5f3b99371a9a85bc430c60f520d61d2be5 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sun, 26 May 2024 19:27:52 -0300
Subject: [PATCH 052/125] fix typos
---
src/webpack/wreq.d.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/webpack/wreq.d.ts b/src/webpack/wreq.d.ts
index ab1f43c53e..4ac85a1cce 100644
--- a/src/webpack/wreq.d.ts
+++ b/src/webpack/wreq.d.ts
@@ -66,7 +66,7 @@ export type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
/**
* Creates an async module. A module that exports something that is a Promise, or requires an export from an async module.
* The body function must be an async function. "module.exports" will become a AsyncModulePromise.
- * The body function will be called with a function to handle requires that import from an async module, and a function to resolve this async module. An example to handle async depedencies:
+ * The body function will be called with a function to handle requires that import from an async module, and a function to resolve this async module. An example on how to handle async dependencies:
* @example
* const factory = (module, exports, wreq) => {
* wreq.a(module, async (handleAsyncDependencies, asyncResult) => {
From 4d27643d39b89b4d981098226815cf8d52fc5ef1 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sun, 26 May 2024 19:37:47 -0300
Subject: [PATCH 053/125] type more
---
src/webpack/wreq.d.ts | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/src/webpack/wreq.d.ts b/src/webpack/wreq.d.ts
index 4ac85a1cce..38038907a1 100644
--- a/src/webpack/wreq.d.ts
+++ b/src/webpack/wreq.d.ts
@@ -15,9 +15,19 @@ export type Module = {
/** exports can be anything, however initially it is always an empty object */
export type ModuleFactory = (this: ModuleExports, module: Module, exports: ModuleExports, require: WebpackRequire) => void;
+export type WebpackQueues = unique symbol;
+export type WebpackExports = unique symbol;
+export type WebpackError = unique symbol;
+
+type AsyncModulePromise = Promise & {
+ [WebpackQueues]: (fnQueue: ((queue: any[]) => any)) => any;
+ [WebpackExports]: ModuleExports;
+ [WebpackError]?: any;
+};
+
export type AsyncModuleBody = (
- handleAsyncDependencies: (deps: Promise[]) =>
- Promise<() => any[]> | (() => any[]),
+ handleAsyncDependencies: (deps: AsyncModulePromise[]) =>
+ Promise<() => ModuleExports[]> | (() => ModuleExports[]),
asyncResult: (error?: any) => void
) => Promise;
From dae3841f106680b77e4e6455b901d30a7b5744b7 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sun, 26 May 2024 19:38:25 -0300
Subject: [PATCH 054/125] ughh
---
src/webpack/wreq.d.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/webpack/wreq.d.ts b/src/webpack/wreq.d.ts
index 38038907a1..85f2c9ab77 100644
--- a/src/webpack/wreq.d.ts
+++ b/src/webpack/wreq.d.ts
@@ -19,7 +19,7 @@ export type WebpackQueues = unique symbol;
export type WebpackExports = unique symbol;
export type WebpackError = unique symbol;
-type AsyncModulePromise = Promise & {
+export type AsyncModulePromise = Promise & {
[WebpackQueues]: (fnQueue: ((queue: any[]) => any)) => any;
[WebpackExports]: ModuleExports;
[WebpackError]?: any;
From 32a2c90761212da2d497fa0d302a01625fdd86f1 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sun, 26 May 2024 19:40:33 -0300
Subject: [PATCH 055/125] fix typing of the global
---
src/webpack/wreq.d.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/webpack/wreq.d.ts b/src/webpack/wreq.d.ts
index 85f2c9ab77..e155f20cd8 100644
--- a/src/webpack/wreq.d.ts
+++ b/src/webpack/wreq.d.ts
@@ -143,7 +143,7 @@ export type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
/** Get the filename for the js part of a chunk */
u: (this: WebpackRequire, chunkId: PropertyKey) => string;
/** The global object, will likely always be the window */
- g: Window;
+ g: typeof globalThis;
/** Harmony module decorator. Decorates a module as an ES Module, and prevents Node.js "module.exports" from being set */
hmd: (this: WebpackRequire, module: Module) => any;
/** Shorthand for Object.prototype.hasOwnProperty */
From c1e78b439750a94559724a89e25155a4acddfa52 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sun, 26 May 2024 19:50:16 -0300
Subject: [PATCH 056/125] hasOwn != in
---
src/webpack/wreq.d.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/webpack/wreq.d.ts b/src/webpack/wreq.d.ts
index e155f20cd8..a491fc4af6 100644
--- a/src/webpack/wreq.d.ts
+++ b/src/webpack/wreq.d.ts
@@ -63,7 +63,7 @@ export type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
* @example
* const fromObject = { a: 1 };
* Object.keys(fromObject).forEach(key => {
- * if (key !== "default" && !(key in toObject)) {
+ * if (key !== "default" && !Object.hasOwn(toObject, key) {
* Object.defineProperty(toObject, key, {
* get: () => fromObject[key],
* enumerable: true
@@ -121,7 +121,7 @@ export type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
* const exports = {};
* const definition = { exportName: () => someExportedValue };
* for (const key in definition) {
- * if (key in definition && !(key in exports)) {
+ * if (Objeect.hasOwn(definition, key) && !Object.hasOwn(exports, key) {
* Object.defineProperty(exports, key, {
* get: definition[key],
* enumerable: true
From 8fd22f6deb90285f594c23fe73a169e61a587f72 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sun, 26 May 2024 19:50:47 -0300
Subject: [PATCH 057/125] e
---
src/webpack/wreq.d.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/webpack/wreq.d.ts b/src/webpack/wreq.d.ts
index a491fc4af6..6457fcc815 100644
--- a/src/webpack/wreq.d.ts
+++ b/src/webpack/wreq.d.ts
@@ -121,7 +121,7 @@ export type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
* const exports = {};
* const definition = { exportName: () => someExportedValue };
* for (const key in definition) {
- * if (Objeect.hasOwn(definition, key) && !Object.hasOwn(exports, key) {
+ * if (Object.hasOwn(definition, key) && !Object.hasOwn(exports, key) {
* Object.defineProperty(exports, key, {
* get: definition[key],
* enumerable: true
From 9ada9bc1a950c9f761e2e66dd342f0a69c1d63b6 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sun, 26 May 2024 19:51:34 -0300
Subject: [PATCH 058/125] e part 2
---
src/webpack/wreq.d.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/webpack/wreq.d.ts b/src/webpack/wreq.d.ts
index 6457fcc815..3dccb629d2 100644
--- a/src/webpack/wreq.d.ts
+++ b/src/webpack/wreq.d.ts
@@ -63,7 +63,7 @@ export type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
* @example
* const fromObject = { a: 1 };
* Object.keys(fromObject).forEach(key => {
- * if (key !== "default" && !Object.hasOwn(toObject, key) {
+ * if (key !== "default" && !Object.hasOwn(toObject, key)) {
* Object.defineProperty(toObject, key, {
* get: () => fromObject[key],
* enumerable: true
@@ -121,7 +121,7 @@ export type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
* const exports = {};
* const definition = { exportName: () => someExportedValue };
* for (const key in definition) {
- * if (Object.hasOwn(definition, key) && !Object.hasOwn(exports, key) {
+ * if (Object.hasOwn(definition, key) && !Object.hasOwn(exports, key)) {
* Object.defineProperty(exports, key, {
* get: definition[key],
* enumerable: true
From 8c5f2c897eaa8f92d5aa13f737f61c36352554fd Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sun, 26 May 2024 20:22:03 -0300
Subject: [PATCH 059/125] Document patchWebpack better
---
src/webpack/patchWebpack.ts | 177 +++++++++++++++++++++---------------
src/webpack/wreq.d.ts | 10 +-
2 files changed, 109 insertions(+), 78 deletions(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index efebaf6bbe..719cd40207 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -24,8 +24,95 @@ const logger = new Logger("WebpackInterceptor", "#8caaee");
/** A set with all the module factories objects */
const allModuleFactories = new Set();
+/** Whether we tried to fallback to factory WebpackRequire, or disabled patches */
+let wreqFallbackApplied = false;
+
+// 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.
+// 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
+// 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", {
+ configurable: true,
+
+ set(this: WebpackRequire, moduleFactories: PatchedModuleFactories) {
+ // When using React DevTools or other extensions, we may also catch their Webpack here.
+ // This ensures we actually got the right ones.
+ const { stack } = new Error();
+ if ((stack?.includes("discord.com") || stack?.includes("discordapp.com")) && !Array.isArray(moduleFactories)) {
+ const fileName = stack.match(/\/assets\/(.+?\.js)/)?.[1];
+ logger.info("Found Webpack module factories" + interpolateIfDefined` in ${fileName}`);
+
+ // 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.
+ Reflect.defineProperty(this, "p", {
+ configurable: true,
+
+ set(this: WebpackRequire, bundlePath: WebpackRequire["p"]) {
+ if (bundlePath !== "/assets/") return;
+
+ logger.info("Main Webpack found" + interpolateIfDefined` in ${fileName}` + ", initializing internal references to WebpackRequire");
+ _initWebpack(this);
+ clearTimeout(setterTimeout);
+
+ Reflect.defineProperty(this, "p", {
+ value: bundlePath,
+ configurable: true,
+ enumerable: true,
+ writable: true
+ });
+ }
+ });
+ // 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(() => Reflect.deleteProperty(this, "p"), 0);
+ // This needs to be added before the loop below
+ allModuleFactories.add(moduleFactories);
+
+ // Patch the pre-populated factories
+ for (const id in moduleFactories) {
+ if (Settings.eagerPatches) {
+ // Patches the factory directly
+ moduleFactories[id] = patchFactory(id, moduleFactories[id]);
+ } else {
+ // Define a getter for the patched version
+ defineModulesFactoryGetter(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);
+ }
+
+ Reflect.defineProperty(this, "m", {
+ value: moduleFactories,
+ configurable: true,
+ enumerable: true,
+ writable: true
+ });
+ }
+});
+
+/**
+ * Define the getter for returning the patched version of the module factory. This only executes and patches the factory when its accessed for the first time.
+ *
+ * It is what patches factories when eagerPatches are disabled.
+ *
+ * The factory argument will become the patched version of the factory once it is accessed.
+ * @param id The id of the module
+ * @param factory The original or patched module factory
+ */
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
+ // have the the patched version
for (const moduleFactories of allModuleFactories) {
Reflect.defineProperty(moduleFactories, id, {
configurable: true,
@@ -37,7 +124,6 @@ function defineModulesFactoryGetter(id: PropertyKey, factory: PatchedModuleFacto
return factory;
}
- // This patches factories if eagerPatches are disabled
return (factory = patchFactory(id, factory));
},
set(v: ModuleFactory) {
@@ -52,6 +138,7 @@ function defineModulesFactoryGetter(id: PropertyKey, factory: PatchedModuleFacto
}
const moduleFactoriesHandler: ProxyHandler = {
+ // The set trap for patching or defining getters for the module factories when new module factories are loaded
set: (target, p, newValue, receiver) => {
// If the property is not a number, we are not dealing with a module factory
if (Number.isNaN(Number(p))) {
@@ -67,6 +154,7 @@ const moduleFactoriesHandler: ProxyHandler = {
return Reflect.set(target, p, newValue, receiver);
}
+ // eagerPatches are disabled, so set up the getter for the patched version
defineModulesFactoryGetter(p, newValue);
return true;
}
@@ -79,7 +167,9 @@ const moduleFactoriesHandler: ProxyHandler = {
const patchedFactory = patchFactory(p, newValue);
- // Modules are only patched once, so we need to set the patched factory on all the modules
+ // If multiple Webpack instances exist, when new a new module is loaded, it will be set in all the module factories objects.
+ // Because patches are only executed once, we need to set the patched version in all of them, to avoid the Webpack instance
+ // that uses the factory to contain the original factory instead of the patched, in case it was set first in another instance
for (const moduleFactories of allModuleFactories) {
Reflect.defineProperty(moduleFactories, p, {
value: patchedFactory,
@@ -92,77 +182,14 @@ const moduleFactoriesHandler: ProxyHandler = {
return true;
}
};
-
-// wreq.m is the Webpack object containing module factories.
-// 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
-// We wrap it with our proxy, which is responsible for patching the module factories, or setting up getters for them
-// If this is the main Webpack, we also set up the internal references to WebpackRequire
-Reflect.defineProperty(Function.prototype, "m", {
- configurable: true,
-
- set(this: WebpackRequire, moduleFactories: PatchedModuleFactories) {
- // When using React DevTools or other extensions, we may also catch their Webpack here.
- // This ensures we actually got the right ones
- const { stack } = new Error();
- if ((stack?.includes("discord.com") || stack?.includes("discordapp.com")) && !Array.isArray(moduleFactories)) {
- const fileName = stack.match(/\/assets\/(.+?\.js)/)?.[1];
- logger.info("Found Webpack module factories" + interpolateIfDefined` in ${fileName}`);
-
- // 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(() => Reflect.deleteProperty(this, "p"), 0);
- Reflect.defineProperty(this, "p", {
- configurable: true,
-
- set(this: WebpackRequire, bundlePath: WebpackRequire["p"]) {
- if (bundlePath !== "/assets/") return;
-
- logger.info("Main Webpack found" + interpolateIfDefined` in ${fileName}` + ", initializing internal references to WebpackRequire");
- _initWebpack(this);
- clearTimeout(setterTimeout);
-
- Reflect.defineProperty(this, "p", {
- value: bundlePath,
- configurable: true,
- enumerable: true,
- writable: true
- });
- }
- });
-
- // This needs to be added before the loop below
- allModuleFactories.add(moduleFactories);
-
- for (const id in moduleFactories) {
- // If we have eagerPatches enabled we have to patch the pre-populated factories
- if (Settings.eagerPatches) {
- moduleFactories[id] = patchFactory(id, moduleFactories[id]);
- } else {
- defineModulesFactoryGetter(id, moduleFactories[id]);
- }
- }
-
- Reflect.defineProperty(moduleFactories, Symbol.toStringTag, {
- value: "ModuleFactories",
- configurable: true,
- writable: true,
- enumerable: false
- });
- moduleFactories = new Proxy(moduleFactories, moduleFactoriesHandler);
- }
-
- Reflect.defineProperty(this, "m", {
- value: moduleFactories,
- configurable: true,
- enumerable: true,
- writable: true
- });
- }
-});
-
-let wreqFallbackApplied = false;
-
+/**
+ * Patches a module factory.
+ *
+ * The factory argument will become the patched version of the factory.
+ * @param id The id of the module
+ * @param factory The original or patched module factory
+ * @returns The wrapper for the patched module factory
+ */
function patchFactory(id: PropertyKey, factory: ModuleFactory) {
const originalFactory = factory;
@@ -288,7 +315,10 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
if (!patch.all) patches.splice(i--, 1);
}
+ // The patched factory wrapper
const patchedFactory: PatchedModuleFactory = function (...args: Parameters) {
+ // 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
for (const moduleFactories of allModuleFactories) {
Reflect.defineProperty(moduleFactories, id, {
value: patchedFactory.$$vencordOriginal,
@@ -327,6 +357,7 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
let factoryReturn: unknown;
try {
+ // Call the patched factory
factoryReturn = factory.apply(this, args);
} catch (err) {
// Just re-throw Discord errors
diff --git a/src/webpack/wreq.d.ts b/src/webpack/wreq.d.ts
index 3dccb629d2..de392668b7 100644
--- a/src/webpack/wreq.d.ts
+++ b/src/webpack/wreq.d.ts
@@ -33,15 +33,15 @@ export type AsyncModuleBody = (
export type ChunkHandlers = {
/**
- * Ensures the js file for this chunk is loaded, or starts to load if it's not
+ * Ensures the js file for this chunk is loaded, or starts to load if it's not.
* @param chunkId The chunk id
- * @param promises The promises array to add the loading promise to.
+ * @param promises The promises array to add the loading promise to
*/
j: (this: ChunkHandlers, chunkId: PropertyKey, promises: Promise) => void,
/**
- * Ensures the css file for this chunk is loaded, or starts to load if it's not
+ * Ensures the css file for this chunk is loaded, or starts to load if it's not.
* @param chunkId The chunk id
- * @param promises The promises array to add the loading promise to. This array will likely contain the promise of the js file too.
+ * @param promises The promises array to add the loading promise to. This array will likely contain the promise of the js file too
*/
css: (this: ChunkHandlers, chunkId: PropertyKey, promises: Promise) => void,
};
@@ -171,7 +171,7 @@ export type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
*/
O: OnChunksLoaded;
/**
- * Instantiate a wasm instance with source using "wasmModuleHash", and importObject "importsObj", and then assign the exports of its instance to "exports"
+ * Instantiate a wasm instance with source using "wasmModuleHash", and importObject "importsObj", and then assign the exports of its instance to "exports".
* @returns The exports argument, but now assigned with the exports of the wasm instance
*/
v: (this: WebpackRequire, exports: ModuleExports, wasmModuleId: any, wasmModuleHash: string, importsObj?: WebAssembly.Imports) => Promise;
From 1f99162b5a4829e1e42aa61c243e101a3842a42b Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sun, 26 May 2024 20:38:57 -0300
Subject: [PATCH 060/125] update patchWebpack license
---
src/webpack/patchWebpack.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index 719cd40207..b29452a565 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -1,6 +1,6 @@
/*
* Vencord, a Discord client mod
- * Copyright (c) 2024 Vendicated and contributors
+ * Copyright (c) 2024 Vendicated, Nuckyz, and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
From 513eea8d1498806b116d5919a4f60377497c46c0 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Mon, 27 May 2024 18:27:21 -0300
Subject: [PATCH 061/125] Prepare for in case modules object is accessed
directly in the future
---
src/webpack/patchWebpack.ts | 45 ++++++++++++++++++++-----------------
1 file changed, 24 insertions(+), 21 deletions(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index b29452a565..570e410970 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -72,13 +72,7 @@ Reflect.defineProperty(Function.prototype, "m", {
// Patch the pre-populated factories
for (const id in moduleFactories) {
- if (Settings.eagerPatches) {
- // Patches the factory directly
- moduleFactories[id] = patchFactory(id, moduleFactories[id]);
- } else {
- // Define a getter for the patched version
- defineModulesFactoryGetter(id, moduleFactories[id]);
- }
+ defineModulesFactoryGetter(id, Settings.eagerPatches ? patchFactory(id, moduleFactories[id]) : moduleFactories[id]);
}
Reflect.defineProperty(moduleFactories, Symbol.toStringTag, {
@@ -90,6 +84,11 @@ Reflect.defineProperty(Function.prototype, "m", {
// 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", {
@@ -102,17 +101,17 @@ Reflect.defineProperty(Function.prototype, "m", {
});
/**
- * Define the getter for returning the patched version of the module factory. This only executes and patches the factory when its accessed for the first time.
+ * Define the getter for returning the patched version of the module factory.
*
- * It is what patches factories when eagerPatches are disabled.
+ * If eagerPatches is enabled, the factory argument should already be the patched version, else it will be the original
+ * and only be patched when accessed for the first time.
*
- * The factory argument will become the patched version of the factory once it is accessed.
* @param id The id of the module
* @param factory The original or patched module factory
*/
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
- // have the the patched version
+ // have the patched version
for (const moduleFactories of allModuleFactories) {
Reflect.defineProperty(moduleFactories, id, {
configurable: true,
@@ -138,6 +137,17 @@ function defineModulesFactoryGetter(id: PropertyKey, factory: PatchedModuleFacto
}
const moduleFactoriesHandler: ProxyHandler = {
+ /*
+ If Discord ever decides to set module factories using the variable of the modules object directly instead of wreq.m, we need to switch the proxy to the prototype
+ and that requires defining additional traps for keeping the object working
+
+ // Proxies on the prototype dont intercept "get" when the property is in the object itself. But in case it isn't we need to return undefined,
+ // to avoid Reflect.get having no effect and causing a stack overflow
+ get: (target, p, receiver) => {
+ return undefined;
+ },
+ */
+
// The set trap for patching or defining getters for the module factories when new module factories are loaded
set: (target, p, newValue, receiver) => {
// If the property is not a number, we are not dealing with a module factory
@@ -145,7 +155,7 @@ const moduleFactoriesHandler: ProxyHandler = {
return Reflect.set(target, p, newValue, receiver);
}
- const existingFactory = Reflect.get(target, p, target);
+ const existingFactory = Reflect.get(target, p, receiver);
if (!Settings.eagerPatches) {
// If existingFactory exists, its either wrapped in defineModuleFactoryGetter, or it has already been required
@@ -154,7 +164,7 @@ const moduleFactoriesHandler: ProxyHandler = {
return Reflect.set(target, p, newValue, receiver);
}
- // eagerPatches are disabled, so set up the getter for the patched version
+ // eagerPatches are disabled, so the factory argument should be the original
defineModulesFactoryGetter(p, newValue);
return true;
}
@@ -170,14 +180,7 @@ const moduleFactoriesHandler: ProxyHandler = {
// If multiple Webpack instances exist, when new a new module is loaded, it will be set in all the module factories objects.
// Because patches are only executed once, we need to set the patched version in all of them, to avoid the Webpack instance
// that uses the factory to contain the original factory instead of the patched, in case it was set first in another instance
- for (const moduleFactories of allModuleFactories) {
- Reflect.defineProperty(moduleFactories, p, {
- value: patchedFactory,
- configurable: true,
- enumerable: true,
- writable: true
- });
- }
+ defineModulesFactoryGetter(p, patchedFactory);
return true;
}
From a1542bcec5c5f92fe3432c6f517a31c5695e3a1c Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Mon, 27 May 2024 23:21:51 -0300
Subject: [PATCH 062/125] okay codium agent
---
scripts/generateReport.ts | 12 ++++++++----
src/webpack/webpack.ts | 8 +++++---
2 files changed, 13 insertions(+), 7 deletions(-)
diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts
index 89632addf1..cd37674049 100644
--- a/scripts/generateReport.ts
+++ b/scripts/generateReport.ts
@@ -42,8 +42,8 @@ const page = await browser.newPage();
await page.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36");
async function maybeGetError(handle: JSHandle) {
- return (handle as JSHandle)?.getProperty("message")
- .then(m => m.jsonValue());
+ return (handle as JSHandle).getProperty("message")
+ .then(m => m?.jsonValue() ?? "Unknown Error");
}
const report = {
@@ -356,7 +356,9 @@ async function runtime(token: string) {
// setImmediate to avoid blocking the factory patching execution while checking for lazy chunks
setTimeout(() => {
let isResolved = false;
- searchAndLoadLazyChunks(String(factory)).then(() => isResolved = true);
+ searchAndLoadLazyChunks(String(factory))
+ .then(() => isResolved = true)
+ .catch(() => isResolved = true);
chunksSearchPromises.push(() => isResolved);
}, 0);
@@ -364,7 +366,9 @@ async function runtime(token: string) {
for (const factoryId in wreq.m) {
let isResolved = false;
- searchAndLoadLazyChunks(String(wreq.m[factoryId])).then(() => isResolved = true);
+ searchAndLoadLazyChunks(String(wreq.m[factoryId]))
+ .then(() => isResolved = true)
+ .catch(() => isResolved = true);
chunksSearchPromises.push(() => isResolved);
}
diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts
index a46a7de459..d9301508de 100644
--- a/src/webpack/webpack.ts
+++ b/src/webpack/webpack.ts
@@ -76,6 +76,8 @@ export const factoryListeners = new Set<(factory: ModuleFactory) => void>();
export function _initWebpack(webpackRequire: WebpackRequire) {
wreq = webpackRequire;
+
+ if (webpackRequire.c == null) return;
cache = webpackRequire.c;
Reflect.defineProperty(webpackRequire.c, Symbol.toStringTag, {
@@ -507,10 +509,10 @@ export function search(...filters: Array) {
outer:
for (const id in factories) {
const factory = factories[id];
- const str = String(factory);
+ const factoryStr = String(factory);
for (const filter of filters) {
- if (typeof filter === "string" && !str.includes(filter)) continue outer;
- if (filter instanceof RegExp && !filter.test(str)) continue outer;
+ if (typeof filter === "string" && !factoryStr.includes(factoryStr)) continue outer;
+ if (filter instanceof RegExp && !filter.test(factoryStr)) continue outer;
}
results[id] = factory;
}
From e96458fafa4d1a3587b7299932b91b8ad6a48608 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Mon, 27 May 2024 23:31:08 -0300
Subject: [PATCH 063/125] fixes for if we use prototype proxy in the future
---
src/webpack/patchWebpack.ts | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index 570e410970..968a2ea0ce 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -88,7 +88,6 @@ Reflect.defineProperty(Function.prototype, "m", {
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", {
@@ -146,13 +145,23 @@ const moduleFactoriesHandler: ProxyHandler = {
get: (target, p, receiver) => {
return undefined;
},
+ // Same thing as get
+ has: (target, p) => {
+ return false;
+ }
*/
// The set trap for patching or defining getters for the module factories when new module factories are loaded
set: (target, p, newValue, receiver) => {
// If the property is not a number, we are not dealing with a module factory
if (Number.isNaN(Number(p))) {
- return Reflect.set(target, p, newValue, receiver);
+ Reflect.defineProperty(target, p, {
+ value: newValue,
+ configurable: true,
+ enumerable: true,
+ writable: true
+ });
+ return true;
}
const existingFactory = Reflect.get(target, p, receiver);
From 539b70cd52016276c599e9c03c26817b9b921fb6 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Mon, 27 May 2024 23:32:40 -0300
Subject: [PATCH 064/125] lmao oops
---
src/webpack/webpack.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts
index d9301508de..bde2697760 100644
--- a/src/webpack/webpack.ts
+++ b/src/webpack/webpack.ts
@@ -511,7 +511,7 @@ export function search(...filters: Array) {
const factory = factories[id];
const factoryStr = String(factory);
for (const filter of filters) {
- if (typeof filter === "string" && !factoryStr.includes(factoryStr)) continue outer;
+ if (typeof filter === "string" && !factoryStr.includes(filter)) continue outer;
if (filter instanceof RegExp && !filter.test(factoryStr)) continue outer;
}
results[id] = factory;
From 513d9f4209ad6e9de696a8dcc228cffa2355affd Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Tue, 28 May 2024 03:34:40 -0300
Subject: [PATCH 065/125] I knew I was forgetting something
---
src/webpack/wreq.d.ts | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/webpack/wreq.d.ts b/src/webpack/wreq.d.ts
index de392668b7..fe393a4c14 100644
--- a/src/webpack/wreq.d.ts
+++ b/src/webpack/wreq.d.ts
@@ -75,7 +75,9 @@ export type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
es: (this: WebpackRequire, fromObject: Record, toObject: Record) => Record;
/**
* Creates an async module. A module that exports something that is a Promise, or requires an export from an async module.
- * The body function must be an async function. "module.exports" will become a AsyncModulePromise.
+ *
+ * The body function must be an async function. "module.exports" will become a {@link AsyncModulePromise}.
+ *
* The body function will be called with a function to handle requires that import from an async module, and a function to resolve this async module. An example on how to handle async dependencies:
* @example
* const factory = (module, exports, wreq) => {
From c9c09b95a3f8faa99f29ac3b0725df57bcb4d539 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Tue, 28 May 2024 03:35:38 -0300
Subject: [PATCH 066/125] fix typo
---
src/webpack/wreq.d.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/webpack/wreq.d.ts b/src/webpack/wreq.d.ts
index fe393a4c14..246682f643 100644
--- a/src/webpack/wreq.d.ts
+++ b/src/webpack/wreq.d.ts
@@ -76,7 +76,7 @@ export type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
/**
* Creates an async module. A module that exports something that is a Promise, or requires an export from an async module.
*
- * The body function must be an async function. "module.exports" will become a {@link AsyncModulePromise}.
+ * The body function must be an async function. "module.exports" will become an {@link AsyncModulePromise}.
*
* The body function will be called with a function to handle requires that import from an async module, and a function to resolve this async module. An example on how to handle async dependencies:
* @example
@@ -84,7 +84,7 @@ export type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
* wreq.a(module, async (handleAsyncDependencies, asyncResult) => {
* try {
* const asyncRequireA = wreq(...);
- *
+ *
* const asyncDependencies = handleAsyncDependencies([asyncRequire]);
* const [requireAResult] = asyncDependencies.then != null ? (await asyncDependencies)() : asyncDependencies;
*
From acbc932542daa1104e3a8a58b6e52f16b8cd80c8 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Tue, 28 May 2024 03:57:56 -0300
Subject: [PATCH 067/125] give name to PatchedFactory
---
src/webpack/patchWebpack.ts | 160 ++++++++++++++++++------------------
1 file changed, 81 insertions(+), 79 deletions(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index 968a2ea0ce..d7e3630657 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -327,98 +327,100 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
if (!patch.all) patches.splice(i--, 1);
}
- // The patched factory wrapper
- const patchedFactory: PatchedModuleFactory = function (...args: Parameters) {
- // 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
- for (const moduleFactories of allModuleFactories) {
- Reflect.defineProperty(moduleFactories, id, {
- value: patchedFactory.$$vencordOriginal,
- configurable: true,
- enumerable: true,
- writable: true
- });
- }
+ // The patched factory wrapper, define it in an object to preserve the name after minification
+ const patchedFactory: PatchedModuleFactory = {
+ PatchedFactory(...args: Parameters) {
+ // 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
+ for (const moduleFactories of allModuleFactories) {
+ Reflect.defineProperty(moduleFactories, id, {
+ value: patchedFactory.$$vencordOriginal,
+ configurable: true,
+ enumerable: true,
+ writable: true
+ });
+ }
- // eslint-disable-next-line prefer-const
- let [module, exports, require] = args;
-
- if (wreq == null) {
- if (!wreqFallbackApplied) {
- wreqFallbackApplied = true;
-
- // Make sure the require argument is actually the WebpackRequire function
- if (typeof require === "function" && require.m != null) {
- const { stack } = new Error();
- const webpackInstanceFileName = stack?.match(/\/assets\/(.+?\.js)/)?.[1];
- logger.warn(
- "WebpackRequire was not initialized, falling back to WebpackRequire passed to the first called patched module factory (" +
- `id: ${String(id)}` + interpolateIfDefined`, WebpackInstance origin: ${webpackInstanceFileName}` +
- ")"
- );
- _initWebpack(require);
- } else if (IS_DEV) {
- logger.error("WebpackRequire was not initialized, running modules without patches instead.");
+ // eslint-disable-next-line prefer-const
+ let [module, exports, require] = args;
+
+ if (wreq == null) {
+ if (!wreqFallbackApplied) {
+ wreqFallbackApplied = true;
+
+ // Make sure the require argument is actually the WebpackRequire function
+ if (typeof require === "function" && require.m != null) {
+ const { stack } = new Error();
+ const webpackInstanceFileName = stack?.match(/\/assets\/(.+?\.js)/)?.[1];
+ logger.warn(
+ "WebpackRequire was not initialized, falling back to WebpackRequire passed to the first called patched module factory (" +
+ `id: ${String(id)}` + interpolateIfDefined`, WebpackInstance origin: ${webpackInstanceFileName}` +
+ ")"
+ );
+ _initWebpack(require);
+ } else if (IS_DEV) {
+ logger.error("WebpackRequire was not initialized, running modules without patches instead.");
+ }
}
- }
- if (IS_DEV) {
- return originalFactory.apply(this, args);
+ if (IS_DEV) {
+ return originalFactory.apply(this, args);
+ }
}
- }
-
- let factoryReturn: unknown;
- try {
- // Call the patched factory
- factoryReturn = factory.apply(this, args);
- } catch (err) {
- // Just re-throw Discord errors
- if (factory === originalFactory) throw err;
- logger.error("Error in patched module factory", err);
- return originalFactory.apply(this, args);
- }
-
- // Webpack sometimes sets the value of module.exports directly, so assign exports to it to make sure we properly handle it
- exports = module?.exports;
- if (exports == null) return factoryReturn;
-
- // There are (at the time of writing) 11 modules exporting the window
- // Make these non enumerable to improve webpack search performance
- if (exports === window && typeof require === "function" && require.c != null) {
- Reflect.defineProperty(require.c, id, {
- value: require.c[id],
- configurable: true,
- writable: true,
- enumerable: false
- });
- return factoryReturn;
- }
-
- for (const callback of moduleListeners) {
+ let factoryReturn: unknown;
try {
- callback(exports, id);
+ // Call the patched factory
+ factoryReturn = factory.apply(this, args);
} catch (err) {
- logger.error("Error in Webpack module listener:\n", err, callback);
+ // Just re-throw Discord errors
+ if (factory === originalFactory) throw err;
+
+ logger.error("Error in patched module factory", err);
+ return originalFactory.apply(this, args);
}
- }
- for (const [filter, callback] of subscriptions) {
- try {
- if (filter(exports)) {
- subscriptions.delete(filter);
+ // Webpack sometimes sets the value of module.exports directly, so assign exports to it to make sure we properly handle it
+ exports = module?.exports;
+ if (exports == null) return factoryReturn;
+
+ // There are (at the time of writing) 11 modules exporting the window
+ // Make these non enumerable to improve webpack search performance
+ if (exports === window && typeof require === "function" && require.c != null) {
+ Reflect.defineProperty(require.c, id, {
+ value: require.c[id],
+ configurable: true,
+ writable: true,
+ enumerable: false
+ });
+ return factoryReturn;
+ }
+
+ for (const callback of moduleListeners) {
+ try {
callback(exports, id);
- } else if (exports.default && filter(exports.default)) {
- subscriptions.delete(filter);
- callback(exports.default, id);
+ } catch (err) {
+ logger.error("Error in Webpack module listener:\n", err, callback);
}
- } catch (err) {
- logger.error("Error while firing callback for Webpack subscription:\n", err, filter, callback);
}
- }
- return factoryReturn;
- };
+ for (const [filter, callback] of subscriptions) {
+ try {
+ if (filter(exports)) {
+ subscriptions.delete(filter);
+ callback(exports, id);
+ } else if (exports.default && filter(exports.default)) {
+ subscriptions.delete(filter);
+ callback(exports.default, id);
+ }
+ } catch (err) {
+ logger.error("Error while firing callback for Webpack subscription:\n", err, filter, callback);
+ }
+ }
+
+ return factoryReturn;
+ }
+ }.PatchedFactory;
patchedFactory.toString = originalFactory.toString.bind(originalFactory);
patchedFactory.$$vencordOriginal = originalFactory;
From 9c2545ab16b31b6d1d0fcdf56f2aebcfd44a5799 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Tue, 28 May 2024 04:27:36 -0300
Subject: [PATCH 068/125] undo explosion
---
scripts/generateReport.ts | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts
index cd37674049..ef7ce04a33 100644
--- a/scripts/generateReport.ts
+++ b/scripts/generateReport.ts
@@ -41,9 +41,9 @@ const browser = await pup.launch({
const page = await browser.newPage();
await page.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36");
-async function maybeGetError(handle: JSHandle) {
- return (handle as JSHandle).getProperty("message")
- .then(m => m?.jsonValue() ?? "Unknown Error");
+async function maybeGetError(handle: JSHandle): Promise {
+ return await (handle as JSHandle)?.getProperty("message")
+ .then(m => m?.jsonValue());
}
const report = {
@@ -236,7 +236,7 @@ page.on("console", async e => {
const [, name] = failedToStartMatch;
report.badStarts.push({
plugin: name,
- error: cause
+ error: cause ?? "Unknown error"
});
break;
From 1eeadbcd97aeb3caa2fcfd80b99a3a3b94dfddf2 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Tue, 28 May 2024 17:11:17 -0300
Subject: [PATCH 069/125] export all webpack instances
---
src/Vencord.ts | 2 +-
src/webpack/patchWebpack.ts | 146 +++++++++++++++++-------------------
2 files changed, 69 insertions(+), 79 deletions(-)
diff --git a/src/Vencord.ts b/src/Vencord.ts
index 72541148e2..ea769c7895 100644
--- a/src/Vencord.ts
+++ b/src/Vencord.ts
@@ -23,10 +23,10 @@ export * as Util from "./utils";
export * as QuickCss from "./utils/quickCss";
export * as Updater from "./utils/updater";
export * as Webpack from "./webpack";
+export * as WebpackPatcher from "./webpack/patchWebpack";
export { PlainSettings, Settings };
import "./utils/quickCss";
-import "./webpack/patchWebpack";
import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab";
import { StartAt } from "@utils/types";
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index d7e3630657..25d0d20f13 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -14,6 +14,8 @@ import { traceFunction } from "../debug/Tracer";
import { patches } from "../plugins";
import { _initWebpack, factoryListeners, ModuleFactory, moduleListeners, subscriptions, WebpackRequire, wreq } from ".";
+type AnyWebpackRequire = Partial & Pick;
+
type PatchedModuleFactory = ModuleFactory & {
$$vencordOriginal?: ModuleFactory;
};
@@ -22,83 +24,87 @@ type PatchedModuleFactories = Record;
const logger = new Logger("WebpackInterceptor", "#8caaee");
-/** A set with all the module factories objects */
-const allModuleFactories = new Set();
+/** A set with all the Webpack instances */
+export const allWebpackInstances = new Set();
/** Whether we tried to fallback to factory WebpackRequire, or disabled patches */
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.
// 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.
// 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.
-Reflect.defineProperty(Function.prototype, "m", {
- configurable: true,
+define(Function.prototype, "m", {
+ enumerable: false,
set(this: WebpackRequire, moduleFactories: PatchedModuleFactories) {
// When using React DevTools or other extensions, we may also catch their Webpack here.
// This ensures we actually got the right ones.
const { stack } = new Error();
- if ((stack?.includes("discord.com") || stack?.includes("discordapp.com")) && !Array.isArray(moduleFactories)) {
- const fileName = stack.match(/\/assets\/(.+?\.js)/)?.[1];
- logger.info("Found Webpack module factories" + interpolateIfDefined` in ${fileName}`);
-
- // 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.
- Reflect.defineProperty(this, "p", {
- configurable: true,
-
- set(this: WebpackRequire, bundlePath: WebpackRequire["p"]) {
- if (bundlePath !== "/assets/") return;
-
- logger.info("Main Webpack found" + interpolateIfDefined` in ${fileName}` + ", initializing internal references to WebpackRequire");
- _initWebpack(this);
- clearTimeout(setterTimeout);
-
- Reflect.defineProperty(this, "p", {
- value: bundlePath,
- configurable: true,
- enumerable: true,
- writable: true
- });
- }
- });
- // 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(() => Reflect.deleteProperty(this, "p"), 0);
+ if (!(stack?.includes("discord.com") || stack?.includes("discordapp.com")) && Array.isArray(moduleFactories)) {
+ define(this, "m", { value: moduleFactories });
+ return;
+ }
- // This needs to be added before the loop below
- allModuleFactories.add(moduleFactories);
+ const fileName = stack?.match(/\/assets\/(.+?\.js)/)?.[1];
+ logger.info("Found Webpack module factories" + interpolateIfDefined` in ${fileName}`);
- // Patch the pre-populated factories
- for (const id in moduleFactories) {
- defineModulesFactoryGetter(id, Settings.eagerPatches ? patchFactory(id, moduleFactories[id]) : moduleFactories[id]);
- }
+ allWebpackInstances.add(this);
- 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));
- */
- }
+ // 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.
+ define(this, "p", {
+ enumerable: false,
+
+ set(this: WebpackRequire, bundlePath: WebpackRequire["p"]) {
+ if (bundlePath !== "/assets/") return;
+
+ logger.info("Main Webpack found" + interpolateIfDefined` in ${fileName}` + ", initializing internal references to WebpackRequire");
+ _initWebpack(this);
+ clearTimeout(setterTimeout);
- Reflect.defineProperty(this, "m", {
- value: moduleFactories,
- configurable: true,
- enumerable: true,
- writable: true
+ define(this, "p", { value: bundlePath });
+ }
+ });
+ // 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(() => Reflect.deleteProperty(this, "p"), 0);
+
+ define(moduleFactories, Symbol.toStringTag, {
+ value: "ModuleFactories",
+ 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));
+ */
+
+ define(this, "m", { value: moduleFactories });
+
+ // Patch the pre-populated factories
+ for (const id in moduleFactories) {
+ defineModulesFactoryGetter(id, Settings.eagerPatches ? patchFactory(id, moduleFactories[id]) : moduleFactories[id]);
+ }
}
});
+
/**
* Define the getter for returning the patched version of the module factory.
*
@@ -111,11 +117,8 @@ Reflect.defineProperty(Function.prototype, "m", {
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
// have the patched version
- for (const moduleFactories of allModuleFactories) {
- Reflect.defineProperty(moduleFactories, id, {
- configurable: true,
- enumerable: true,
-
+ for (const wreq of allWebpackInstances) {
+ define(wreq.m, id, {
get() {
// $$vencordOriginal means the factory is already patched
if (factory.$$vencordOriginal != null) {
@@ -155,13 +158,7 @@ const moduleFactoriesHandler: ProxyHandler = {
set: (target, p, newValue, receiver) => {
// If the property is not a number, we are not dealing with a module factory
if (Number.isNaN(Number(p))) {
- Reflect.defineProperty(target, p, {
- value: newValue,
- configurable: true,
- enumerable: true,
- writable: true
- });
- return true;
+ return define(target, p, { value: newValue });
}
const existingFactory = Reflect.get(target, p, receiver);
@@ -332,13 +329,8 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
PatchedFactory(...args: Parameters) {
// 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
- for (const moduleFactories of allModuleFactories) {
- Reflect.defineProperty(moduleFactories, id, {
- value: patchedFactory.$$vencordOriginal,
- configurable: true,
- enumerable: true,
- writable: true
- });
+ for (const wreq of allWebpackInstances) {
+ define(wreq.m, id, { value: patchedFactory.$$vencordOriginal });
}
// eslint-disable-next-line prefer-const
@@ -387,10 +379,8 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
// There are (at the time of writing) 11 modules exporting the window
// Make these non enumerable to improve webpack search performance
if (exports === window && typeof require === "function" && require.c != null) {
- Reflect.defineProperty(require.c, id, {
+ define(require.c, id, {
value: require.c[id],
- configurable: true,
- writable: true,
enumerable: false
});
return factoryReturn;
From 6b648f3d381dbfc9027d8f85839a6fd26f4c7830 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Tue, 28 May 2024 17:12:36 -0300
Subject: [PATCH 070/125] oops!
---
src/webpack/patchWebpack.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index 25d0d20f13..40c428e650 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -54,7 +54,7 @@ define(Function.prototype, "m", {
// When using React DevTools or other extensions, we may also catch their Webpack here.
// This ensures we actually got the right ones.
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)) {
define(this, "m", { value: moduleFactories });
return;
}
From 64262001e8a4d6203864fcf76906238980a31df1 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Tue, 28 May 2024 17:14:35 -0300
Subject: [PATCH 071/125] boop
---
src/webpack/patchWebpack.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index 40c428e650..6cbcd2a464 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -89,13 +89,13 @@ define(Function.prototype, "m", {
});
// The proxy responsible for patching the module factories when they are set, or definining getters for the patched versions
- moduleFactories = new Proxy(moduleFactories, moduleFactoriesHandler);
+ 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: moduleFactories });
+ define(this, "m", { value: proxiedModuleFactories });
// Patch the pre-populated factories
for (const id in moduleFactories) {
From d78f1c80004e0558dd5d520316aaa0d423dec420 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Tue, 28 May 2024 17:15:25 -0300
Subject: [PATCH 072/125] where did this come from?
---
src/webpack/patchWebpack.ts | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index 6cbcd2a464..b19b16b9ae 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -104,7 +104,6 @@ define(Function.prototype, "m", {
}
});
-
/**
* Define the getter for returning the patched version of the module factory.
*
From 84b9e3fec173e2fd2bba33a2f900ba02b0ee1077 Mon Sep 17 00:00:00 2001
From: vee
Date: Tue, 28 May 2024 22:31:58 +0200
Subject: [PATCH 073/125] ConsoleShortcuts: Fix autocomplete on lazies, add
more utils (#2519)
---
src/plugins/consoleShortcuts/index.ts | 260 +++++++++++++++----------
src/plugins/consoleShortcuts/native.ts | 16 ++
src/utils/lazy.ts | 29 +--
3 files changed, 192 insertions(+), 113 deletions(-)
create mode 100644 src/plugins/consoleShortcuts/native.ts
diff --git a/src/plugins/consoleShortcuts/index.ts b/src/plugins/consoleShortcuts/index.ts
index b0efe8a08f..ee86b5fcfd 100644
--- a/src/plugins/consoleShortcuts/index.ts
+++ b/src/plugins/consoleShortcuts/index.ts
@@ -17,138 +17,198 @@
*/
import { Devs } from "@utils/constants";
+import { getCurrentChannel, getCurrentGuild } from "@utils/discord";
+import { SYM_LAZY_CACHED, SYM_LAZY_GET } from "@utils/lazy";
import { relaunch } from "@utils/native";
import { canonicalizeMatch, canonicalizeReplace, canonicalizeReplacement } from "@utils/patches";
-import definePlugin, { StartAt } from "@utils/types";
+import definePlugin, { PluginNative, StartAt } from "@utils/types";
import * as Webpack from "@webpack";
import { extract, filters, findAll, findModuleId, search } from "@webpack";
import * as Common from "@webpack/common";
import type { ComponentType } from "react";
-const WEB_ONLY = (f: string) => () => {
+const DESKTOP_ONLY = (f: string) => () => {
throw new Error(`'${f}' is Discord Desktop only.`);
};
+const define: typeof Object.defineProperty =
+ (obj, prop, desc) => {
+ if (Object.hasOwn(desc, "value"))
+ desc.writable = true;
+
+ return Object.defineProperty(obj, prop, {
+ configurable: true,
+ enumerable: true,
+ ...desc
+ });
+ };
+
+function makeShortcuts() {
+ function newFindWrapper(filterFactory: (...props: any[]) => Webpack.FilterFn) {
+ const cache = new Map();
+
+ return function (...filterProps: unknown[]) {
+ const cacheKey = String(filterProps);
+ if (cache.has(cacheKey)) return cache.get(cacheKey);
+
+ const matches = findAll(filterFactory(...filterProps));
+
+ const result = (() => {
+ switch (matches.length) {
+ case 0: return null;
+ case 1: return matches[0];
+ default:
+ const uniqueMatches = [...new Set(matches)];
+ if (uniqueMatches.length > 1)
+ console.warn(`Warning: This filter matches ${matches.length} modules. Make it more specific!\n`, uniqueMatches);
+
+ return matches[0];
+ }
+ })();
+ if (result && cacheKey) cache.set(cacheKey, result);
+ return result;
+ };
+ }
+
+ let fakeRenderWin: WeakRef | undefined;
+ const find = newFindWrapper(f => f);
+ const findByProps = newFindWrapper(filters.byProps);
+
+ return {
+ ...Object.fromEntries(Object.keys(Common).map(key => [key, { getter: () => Common[key] }])),
+ wp: Webpack,
+ wpc: { getter: () => Webpack.cache },
+ wreq: { getter: () => Webpack.wreq },
+ wpsearch: search,
+ wpex: extract,
+ wpexs: (code: string) => extract(findModuleId(code)!),
+ find,
+ findAll: findAll,
+ findByProps,
+ findAllByProps: (...props: string[]) => findAll(filters.byProps(...props)),
+ findByCode: newFindWrapper(filters.byCode),
+ findAllByCode: (code: string) => findAll(filters.byCode(code)),
+ findComponentByCode: newFindWrapper(filters.componentByCode),
+ findAllComponentsByCode: (...code: string[]) => findAll(filters.componentByCode(...code)),
+ findExportedComponent: (...props: string[]) => findByProps(...props)[props[0]],
+ findStore: newFindWrapper(filters.byStoreName),
+ PluginsApi: { getter: () => Vencord.Plugins },
+ plugins: { getter: () => Vencord.Plugins.plugins },
+ Settings: { getter: () => Vencord.Settings },
+ Api: { getter: () => Vencord.Api },
+ Util: { getter: () => Vencord.Util },
+ reload: () => location.reload(),
+ restart: IS_WEB ? DESKTOP_ONLY("restart") : relaunch,
+ canonicalizeMatch,
+ canonicalizeReplace,
+ canonicalizeReplacement,
+ fakeRender: (component: ComponentType, props: any) => {
+ const prevWin = fakeRenderWin?.deref();
+ const win = prevWin?.closed === false
+ ? prevWin
+ : window.open("about:blank", "Fake Render", "popup,width=500,height=500")!;
+ fakeRenderWin = new WeakRef(win);
+ win.focus();
+
+ const doc = win.document;
+ doc.body.style.margin = "1em";
+
+ if (!win.prepared) {
+ win.prepared = true;
+
+ [...document.querySelectorAll("style"), ...document.querySelectorAll("link[rel=stylesheet]")].forEach(s => {
+ const n = s.cloneNode(true) as HTMLStyleElement | HTMLLinkElement;
+
+ if (s.parentElement?.tagName === "HEAD")
+ doc.head.append(n);
+ else if (n.id?.startsWith("vencord-") || n.id?.startsWith("vcd-"))
+ doc.documentElement.append(n);
+ else
+ doc.body.append(n);
+ });
+ }
+
+ Common.ReactDOM.render(Common.React.createElement(component, props), doc.body.appendChild(document.createElement("div")));
+ },
+
+ preEnable: (plugin: string) => (Vencord.Settings.plugins[plugin] ??= { enabled: true }).enabled = true,
+
+ channel: { getter: () => getCurrentChannel(), preload: false },
+ channelId: { getter: () => Common.SelectedChannelStore.getChannelId(), preload: false },
+ guild: { getter: () => getCurrentGuild(), preload: false },
+ guildId: { getter: () => Common.SelectedGuildStore.getGuildId(), preload: false },
+ me: { getter: () => Common.UserStore.getCurrentUser(), preload: false },
+ meId: { getter: () => Common.UserStore.getCurrentUser().id, preload: false },
+ messages: { getter: () => Common.MessageStore.getMessages(Common.SelectedChannelStore.getChannelId()), preload: false }
+ };
+}
+
+function loadAndCacheShortcut(key: string, val: any, forceLoad: boolean) {
+ const currentVal = val.getter();
+ if (!currentVal || val.preload === false) return currentVal;
+
+ const value = currentVal[SYM_LAZY_GET]
+ ? forceLoad ? currentVal[SYM_LAZY_GET]() : currentVal[SYM_LAZY_CACHED]
+ : currentVal;
+
+ if (value) define(window.shortcutList, key, { value });
+
+ return value;
+}
+
export default definePlugin({
name: "ConsoleShortcuts",
description: "Adds shorter Aliases for many things on the window. Run `shortcutList` for a list.",
authors: [Devs.Ven],
- getShortcuts(): Record {
- function newFindWrapper(filterFactory: (...props: any[]) => Webpack.FilterFn) {
- const cache = new Map();
-
- return function (...filterProps: unknown[]) {
- const cacheKey = String(filterProps);
- if (cache.has(cacheKey)) return cache.get(cacheKey);
-
- const matches = findAll(filterFactory(...filterProps));
-
- const result = (() => {
- switch (matches.length) {
- case 0: return null;
- case 1: return matches[0];
- default:
- const uniqueMatches = [...new Set(matches)];
- if (uniqueMatches.length > 1)
- console.warn(`Warning: This filter matches ${matches.length} modules. Make it more specific!\n`, uniqueMatches);
-
- return matches[0];
- }
- })();
- if (result && cacheKey) cache.set(cacheKey, result);
- return result;
- };
- }
-
- let fakeRenderWin: WeakRef | undefined;
- const find = newFindWrapper(f => f);
- const findByProps = newFindWrapper(filters.byProps);
-
- return {
- ...Object.fromEntries(Object.keys(Common).map(key => [key, { getter: () => Common[key] }])),
- wp: Webpack,
- wpc: { getter: () => Webpack.cache },
- wreq: { getter: () => Webpack.wreq },
- wpsearch: search,
- wpex: extract,
- wpexs: (code: string) => extract(findModuleId(code)!),
- find,
- findAll: findAll,
- findByProps,
- findAllByProps: (...props: string[]) => findAll(filters.byProps(...props)),
- findByCode: newFindWrapper(filters.byCode),
- findAllByCode: (code: string) => findAll(filters.byCode(code)),
- findComponentByCode: newFindWrapper(filters.componentByCode),
- findAllComponentsByCode: (...code: string[]) => findAll(filters.componentByCode(...code)),
- findExportedComponent: (...props: string[]) => findByProps(...props)[props[0]],
- findStore: newFindWrapper(filters.byStoreName),
- PluginsApi: { getter: () => Vencord.Plugins },
- plugins: { getter: () => Vencord.Plugins.plugins },
- Settings: { getter: () => Vencord.Settings },
- Api: { getter: () => Vencord.Api },
- reload: () => location.reload(),
- restart: IS_WEB ? WEB_ONLY("restart") : relaunch,
- canonicalizeMatch,
- canonicalizeReplace,
- canonicalizeReplacement,
- fakeRender: (component: ComponentType, props: any) => {
- const prevWin = fakeRenderWin?.deref();
- const win = prevWin?.closed === false ? prevWin : window.open("about:blank", "Fake Render", "popup,width=500,height=500")!;
- fakeRenderWin = new WeakRef(win);
- win.focus();
-
- const doc = win.document;
- doc.body.style.margin = "1em";
-
- if (!win.prepared) {
- win.prepared = true;
-
- [...document.querySelectorAll("style"), ...document.querySelectorAll("link[rel=stylesheet]")].forEach(s => {
- const n = s.cloneNode(true) as HTMLStyleElement | HTMLLinkElement;
-
- if (s.parentElement?.tagName === "HEAD")
- doc.head.append(n);
- else if (n.id?.startsWith("vencord-") || n.id?.startsWith("vcd-"))
- doc.documentElement.append(n);
- else
- doc.body.append(n);
- });
- }
-
- Common.ReactDOM.render(Common.React.createElement(component, props), doc.body.appendChild(document.createElement("div")));
- }
- };
- },
-
startAt: StartAt.Init,
start() {
- const shortcuts = this.getShortcuts();
+ const shortcuts = makeShortcuts();
window.shortcutList = {};
for (const [key, val] of Object.entries(shortcuts)) {
- if (val.getter != null) {
- Object.defineProperty(window.shortcutList, key, {
- get: val.getter,
- configurable: true,
- enumerable: true
+ if ("getter" in val) {
+ define(window.shortcutList, key, {
+ get: () => loadAndCacheShortcut(key, val, true)
});
- Object.defineProperty(window, key, {
- get: () => window.shortcutList[key],
- configurable: true,
- enumerable: true
+ define(window, key, {
+ get: () => window.shortcutList[key]
});
} else {
window.shortcutList[key] = val;
window[key] = val;
}
}
+
+ // unproxy loaded modules
+ Webpack.onceReady.then(() => {
+ setTimeout(() => this.eagerLoad(false), 1000);
+
+ if (!IS_WEB) {
+ const Native = VencordNative.pluginHelpers.ConsoleShortcuts as PluginNative;
+ Native.initDevtoolsOpenEagerLoad();
+ }
+ });
+ },
+
+ async eagerLoad(forceLoad: boolean) {
+ await Webpack.onceReady;
+
+ const shortcuts = makeShortcuts();
+
+ for (const [key, val] of Object.entries(shortcuts)) {
+ if (!Object.hasOwn(val, "getter") || (val as any).preload === false) continue;
+
+ try {
+ loadAndCacheShortcut(key, val, forceLoad);
+ } catch { } // swallow not found errors in DEV
+ }
},
stop() {
delete window.shortcutList;
- for (const key in this.getShortcuts()) {
+ for (const key in makeShortcuts()) {
delete window[key];
}
}
diff --git a/src/plugins/consoleShortcuts/native.ts b/src/plugins/consoleShortcuts/native.ts
new file mode 100644
index 0000000000..763b239a47
--- /dev/null
+++ b/src/plugins/consoleShortcuts/native.ts
@@ -0,0 +1,16 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2024 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import { IpcMainInvokeEvent } from "electron";
+
+export function initDevtoolsOpenEagerLoad(e: IpcMainInvokeEvent) {
+ const handleDevtoolsOpened = () => e.sender.executeJavaScript("Vencord.Plugins.plugins.ConsoleShortcuts.eagerLoad(true)");
+
+ if (e.sender.isDevToolsOpened())
+ handleDevtoolsOpened();
+ else
+ e.sender.once("devtools-opened", () => handleDevtoolsOpened());
+}
diff --git a/src/utils/lazy.ts b/src/utils/lazy.ts
index b05855fa09..b86533d4d0 100644
--- a/src/utils/lazy.ts
+++ b/src/utils/lazy.ts
@@ -33,8 +33,8 @@ export function makeLazy(factory: () => T, attempts = 5): () => T {
const handler: ProxyHandler = {};
-const kGET = Symbol.for("vencord.lazy.get");
-const kCACHE = Symbol.for("vencord.lazy.cached");
+export const SYM_LAZY_GET = Symbol.for("vencord.lazy.get");
+export const SYM_LAZY_CACHED = Symbol.for("vencord.lazy.cached");
for (const method of [
"apply",
@@ -51,11 +51,11 @@ for (const method of [
"setPrototypeOf"
]) {
handler[method] =
- (target: any, ...args: any[]) => Reflect[method](target[kGET](), ...args);
+ (target: any, ...args: any[]) => Reflect[method](target[SYM_LAZY_GET](), ...args);
}
handler.ownKeys = target => {
- const v = target[kGET]();
+ const v = target[SYM_LAZY_GET]();
const keys = Reflect.ownKeys(v);
for (const key of UNCONFIGURABLE_PROPERTIES) {
if (!keys.includes(key)) keys.push(key);
@@ -67,7 +67,7 @@ handler.getOwnPropertyDescriptor = (target, p) => {
if (typeof p === "string" && UNCONFIGURABLE_PROPERTIES.includes(p))
return Reflect.getOwnPropertyDescriptor(target, p);
- const descriptor = Reflect.getOwnPropertyDescriptor(target[kGET](), p);
+ const descriptor = Reflect.getOwnPropertyDescriptor(target[SYM_LAZY_GET](), p);
if (descriptor) Object.defineProperty(target, p, descriptor);
return descriptor;
@@ -90,31 +90,34 @@ export function proxyLazy(factory: () => T, attempts = 5, isChild = false): T
let tries = 0;
const proxyDummy = Object.assign(function () { }, {
- [kCACHE]: void 0 as T | undefined,
- [kGET]() {
- if (!proxyDummy[kCACHE] && attempts > tries++) {
- proxyDummy[kCACHE] = factory();
- if (!proxyDummy[kCACHE] && attempts === tries)
+ [SYM_LAZY_CACHED]: void 0 as T | undefined,
+ [SYM_LAZY_GET]() {
+ if (!proxyDummy[SYM_LAZY_CACHED] && attempts > tries++) {
+ proxyDummy[SYM_LAZY_CACHED] = factory();
+ if (!proxyDummy[SYM_LAZY_CACHED] && attempts === tries)
console.error("Lazy factory failed:", factory);
}
- return proxyDummy[kCACHE];
+ return proxyDummy[SYM_LAZY_CACHED];
}
});
return new Proxy(proxyDummy, {
...handler,
get(target, p, receiver) {
+ if (p === SYM_LAZY_CACHED || p === SYM_LAZY_GET)
+ return Reflect.get(target, p, receiver);
+
// if we're still in the same tick, it means the lazy was immediately used.
// thus, we lazy proxy the get access to make things like destructuring work as expected
// meow here will also be a lazy
// `const { meow } = findByPropsLazy("meow");`
if (!isChild && isSameTick)
return proxyLazy(
- () => Reflect.get(target[kGET](), p, receiver),
+ () => Reflect.get(target[SYM_LAZY_GET](), p, receiver),
attempts,
true
);
- const lazyTarget = target[kGET]();
+ const lazyTarget = target[SYM_LAZY_GET]();
if (typeof lazyTarget === "object" || typeof lazyTarget === "function") {
return Reflect.get(lazyTarget, p, receiver);
}
From a4073703fd628622e2b22f59a2b9252cce4db9d7 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Tue, 28 May 2024 17:44:34 -0300
Subject: [PATCH 074/125] explain UNCONFIGURABLE_PROPERTIES better
---
src/utils/misc.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/utils/misc.tsx b/src/utils/misc.tsx
index 089bd541c9..424386a269 100644
--- a/src/utils/misc.tsx
+++ b/src/utils/misc.tsx
@@ -100,7 +100,7 @@ export function pluralise(amount: number, singular: string, plural = singular +
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 function interpolateIfDefined(strings: TemplateStringsArray, ...args: any[]) {
From 73503945fba7732fae634e754bdfc9658890ddcf Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Tue, 28 May 2024 17:58:54 -0300
Subject: [PATCH 075/125] Add WebpackInstances shortcut
---
src/plugins/consoleShortcuts/index.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/plugins/consoleShortcuts/index.ts b/src/plugins/consoleShortcuts/index.ts
index ee86b5fcfd..3d66258a70 100644
--- a/src/plugins/consoleShortcuts/index.ts
+++ b/src/plugins/consoleShortcuts/index.ts
@@ -79,6 +79,7 @@ function makeShortcuts() {
wp: Webpack,
wpc: { getter: () => Webpack.cache },
wreq: { getter: () => Webpack.wreq },
+ WebpackInstances: { getter: () => Vencord.WebpackPatcher.allWebpackInstances },
wpsearch: search,
wpex: extract,
wpexs: (code: string) => extract(findModuleId(code)!),
From 4a872236e1efcb04d0b895ba8d177f75462764b5 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Wed, 29 May 2024 06:03:31 -0300
Subject: [PATCH 076/125] Fix reporter breaking because of ConsoleShortcuts
---
scripts/generateReport.ts | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts
index 8bb87d812c..8d64451430 100644
--- a/scripts/generateReport.ts
+++ b/scripts/generateReport.ts
@@ -204,8 +204,13 @@ page.on("console", async e => {
report.badWebpackFinds.push(await rawArgs[1].jsonValue() as string);
}
- if (isVencord) {
- const args = await Promise.all(e.args().map(a => a.jsonValue()));
+ if (isVencord && level !== "error") {
+ let args: unknown[] = [];
+ try {
+ args = await Promise.all(e.args().map(a => a.jsonValue()));
+ } catch {
+ return;
+ }
const [, tag, message] = args as Array;
const cause = await maybeGetError(e.args()[3]);
From c8602ef52be0a1171be9506f23ba6208f0be4436 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Wed, 29 May 2024 06:45:44 -0300
Subject: [PATCH 077/125] Fix reporter breaking because of ConsoleShortcuts
---
scripts/generateReport.ts | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts
index 8bb87d812c..200aa3f834 100644
--- a/scripts/generateReport.ts
+++ b/scripts/generateReport.ts
@@ -205,7 +205,12 @@ page.on("console", async e => {
}
if (isVencord) {
- const args = await Promise.all(e.args().map(a => a.jsonValue()));
+ let args: unknown[] = [];
+ try {
+ args = await Promise.all(e.args().map(a => a.jsonValue()));
+ } catch {
+ return;
+ }
const [, tag, message] = args as Array;
const cause = await maybeGetError(e.args()[3]);
From 892de53603a1af1bf9f4dbd331868f12fec2edc0 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Thu, 30 May 2024 00:51:23 -0300
Subject: [PATCH 078/125] Fix extractAndLoadChunks issue with 2 match groups;
Improve testing of lazy extractAndLoadChunks
---
scripts/generateReport.ts | 11 +++++------
src/webpack/webpack.ts | 37 ++++++++++++++++++++++++++-----------
2 files changed, 31 insertions(+), 17 deletions(-)
diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts
index 200aa3f834..d3068492f8 100644
--- a/scripts/generateReport.ts
+++ b/scripts/generateReport.ts
@@ -343,7 +343,7 @@ async function runtime(token: string) {
// True if resolved, false otherwise
const chunksSearchPromises = [] as Array<() => boolean>;
- const LazyChunkRegex = canonicalizeMatch(/(?:Promise\.all\(\[(\i\.\i\("[^)]+?"\)[^\]]+?)\]\)|(\i\.\i\("[^)]+?"\)))\.then\(\i\.bind\(\i,"([^)]+?)"\)\)/g);
+ const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("[^)]+?"\)[^\]]*?)(?:\]\))?)\.then\(\i\.bind\(\i,"([^)]+?)"\)\)/g);
async function searchAndLoadLazyChunks(factoryCode: string) {
const lazyChunks = factoryCode.matchAll(LazyChunkRegex);
@@ -353,8 +353,7 @@ async function runtime(token: string) {
// the chunk containing the component
const shouldForceDefer = factoryCode.includes(".Messages.GUILD_FEED_UNFEATURE_BUTTON_TEXT");
- await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIdsArray, rawChunkIdsSingle, entryPoint]) => {
- const rawChunkIds = rawChunkIdsArray ?? rawChunkIdsSingle;
+ await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => {
const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Vencord.Webpack.ChunkIdsRegex)).map(m => m[1]) : [];
if (chunkIds.length === 0) {
@@ -525,14 +524,14 @@ async function runtime(token: string) {
} else if (method === "extractAndLoadChunks") {
const [code, matcher] = args;
- const module = Vencord.Webpack.findModuleFactory(...code);
- if (module) result = module.toString().match(canonicalizeMatch(matcher));
+ result = await Vencord.Webpack.extractAndLoadChunks(code, matcher);
+ if (result === false) result = null;
} else {
// @ts-ignore
result = Vencord.Webpack[method](...args);
}
- if (result == null || ("$$vencordInternal" in result && result.$$vencordInternal() == null)) throw "a rock at ben shapiro";
+ if (result == null || (result.$$vencordInternal != null && result.$$vencordInternal() == null)) throw "a rock at ben shapiro";
} catch (e) {
let logMessage = searchType;
if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") logMessage += `(${args[0].toString().slice(0, 147)}...)`;
diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts
index f2e6e58a81..9e0c0d000e 100644
--- a/src/webpack/webpack.ts
+++ b/src/webpack/webpack.ts
@@ -402,14 +402,14 @@ export function findExportedComponentLazy(...props: stri
});
}
-export const DefaultExtractAndLoadChunksRegex = /(?:Promise\.all\(\[(\i\.\i\("[^)]+?"\)[^\]]+?)\]\)|(\i\.\i\("[^)]+?"\))|Promise\.resolve\(\))\.then\(\i\.bind\(\i,"([^)]+?)"\)\)/;
-export const ChunkIdsRegex = /\("(.+?)"\)/g;
+export const DefaultExtractAndLoadChunksRegex = /(?:(?:Promise\.all\(\[)?(\i\.e\("[^)]+?"\)[^\]]*?)(?:\]\))?|Promise\.resolve\(\))\.then\(\i\.bind\(\i,"([^)]+?)"\)\)/;
+export const ChunkIdsRegex = /\("([^"]+?)"\)/g;
/**
* Extract and load chunks using their entry point
* @param code An array of all the code the module factory containing the lazy chunk loading must include
- * @param matcher A RegExp that returns the chunk ids array as the first capture group and the entry point id as the second. Defaults to a matcher that captures the lazy chunk loading found in the module factory
- * @returns A promise that resolves when the chunks were loaded
+ * @param matcher A RegExp that returns the chunk ids array as the first capture group and the entry point id as the second. Defaults to a matcher that captures the first lazy chunk loading found in the module factory
+ * @returns A promise that resolves with a boolean whether the chunks were loaded
*/
export async function extractAndLoadChunks(code: string[], matcher: RegExp = DefaultExtractAndLoadChunksRegex) {
const module = findModuleFactory(...code);
@@ -417,7 +417,11 @@ export async function extractAndLoadChunks(code: string[], matcher: RegExp = Def
const err = new Error("extractAndLoadChunks: Couldn't find module factory");
logger.warn(err, "Code:", code, "Matcher:", matcher);
- return;
+ // Strict behaviour in DevBuilds to fail early and make sure the issue is found
+ if (IS_DEV && !devToolsOpen)
+ throw err;
+
+ return false;
}
const match = module.toString().match(canonicalizeMatch(matcher));
@@ -429,10 +433,10 @@ export async function extractAndLoadChunks(code: string[], matcher: RegExp = Def
if (IS_DEV && !devToolsOpen)
throw err;
- return;
+ return false;
}
- const [, rawChunkIdsArray, rawChunkIdsSingle, entryPointId] = match;
+ const [, rawChunkIds, entryPointId] = match;
if (Number.isNaN(Number(entryPointId))) {
const err = new Error("extractAndLoadChunks: Matcher didn't return a capturing group with the chunk ids array, or the entry point id returned as the second group wasn't a number");
logger.warn(err, "Code:", code, "Matcher:", matcher);
@@ -441,16 +445,27 @@ export async function extractAndLoadChunks(code: string[], matcher: RegExp = Def
if (IS_DEV && !devToolsOpen)
throw err;
- return;
+ return false;
}
- const rawChunkIds = rawChunkIdsArray ?? rawChunkIdsSingle;
if (rawChunkIds) {
const chunkIds = Array.from(rawChunkIds.matchAll(ChunkIdsRegex)).map((m: any) => m[1]);
await Promise.all(chunkIds.map(id => wreq.e(id)));
}
+ if (wreq.m[entryPointId] == null) {
+ const err = new Error("extractAndLoadChunks: Entry point is not loaded in the module factories, perhaps one of the chunks failed to load");
+ logger.warn(err, "Code:", code, "Matcher:", matcher);
+
+ // Strict behaviour in DevBuilds to fail early and make sure the issue is found
+ if (IS_DEV && !devToolsOpen)
+ throw err;
+
+ return false;
+ }
+
wreq(entryPointId);
+ return true;
}
/**
@@ -458,8 +473,8 @@ export async function extractAndLoadChunks(code: string[], matcher: RegExp = Def
*
* Extract and load chunks using their entry point
* @param code An array of all the code the module factory containing the lazy chunk loading must include
- * @param matcher A RegExp that returns the chunk ids array as the first capture group and the entry point id as the second. Defaults to a matcher that captures the lazy chunk loading found in the module factory
- * @returns A function that returns a promise that resolves when the chunks were loaded, on first call
+ * @param matcher A RegExp that returns the chunk ids array as the first capture group and the entry point id as the second. Defaults to a matcher that captures the first lazy chunk loading found in the module factory
+ * @returns A function that returns a promise that resolves with a boolean whether the chunks were loaded, on first call
*/
export function extractAndLoadChunksLazy(code: string[], matcher = DefaultExtractAndLoadChunksRegex) {
if (IS_DEV) lazyWebpackSearchHistory.push(["extractAndLoadChunks", [code, matcher]]);
From 9a9c1b04871599000557d068ab7d00c5b495bfa5 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Thu, 30 May 2024 04:44:13 -0300
Subject: [PATCH 079/125] Reporter: Properly implement reporter build of
Vencord; Test more plugins; Fix running in wrong pages
---
.github/workflows/reportBrokenPlugins.yml | 4 +-
package.json | 2 +
scripts/build/build.mjs | 22 ++---
scripts/build/buildWeb.mjs | 52 ++++++-----
scripts/build/common.mjs | 30 ++++---
scripts/generateReport.ts | 47 ++--------
src/debug/Tracer.ts | 8 +-
src/globals.d.ts | 3 +-
src/plugins/arRPC.web/index.tsx | 3 +-
src/plugins/devCompanion.dev/index.tsx | 3 +-
src/plugins/index.ts | 94 ++++++++++++++------
src/plugins/invisibleChat.desktop/index.tsx | 6 +-
src/plugins/shikiCodeblocks.desktop/index.ts | 6 +-
src/plugins/vcNarrator/index.tsx | 3 +-
src/plugins/xsOverlay.desktop/index.ts | 4 +-
src/utils/Logger.ts | 5 ++
src/utils/types.ts | 11 +++
src/webpack/common/internal.tsx | 4 +-
src/webpack/webpack.ts | 22 ++---
19 files changed, 184 insertions(+), 145 deletions(-)
diff --git a/.github/workflows/reportBrokenPlugins.yml b/.github/workflows/reportBrokenPlugins.yml
index d3a882fa35..a669c1a276 100644
--- a/.github/workflows/reportBrokenPlugins.yml
+++ b/.github/workflows/reportBrokenPlugins.yml
@@ -37,8 +37,8 @@ jobs:
with:
chrome-version: stable
- - name: Build web
- run: pnpm buildWeb --standalone --dev
+ - name: Build Vencord Reporter Version
+ run: pnpm buildReporter
- name: Create Report
timeout-minutes: 10
diff --git a/package.json b/package.json
index 29b1506e2a..881e42a287 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,9 @@
"build": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/build.mjs",
"buildStandalone": "pnpm build --standalone",
"buildWeb": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/buildWeb.mjs",
+ "buildReporter": "pnpm buildWeb --standalone --reporter --skip-extension",
"watch": "pnpm build --watch",
+ "watchWeb": "pnpm buildWeb --watch",
"generatePluginJson": "tsx scripts/generatePluginList.ts",
"generateTypes": "tspc --emitDeclarationOnly --declaration --outDir packages/vencord-types",
"inject": "node scripts/runInstaller.mjs",
diff --git a/scripts/build/build.mjs b/scripts/build/build.mjs
index 0c2a930a05..fcf56f66c6 100755
--- a/scripts/build/build.mjs
+++ b/scripts/build/build.mjs
@@ -21,19 +21,21 @@ import esbuild from "esbuild";
import { readdir } from "fs/promises";
import { join } from "path";
-import { BUILD_TIMESTAMP, commonOpts, existsAsync, globPlugins, isDev, isStandalone, updaterDisabled, VERSION, watch } from "./common.mjs";
+import { BUILD_TIMESTAMP, commonOpts, exists, globPlugins, IS_DEV, IS_REPORTER, IS_STANDALONE, IS_UPDATER_DISABLED, VERSION, watch } from "./common.mjs";
const defines = {
- IS_STANDALONE: isStandalone,
- IS_DEV: JSON.stringify(isDev),
- IS_UPDATER_DISABLED: updaterDisabled,
+ IS_STANDALONE,
+ IS_DEV,
+ IS_REPORTER,
+ IS_UPDATER_DISABLED,
IS_WEB: false,
IS_EXTENSION: false,
VERSION: JSON.stringify(VERSION),
- BUILD_TIMESTAMP,
+ BUILD_TIMESTAMP
};
-if (defines.IS_STANDALONE === "false")
- // If this is a local build (not standalone), optimise
+
+if (defines.IS_STANDALONE === false)
+ // If this is a local build (not standalone), optimize
// for the specific platform we're on
defines["process.platform"] = JSON.stringify(process.platform);
@@ -46,7 +48,7 @@ const nodeCommonOpts = {
platform: "node",
target: ["esnext"],
external: ["electron", "original-fs", "~pluginNatives", ...commonOpts.external],
- define: defines,
+ define: defines
};
const sourceMapFooter = s => watch ? "" : `//# sourceMappingURL=vencord://${s}.js.map`;
@@ -73,13 +75,13 @@ const globNativesPlugin = {
let i = 0;
for (const dir of pluginDirs) {
const dirPath = join("src", dir);
- if (!await existsAsync(dirPath)) continue;
+ if (!await exists(dirPath)) continue;
const plugins = await readdir(dirPath);
for (const p of plugins) {
const nativePath = join(dirPath, p, "native.ts");
const indexNativePath = join(dirPath, p, "native/index.ts");
- if (!(await existsAsync(nativePath)) && !(await existsAsync(indexNativePath)))
+ if (!(await exists(nativePath)) && !(await exists(indexNativePath)))
continue;
const nameParts = p.split(".");
diff --git a/scripts/build/buildWeb.mjs b/scripts/build/buildWeb.mjs
index b4c726064c..04ff674fc8 100644
--- a/scripts/build/buildWeb.mjs
+++ b/scripts/build/buildWeb.mjs
@@ -23,7 +23,7 @@ import { appendFile, mkdir, readdir, readFile, rm, writeFile } from "fs/promises
import { join } from "path";
import Zip from "zip-local";
-import { BUILD_TIMESTAMP, commonOpts, globPlugins, isDev, VERSION } from "./common.mjs";
+import { BUILD_TIMESTAMP, commonOpts, globPlugins, IS_DEV, IS_REPORTER, VERSION } from "./common.mjs";
/**
* @type {esbuild.BuildOptions}
@@ -40,15 +40,16 @@ const commonOptions = {
],
target: ["esnext"],
define: {
- IS_WEB: "true",
- IS_EXTENSION: "false",
- IS_STANDALONE: "true",
- IS_DEV: JSON.stringify(isDev),
- IS_DISCORD_DESKTOP: "false",
- IS_VESKTOP: "false",
- IS_UPDATER_DISABLED: "true",
+ IS_WEB: true,
+ IS_EXTENSION: false,
+ IS_STANDALONE: true,
+ IS_DEV,
+ IS_REPORTER,
+ IS_DISCORD_DESKTOP: false,
+ IS_VESKTOP: false,
+ IS_UPDATER_DISABLED: true,
VERSION: JSON.stringify(VERSION),
- BUILD_TIMESTAMP,
+ BUILD_TIMESTAMP
}
};
@@ -87,16 +88,16 @@ await Promise.all(
esbuild.build({
...commonOptions,
outfile: "dist/browser.js",
- footer: { js: "//# sourceURL=VencordWeb" },
+ footer: { js: "//# sourceURL=VencordWeb" }
}),
esbuild.build({
...commonOptions,
outfile: "dist/extension.js",
define: {
...commonOptions?.define,
- IS_EXTENSION: "true",
+ IS_EXTENSION: true,
},
- footer: { js: "//# sourceURL=VencordWeb" },
+ footer: { js: "//# sourceURL=VencordWeb" }
}),
esbuild.build({
...commonOptions,
@@ -112,7 +113,7 @@ await Promise.all(
footer: {
// UserScripts get wrapped in an iife, so define Vencord prop on window that returns our local
js: "Object.defineProperty(unsafeWindow,'Vencord',{get:()=>Vencord});"
- },
+ }
})
]
);
@@ -165,7 +166,7 @@ async function buildExtension(target, files) {
f.startsWith("manifest") ? "manifest.json" : f,
content
];
- }))),
+ })))
};
await rm(target, { recursive: true, force: true });
@@ -192,14 +193,19 @@ const appendCssRuntime = readFile("dist/Vencord.user.css", "utf-8").then(content
return appendFile("dist/Vencord.user.js", cssRuntime);
});
-await Promise.all([
- appendCssRuntime,
- buildExtension("chromium-unpacked", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"]),
- buildExtension("firefox-unpacked", ["background.js", "content.js", "manifestv2.json", "icon.png"]),
-]);
+if (!process.argv.includes("--skip-extension")) {
+ await Promise.all([
+ appendCssRuntime,
+ buildExtension("chromium-unpacked", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"]),
+ buildExtension("firefox-unpacked", ["background.js", "content.js", "manifestv2.json", "icon.png"]),
+ ]);
-Zip.sync.zip("dist/chromium-unpacked").compress().save("dist/extension-chrome.zip");
-console.info("Packed Chromium Extension written to dist/extension-chrome.zip");
+ Zip.sync.zip("dist/chromium-unpacked").compress().save("dist/extension-chrome.zip");
+ console.info("Packed Chromium Extension written to dist/extension-chrome.zip");
-Zip.sync.zip("dist/firefox-unpacked").compress().save("dist/extension-firefox.zip");
-console.info("Packed Firefox Extension written to dist/extension-firefox.zip");
+ Zip.sync.zip("dist/firefox-unpacked").compress().save("dist/extension-firefox.zip");
+ console.info("Packed Firefox Extension written to dist/extension-firefox.zip");
+
+} else {
+ await appendCssRuntime;
+}
diff --git a/scripts/build/common.mjs b/scripts/build/common.mjs
index 3b1473e1c8..cdbb26eec8 100644
--- a/scripts/build/common.mjs
+++ b/scripts/build/common.mjs
@@ -35,24 +35,26 @@ const PackageJSON = JSON.parse(readFileSync("package.json"));
export const VERSION = PackageJSON.version;
// https://reproducible-builds.org/docs/source-date-epoch/
export const BUILD_TIMESTAMP = Number(process.env.SOURCE_DATE_EPOCH) || Date.now();
+
export const watch = process.argv.includes("--watch");
-export const isDev = watch || process.argv.includes("--dev");
-export const isStandalone = JSON.stringify(process.argv.includes("--standalone"));
-export const updaterDisabled = JSON.stringify(process.argv.includes("--disable-updater"));
+export const IS_DEV = watch || process.argv.includes("--dev");
+export const IS_REPORTER = process.argv.includes("--reporter");
+export const IS_STANDALONE = process.argv.includes("--standalone");
+
+export const IS_UPDATER_DISABLED = process.argv.includes("--disable-updater");
export const gitHash = process.env.VENCORD_HASH || execSync("git rev-parse --short HEAD", { encoding: "utf-8" }).trim();
+
export const banner = {
js: `
// Vencord ${gitHash}
-// Standalone: ${isStandalone}
-// Platform: ${isStandalone === "false" ? process.platform : "Universal"}
-// Updater disabled: ${updaterDisabled}
+// Standalone: ${IS_STANDALONE}
+// Platform: ${IS_STANDALONE === false ? process.platform : "Universal"}
+// Updater Disabled: ${IS_UPDATER_DISABLED}
`.trim()
};
-const isWeb = process.argv.slice(0, 2).some(f => f.endsWith("buildWeb.mjs"));
-
-export function existsAsync(path) {
- return access(path, FsConstants.F_OK)
+export async function exists(path) {
+ return await access(path, FsConstants.F_OK)
.then(() => true)
.catch(() => false);
}
@@ -66,7 +68,7 @@ export const makeAllPackagesExternalPlugin = {
setup(build) {
const filter = /^[^./]|^\.[^./]|^\.\.[^/]/; // Must not start with "/" or "./" or "../"
build.onResolve({ filter }, args => ({ path: args.path, external: true }));
- },
+ }
};
/**
@@ -89,14 +91,14 @@ export const globPlugins = kind => ({
let plugins = "\n";
let i = 0;
for (const dir of pluginDirs) {
- if (!await existsAsync(`./src/${dir}`)) continue;
+ if (!await exists(`./src/${dir}`)) continue;
const files = await readdir(`./src/${dir}`);
for (const file of files) {
if (file.startsWith("_") || file.startsWith(".")) continue;
if (file === "index.ts") continue;
const target = getPluginTarget(file);
- if (target) {
+ if (target && !IS_REPORTER) {
if (target === "dev" && !watch) continue;
if (target === "web" && kind === "discordDesktop") continue;
if (target === "desktop" && kind === "web") continue;
@@ -178,7 +180,7 @@ export const fileUrlPlugin = {
build.onLoad({ filter, namespace: "file-uri" }, async ({ pluginData: { path, uri } }) => {
const { searchParams } = new URL(uri);
const base64 = searchParams.has("base64");
- const minify = isStandalone === "true" && searchParams.has("minify");
+ const minify = IS_STANDALONE === true && searchParams.has("minify");
const noTrim = searchParams.get("trim") === "false";
const encoding = base64 ? "base64" : "utf-8";
diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts
index d3068492f8..8233f3e5d6 100644
--- a/scripts/generateReport.ts
+++ b/scripts/generateReport.ts
@@ -282,7 +282,7 @@ page.on("pageerror", e => console.error("[Page Error]", e));
await page.setBypassCSP(true);
-async function runtime(token: string) {
+async function reporterRuntime(token: string) {
console.log("[PUP_DEBUG]", "Starting test...");
try {
@@ -290,43 +290,7 @@ async function runtime(token: string) {
Object.defineProperty(navigator, "languages", {
get: function () {
return ["en-US", "en"];
- },
- });
-
- // Monkey patch Logger to not log with custom css
- // @ts-ignore
- const originalLog = Vencord.Util.Logger.prototype._log;
- // @ts-ignore
- Vencord.Util.Logger.prototype._log = function (level, levelColor, args) {
- if (level === "warn" || level === "error")
- return console[level]("[Vencord]", this.name + ":", ...args);
-
- return originalLog.call(this, level, levelColor, args);
- };
-
- // Force enable all plugins and patches
- Vencord.Plugins.patches.length = 0;
- Object.values(Vencord.Plugins.plugins).forEach(p => {
- // Needs native server to run
- if (p.name === "WebRichPresence (arRPC)") return;
-
- Vencord.Settings.plugins[p.name].enabled = true;
- p.patches?.forEach(patch => {
- patch.plugin = p.name;
- delete patch.predicate;
- delete patch.group;
-
- Vencord.Util.canonicalizeFind(patch);
- if (!Array.isArray(patch.replacement)) {
- patch.replacement = [patch.replacement];
- }
-
- patch.replacement.forEach(r => {
- delete r.predicate;
- });
-
- Vencord.Plugins.patches.push(patch);
- });
+ }
});
let wreq: typeof Vencord.Webpack.wreq;
@@ -549,9 +513,10 @@ async function runtime(token: string) {
}
await page.evaluateOnNewDocument(`
- ${readFileSync("./dist/browser.js", "utf-8")}
-
- ;(${runtime.toString()})(${JSON.stringify(process.env.DISCORD_TOKEN)});
+ if (location.host.endsWith("discord.com")) {
+ ${readFileSync("./dist/browser.js", "utf-8")};
+ (${reporterRuntime.toString()})(${JSON.stringify(process.env.DISCORD_TOKEN)});
+ }
`);
await page.goto(CANARY ? "https://canary.discord.com/login" : "https://discord.com/login");
diff --git a/src/debug/Tracer.ts b/src/debug/Tracer.ts
index 4337e00193..7d80f425ce 100644
--- a/src/debug/Tracer.ts
+++ b/src/debug/Tracer.ts
@@ -18,14 +18,14 @@
import { Logger } from "@utils/Logger";
-if (IS_DEV) {
+if (IS_DEV || IS_REPORTER) {
var traces = {} as Record;
var logger = new Logger("Tracer", "#FFD166");
}
const noop = function () { };
-export const beginTrace = !IS_DEV ? noop :
+export const beginTrace = !(IS_DEV || IS_REPORTER) ? noop :
function beginTrace(name: string, ...args: any[]) {
if (name in traces)
throw new Error(`Trace ${name} already exists!`);
@@ -33,7 +33,7 @@ export const beginTrace = !IS_DEV ? noop :
traces[name] = [performance.now(), args];
};
-export const finishTrace = !IS_DEV ? noop : function finishTrace(name: string) {
+export const finishTrace = !(IS_DEV || IS_REPORTER) ? noop : function finishTrace(name: string) {
const end = performance.now();
const [start, args] = traces[name];
@@ -48,7 +48,7 @@ type TraceNameMapper = (...args: Parameters) => string;
const noopTracer =
(name: string, f: F, mapper?: TraceNameMapper) => f;
-export const traceFunction = !IS_DEV
+export const traceFunction = !(IS_DEV || IS_REPORTER)
? noopTracer
: function traceFunction(name: string, f: F, mapper?: TraceNameMapper): F {
return function (this: any, ...args: Parameters) {
diff --git a/src/globals.d.ts b/src/globals.d.ts
index 94b5f15e85..e20ca4b71a 100644
--- a/src/globals.d.ts
+++ b/src/globals.d.ts
@@ -34,9 +34,10 @@ declare global {
*/
export var IS_WEB: boolean;
export var IS_EXTENSION: boolean;
- export var IS_DEV: boolean;
export var IS_STANDALONE: boolean;
export var IS_UPDATER_DISABLED: boolean;
+ export var IS_DEV: boolean;
+ export var IS_REPORTER: boolean;
export var IS_DISCORD_DESKTOP: boolean;
export var IS_VESKTOP: boolean;
export var VERSION: string;
diff --git a/src/plugins/arRPC.web/index.tsx b/src/plugins/arRPC.web/index.tsx
index 423dce9b52..e41e8675ed 100644
--- a/src/plugins/arRPC.web/index.tsx
+++ b/src/plugins/arRPC.web/index.tsx
@@ -19,7 +19,7 @@
import { popNotice, showNotice } from "@api/Notices";
import { Link } from "@components/Link";
import { Devs } from "@utils/constants";
-import definePlugin from "@utils/types";
+import definePlugin, { ReporterTestable } from "@utils/types";
import { findByPropsLazy } from "@webpack";
import { ApplicationAssetUtils, FluxDispatcher, Forms, Toasts } from "@webpack/common";
@@ -41,6 +41,7 @@ export default definePlugin({
name: "WebRichPresence (arRPC)",
description: "Client plugin for arRPC to enable RPC on Discord Web (experimental)",
authors: [Devs.Ducko],
+ reporterTestable: ReporterTestable.None,
settingsAboutComponent: () => (
<>
diff --git a/src/plugins/devCompanion.dev/index.tsx b/src/plugins/devCompanion.dev/index.tsx
index 25fd563e46..a495907b2d 100644
--- a/src/plugins/devCompanion.dev/index.tsx
+++ b/src/plugins/devCompanion.dev/index.tsx
@@ -21,7 +21,7 @@ import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches";
-import definePlugin, { OptionType } from "@utils/types";
+import definePlugin, { OptionType, ReporterTestable } from "@utils/types";
import { filters, findAll, search } from "@webpack";
const PORT = 8485;
@@ -243,6 +243,7 @@ export default definePlugin({
name: "DevCompanion",
description: "Dev Companion Plugin",
authors: [Devs.Ven],
+ reporterTestable: ReporterTestable.None,
settings,
toolboxActions: {
diff --git a/src/plugins/index.ts b/src/plugins/index.ts
index a434b4a6fe..53ab7983a5 100644
--- a/src/plugins/index.ts
+++ b/src/plugins/index.ts
@@ -21,7 +21,7 @@ import { addContextMenuPatch, removeContextMenuPatch } from "@api/ContextMenu";
import { Settings } from "@api/Settings";
import { Logger } from "@utils/Logger";
import { canonicalizeFind } from "@utils/patches";
-import { Patch, Plugin, StartAt } from "@utils/types";
+import { Patch, Plugin, ReporterTestable, StartAt } from "@utils/types";
import { FluxDispatcher } from "@webpack/common";
import { FluxEvents } from "@webpack/types";
@@ -39,35 +39,68 @@ export const patches = [] as Patch[];
let enabledPluginsSubscribedFlux = false;
const subscribedFluxEventsPlugins = new Set();
+const pluginsValues = Object.values(Plugins);
const settings = Settings.plugins;
export function isPluginEnabled(p: string) {
return (
+ IS_REPORTER ||
Plugins[p]?.required ||
Plugins[p]?.isDependency ||
settings[p]?.enabled
) ?? false;
}
-const pluginsValues = Object.values(Plugins);
+export function addPatch(newPatch: Omit, pluginName: string) {
+ const patch = newPatch as Patch;
+ patch.plugin = pluginName;
+
+ if (IS_REPORTER) {
+ delete patch.predicate;
+ delete patch.group;
+ }
+
+ canonicalizeFind(patch);
+ if (!Array.isArray(patch.replacement)) {
+ patch.replacement = [patch.replacement];
+ }
+
+ if (IS_REPORTER) {
+ patch.replacement.forEach(r => {
+ delete r.predicate;
+ });
+ }
-// First roundtrip to mark and force enable dependencies (only for enabled plugins)
+ patches.push(patch);
+}
+
+function isReporterTestable(p: Plugin, part: ReporterTestable) {
+ return p.reporterTestable == null
+ ? true
+ : (p.reporterTestable & part) === part;
+}
+
+// First round-trip to mark and force enable dependencies
//
// FIXME: might need to revisit this if there's ever nested (dependencies of dependencies) dependencies since this only
// goes for the top level and their children, but for now this works okay with the current API plugins
-for (const p of pluginsValues) if (settings[p.name]?.enabled) {
+for (const p of pluginsValues) if (isPluginEnabled(p.name)) {
p.dependencies?.forEach(d => {
const dep = Plugins[d];
- if (dep) {
- settings[d].enabled = true;
- dep.isDependency = true;
- }
- else {
+
+ if (!dep) {
const error = new Error(`Plugin ${p.name} has unresolved dependency ${d}`);
- if (IS_DEV)
+
+ if (IS_DEV) {
throw error;
+ }
+
logger.warn(error);
+ return;
}
+
+ settings[d].enabled = true;
+ dep.isDependency = true;
});
}
@@ -82,23 +115,18 @@ for (const p of pluginsValues) {
}
if (p.patches && isPluginEnabled(p.name)) {
- for (const patch of p.patches) {
- patch.plugin = p.name;
-
- canonicalizeFind(patch);
- if (!Array.isArray(patch.replacement)) {
- patch.replacement = [patch.replacement];
+ if (!IS_REPORTER || isReporterTestable(p, ReporterTestable.Patches)) {
+ for (const patch of p.patches) {
+ addPatch(patch, p.name);
}
-
- patches.push(patch);
}
}
}
export const startAllPlugins = traceFunction("startAllPlugins", function startAllPlugins(target: StartAt) {
logger.info(`Starting plugins (stage ${target})`);
- for (const name in Plugins)
- if (isPluginEnabled(name)) {
+ for (const name in Plugins) {
+ if (isPluginEnabled(name) && (!IS_REPORTER || isReporterTestable(Plugins[name], ReporterTestable.Start))) {
const p = Plugins[name];
const startAt = p.startAt ?? StartAt.WebpackReady;
@@ -106,30 +134,38 @@ export const startAllPlugins = traceFunction("startAllPlugins", function startAl
startPlugin(Plugins[name]);
}
+ }
});
export function startDependenciesRecursive(p: Plugin) {
let restartNeeded = false;
const failures: string[] = [];
- p.dependencies?.forEach(dep => {
- if (!Settings.plugins[dep].enabled) {
- startDependenciesRecursive(Plugins[dep]);
+
+ p.dependencies?.forEach(d => {
+ if (!settings[d].enabled) {
+ const dep = Plugins[d];
+ startDependenciesRecursive(dep);
+
// If the plugin has patches, don't start the plugin, just enable it.
- Settings.plugins[dep].enabled = true;
- if (Plugins[dep].patches) {
- logger.warn(`Enabling dependency ${dep} requires restart.`);
+ settings[d].enabled = true;
+ dep.isDependency = true;
+
+ if (dep.patches) {
+ logger.warn(`Enabling dependency ${d} requires restart.`);
restartNeeded = true;
return;
}
- const result = startPlugin(Plugins[dep]);
- if (!result) failures.push(dep);
+
+ const result = startPlugin(dep);
+ if (!result) failures.push(d);
}
});
+
return { restartNeeded, failures };
}
export function subscribePluginFluxEvents(p: Plugin, fluxDispatcher: typeof FluxDispatcher) {
- if (p.flux && !subscribedFluxEventsPlugins.has(p.name)) {
+ if (p.flux && !subscribedFluxEventsPlugins.has(p.name) && (!IS_REPORTER || isReporterTestable(p, ReporterTestable.FluxEvents))) {
subscribedFluxEventsPlugins.add(p.name);
logger.debug("Subscribing to flux events of plugin", p.name);
diff --git a/src/plugins/invisibleChat.desktop/index.tsx b/src/plugins/invisibleChat.desktop/index.tsx
index 884ffafe37..01199d9996 100644
--- a/src/plugins/invisibleChat.desktop/index.tsx
+++ b/src/plugins/invisibleChat.desktop/index.tsx
@@ -23,7 +23,7 @@ import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { getStegCloak } from "@utils/dependencies";
-import definePlugin, { OptionType } from "@utils/types";
+import definePlugin, { OptionType, ReporterTestable } from "@utils/types";
import { ChannelStore, Constants, RestAPI, Tooltip } from "@webpack/common";
import { Message } from "discord-types/general";
@@ -105,6 +105,9 @@ export default definePlugin({
description: "Encrypt your Messages in a non-suspicious way!",
authors: [Devs.SammCheese],
dependencies: ["MessagePopoverAPI", "ChatInputButtonAPI", "MessageUpdaterAPI"],
+ reporterTestable: ReporterTestable.Patches,
+ settings,
+
patches: [
{
// Indicator
@@ -121,7 +124,6 @@ export default definePlugin({
URL_REGEX: new RegExp(
/(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)/,
),
- settings,
async start() {
addButton("InvisibleChat", message => {
return this.INV_REGEX.test(message?.content)
diff --git a/src/plugins/shikiCodeblocks.desktop/index.ts b/src/plugins/shikiCodeblocks.desktop/index.ts
index 27463195d2..e6676a866c 100644
--- a/src/plugins/shikiCodeblocks.desktop/index.ts
+++ b/src/plugins/shikiCodeblocks.desktop/index.ts
@@ -20,7 +20,7 @@ import "./shiki.css";
import { enableStyle } from "@api/Styles";
import { Devs } from "@utils/constants";
-import definePlugin from "@utils/types";
+import definePlugin, { ReporterTestable } from "@utils/types";
import previewExampleText from "file://previewExample.tsx";
import { shiki } from "./api/shiki";
@@ -34,6 +34,9 @@ export default definePlugin({
name: "ShikiCodeblocks",
description: "Brings vscode-style codeblocks into Discord, powered by Shiki",
authors: [Devs.Vap],
+ reporterTestable: ReporterTestable.Patches,
+ settings,
+
patches: [
{
find: "codeBlock:{react(",
@@ -66,7 +69,6 @@ export default definePlugin({
isPreview: true,
tempSettings,
}),
- settings,
// exports
shiki,
diff --git a/src/plugins/vcNarrator/index.tsx b/src/plugins/vcNarrator/index.tsx
index ac629e749a..6e8e4bbf5e 100644
--- a/src/plugins/vcNarrator/index.tsx
+++ b/src/plugins/vcNarrator/index.tsx
@@ -22,7 +22,7 @@ import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins";
import { wordsToTitle } from "@utils/text";
-import definePlugin, { OptionType, PluginOptionsItem } from "@utils/types";
+import definePlugin, { OptionType, PluginOptionsItem, ReporterTestable } from "@utils/types";
import { findByPropsLazy } from "@webpack";
import { Button, ChannelStore, Forms, GuildMemberStore, SelectedChannelStore, SelectedGuildStore, useMemo, UserStore } from "@webpack/common";
@@ -155,6 +155,7 @@ export default definePlugin({
name: "VcNarrator",
description: "Announces when users join, leave, or move voice channels via narrator",
authors: [Devs.Ven],
+ reporterTestable: ReporterTestable.None,
flux: {
VOICE_STATE_UPDATES({ voiceStates }: { voiceStates: VoiceState[]; }) {
diff --git a/src/plugins/xsOverlay.desktop/index.ts b/src/plugins/xsOverlay.desktop/index.ts
index 5251959f2f..caa44a40c4 100644
--- a/src/plugins/xsOverlay.desktop/index.ts
+++ b/src/plugins/xsOverlay.desktop/index.ts
@@ -8,7 +8,7 @@ import { definePluginSettings } from "@api/Settings";
import { makeRange } from "@components/PluginSettings/components";
import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
-import definePlugin, { OptionType, PluginNative } from "@utils/types";
+import definePlugin, { OptionType, PluginNative, ReporterTestable } from "@utils/types";
import { findByPropsLazy } from "@webpack";
import { ChannelStore, GuildStore, UserStore } from "@webpack/common";
import type { Channel, Embed, GuildMember, MessageAttachment, User } from "discord-types/general";
@@ -143,7 +143,9 @@ export default definePlugin({
description: "Forwards discord notifications to XSOverlay, for easy viewing in VR",
authors: [Devs.Nyako],
tags: ["vr", "notify"],
+ reporterTestable: ReporterTestable.None,
settings,
+
flux: {
CALL_UPDATE({ call }: { call: Call; }) {
if (call?.ringing?.includes(UserStore.getCurrentUser().id) && settings.store.callNotifications) {
diff --git a/src/utils/Logger.ts b/src/utils/Logger.ts
index 1ae4762d7b..e222d71fbe 100644
--- a/src/utils/Logger.ts
+++ b/src/utils/Logger.ts
@@ -32,6 +32,11 @@ export class Logger {
constructor(public name: string, public color: string = "white") { }
private _log(level: "log" | "error" | "warn" | "info" | "debug", levelColor: string, args: any[], customFmt = "") {
+ if (IS_REPORTER && (level === "warn" || level === "error")) {
+ console[level]("[Vencord]", this.name + ":", ...args);
+ return;
+ }
+
console[level](
`%c Vencord %c %c ${this.name} ${customFmt}`,
`background: ${levelColor}; color: black; font-weight: bold; border-radius: 5px;`,
diff --git a/src/utils/types.ts b/src/utils/types.ts
index 6e15241967..fe19a10936 100644
--- a/src/utils/types.ts
+++ b/src/utils/types.ts
@@ -94,6 +94,10 @@ export interface PluginDef {
* @default StartAt.WebpackReady
*/
startAt?: StartAt,
+ /**
+ * Which parts of the plugin can be tested by the reporter. Defaults to all parts
+ */
+ reporterTestable?: number;
/**
* Optionally provide settings that the user can configure in the Plugins tab of settings.
* @deprecated Use `settings` instead
@@ -144,6 +148,13 @@ export const enum StartAt {
WebpackReady = "WebpackReady"
}
+export const enum ReporterTestable {
+ None = 1 << 1,
+ Start = 1 << 2,
+ Patches = 1 << 3,
+ FluxEvents = 1 << 4
+}
+
export const enum OptionType {
STRING,
NUMBER,
diff --git a/src/webpack/common/internal.tsx b/src/webpack/common/internal.tsx
index 9a89af362e..8957c254b9 100644
--- a/src/webpack/common/internal.tsx
+++ b/src/webpack/common/internal.tsx
@@ -22,7 +22,7 @@ import { LazyComponent } from "@utils/react";
import { FilterFn, filters, lazyWebpackSearchHistory, waitFor } from "../webpack";
export function waitForComponent = React.ComponentType & Record>(name: string, filter: FilterFn | string | string[]): T {
- if (IS_DEV) lazyWebpackSearchHistory.push(["waitForComponent", Array.isArray(filter) ? filter : [filter]]);
+ if (IS_REPORTER) lazyWebpackSearchHistory.push(["waitForComponent", Array.isArray(filter) ? filter : [filter]]);
let myValue: T = function () {
throw new Error(`Vencord could not find the ${name} Component`);
@@ -38,7 +38,7 @@ export function waitForComponent = React.Comp
}
export function waitForStore(name: string, cb: (v: any) => void) {
- if (IS_DEV) lazyWebpackSearchHistory.push(["waitForStore", [name]]);
+ if (IS_REPORTER) lazyWebpackSearchHistory.push(["waitForStore", [name]]);
waitFor(filters.byStoreName(name), cb, { isIndirect: true });
}
diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts
index 9e0c0d000e..ec7218c649 100644
--- a/src/webpack/webpack.ts
+++ b/src/webpack/webpack.ts
@@ -264,7 +264,7 @@ export const lazyWebpackSearchHistory = [] as Array<["find" | "findByProps" | "f
* @example const mod = proxyLazy(() => findByProps("blah")); console.log(mod.blah);
*/
export function proxyLazyWebpack(factory: () => any, attempts?: number) {
- if (IS_DEV) lazyWebpackSearchHistory.push(["proxyLazyWebpack", [factory]]);
+ if (IS_REPORTER) lazyWebpackSearchHistory.push(["proxyLazyWebpack", [factory]]);
return proxyLazy(factory, attempts);
}
@@ -278,7 +278,7 @@ export function proxyLazyWebpack(factory: () => any, attempts?: number)
* @returns Result of factory function
*/
export function LazyComponentWebpack(factory: () => any, attempts?: number) {
- if (IS_DEV) lazyWebpackSearchHistory.push(["LazyComponentWebpack", [factory]]);
+ if (IS_REPORTER) lazyWebpackSearchHistory.push(["LazyComponentWebpack", [factory]]);
return LazyComponent(factory, attempts);
}
@@ -287,7 +287,7 @@ export function LazyComponentWebpack(factory: () => any,
* Find the first module that matches the filter, lazily
*/
export function findLazy(filter: FilterFn) {
- if (IS_DEV) lazyWebpackSearchHistory.push(["find", [filter]]);
+ if (IS_REPORTER) lazyWebpackSearchHistory.push(["find", [filter]]);
return proxyLazy(() => find(filter));
}
@@ -306,7 +306,7 @@ export function findByProps(...props: string[]) {
* Find the first module that has the specified properties, lazily
*/
export function findByPropsLazy(...props: string[]) {
- if (IS_DEV) lazyWebpackSearchHistory.push(["findByProps", props]);
+ if (IS_REPORTER) lazyWebpackSearchHistory.push(["findByProps", props]);
return proxyLazy(() => findByProps(...props));
}
@@ -325,7 +325,7 @@ export function findByCode(...code: string[]) {
* Find the first function that includes all the given code, lazily
*/
export function findByCodeLazy(...code: string[]) {
- if (IS_DEV) lazyWebpackSearchHistory.push(["findByCode", code]);
+ if (IS_REPORTER) lazyWebpackSearchHistory.push(["findByCode", code]);
return proxyLazy(() => findByCode(...code));
}
@@ -344,7 +344,7 @@ export function findStore(name: string) {
* Find a store by its displayName, lazily
*/
export function findStoreLazy(name: string) {
- if (IS_DEV) lazyWebpackSearchHistory.push(["findStore", [name]]);
+ if (IS_REPORTER) lazyWebpackSearchHistory.push(["findStore", [name]]);
return proxyLazy(() => findStore(name));
}
@@ -363,7 +363,7 @@ export function findComponentByCode(...code: string[]) {
* Finds the first component that matches the filter, lazily.
*/
export function findComponentLazy(filter: FilterFn) {
- if (IS_DEV) lazyWebpackSearchHistory.push(["findComponent", [filter]]);
+ if (IS_REPORTER) lazyWebpackSearchHistory.push(["findComponent", [filter]]);
return LazyComponent(() => {
@@ -378,7 +378,7 @@ export function findComponentLazy(filter: FilterFn) {
* Finds the first component that includes all the given code, lazily
*/
export function findComponentByCodeLazy(...code: string[]) {
- if (IS_DEV) lazyWebpackSearchHistory.push(["findComponentByCode", code]);
+ if (IS_REPORTER) lazyWebpackSearchHistory.push(["findComponentByCode", code]);
return LazyComponent(() => {
const res = find(filters.componentByCode(...code), { isIndirect: true });
@@ -392,7 +392,7 @@ export function findComponentByCodeLazy(...code: string[
* Finds the first component that is exported by the first prop name, lazily
*/
export function findExportedComponentLazy(...props: string[]) {
- if (IS_DEV) lazyWebpackSearchHistory.push(["findExportedComponent", props]);
+ if (IS_REPORTER) lazyWebpackSearchHistory.push(["findExportedComponent", props]);
return LazyComponent(() => {
const res = find(filters.byProps(...props), { isIndirect: true });
@@ -477,7 +477,7 @@ export async function extractAndLoadChunks(code: string[], matcher: RegExp = Def
* @returns A function that returns a promise that resolves with a boolean whether the chunks were loaded, on first call
*/
export function extractAndLoadChunksLazy(code: string[], matcher = DefaultExtractAndLoadChunksRegex) {
- if (IS_DEV) lazyWebpackSearchHistory.push(["extractAndLoadChunks", [code, matcher]]);
+ if (IS_REPORTER) lazyWebpackSearchHistory.push(["extractAndLoadChunks", [code, matcher]]);
return makeLazy(() => extractAndLoadChunks(code, matcher));
}
@@ -487,7 +487,7 @@ export function extractAndLoadChunksLazy(code: string[], matcher = DefaultExtrac
* then call the callback with the module as the first argument
*/
export function waitFor(filter: string | string[] | FilterFn, callback: CallbackFn, { isIndirect = false }: { isIndirect?: boolean; } = {}) {
- if (IS_DEV && !isIndirect) lazyWebpackSearchHistory.push(["waitFor", Array.isArray(filter) ? filter : [filter]]);
+ if (IS_REPORTER && !isIndirect) lazyWebpackSearchHistory.push(["waitFor", Array.isArray(filter) ? filter : [filter]]);
if (typeof filter === "string")
filter = filters.byProps(filter);
From 3c62e393a3cc09d51cf3c1be854dae79d2d5adad Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Thu, 30 May 2024 13:38:22 -0300
Subject: [PATCH 080/125] edit another license header
---
src/webpack/wreq.d.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/webpack/wreq.d.ts b/src/webpack/wreq.d.ts
index 246682f643..6611680bdd 100644
--- a/src/webpack/wreq.d.ts
+++ b/src/webpack/wreq.d.ts
@@ -1,6 +1,6 @@
/*
* Vencord, a Discord client mod
- * Copyright (c) 2024 Vendicated and contributors
+ * Copyright (c) 2024 Vendicated, Nuckyz and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
From 3a25da5f1443b3caf6ea0b410011b5319226f58c Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Thu, 30 May 2024 18:03:00 -0300
Subject: [PATCH 081/125] Fix wrong external files and clean up build script;
Remove non used stuff
---
.github/workflows/build.yml | 2 +-
.github/workflows/publish.yml | 2 +-
package.json | 3 ++-
scripts/build/buildWeb.mjs | 2 +-
src/utils/dependencies.ts | 10 ----------
5 files changed, 5 insertions(+), 14 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index c7a2f24e89..ba22b12301 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -32,7 +32,7 @@ jobs:
run: pnpm install --frozen-lockfile
- name: Build web
- run: pnpm buildWeb --standalone
+ run: pnpm buildWebStandalone
- name: Build
run: pnpm build --standalone
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 8407e08e2d..190e3069c4 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -32,7 +32,7 @@ jobs:
run: pnpm install --frozen-lockfile
- name: Build web
- run: pnpm buildWeb --standalone
+ run: pnpm buildWebStandalone
- name: Publish extension
run: |
diff --git a/package.json b/package.json
index 881e42a287..43ac363043 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,8 @@
"build": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/build.mjs",
"buildStandalone": "pnpm build --standalone",
"buildWeb": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/buildWeb.mjs",
- "buildReporter": "pnpm buildWeb --standalone --reporter --skip-extension",
+ "buildWebStandalone": "pnpm buildWeb --standalone",
+ "buildReporter": "pnpm buildWebStandalone --reporter --skip-extension",
"watch": "pnpm build --watch",
"watchWeb": "pnpm buildWeb --watch",
"generatePluginJson": "tsx scripts/generatePluginList.ts",
diff --git a/scripts/build/buildWeb.mjs b/scripts/build/buildWeb.mjs
index 04ff674fc8..bc15cccedc 100644
--- a/scripts/build/buildWeb.mjs
+++ b/scripts/build/buildWeb.mjs
@@ -33,7 +33,7 @@ const commonOptions = {
entryPoints: ["browser/Vencord.ts"],
globalName: "Vencord",
format: "iife",
- external: ["plugins", "git-hash", "/assets/*"],
+ external: ["~plugins", "~git-hash", "/assets/*"],
plugins: [
globPlugins("web"),
...commonOpts.plugins,
diff --git a/src/utils/dependencies.ts b/src/utils/dependencies.ts
index f05900e146..d8c361e884 100644
--- a/src/utils/dependencies.ts
+++ b/src/utils/dependencies.ts
@@ -17,7 +17,6 @@
*/
import { makeLazy } from "./lazy";
-import { EXTENSION_BASE_URL } from "./web-metadata";
/*
Add dynamically loaded dependencies for plugins here.
@@ -67,15 +66,6 @@ export interface ApngFrameData {
playTime: number;
}
-// On web (extensions), use extension uri as basepath (load files from extension)
-// On desktop (electron), load from cdn
-export const rnnoiseDist = IS_EXTENSION
- ? new URL("/third-party/rnnoise", EXTENSION_BASE_URL).toString()
- : "https://unpkg.com/@sapphi-red/web-noise-suppressor@0.3.3/dist";
-export const rnnoiseWasmSrc = (simd = false) => `${rnnoiseDist}/rnnoise${simd ? "_simd" : ""}.wasm`;
-export const rnnoiseWorkletSrc = `${rnnoiseDist}/rnnoise/workletProcessor.js`;
-
-
// The below code is only used on the Desktop (electron) build of Vencord.
// Browser (extension) builds do not contain these remote imports.
From 47eec049fc98548846554115016ca7a220f9af6b Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Fri, 31 May 2024 00:21:15 -0300
Subject: [PATCH 082/125] Not needed anymore and breaks interpolated strings
with newlines
---
src/webpack/patchWebpack.ts | 12 ++----------
1 file changed, 2 insertions(+), 10 deletions(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index ffc4100e0c..d728e5a2f4 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -211,16 +211,8 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
const patchedBy = new Set();
- // Discords Webpack chunks for some ungodly reason contain random
- // newlines. Cyn recommended this workaround and it seems to work fine,
- // however this could potentially break code, so if anything goes weird,
- // this is probably why.
- // Additionally, `[actual newline]` is one less char than "\n", so if Discord
- // ever targets newer browsers, the minifier could potentially use this trick and
- // cause issues.
- //
- // 0, prefix is to turn it into an expression: 0,function(){} would be invalid syntax without the 0,
- let code: string = "0," + String(factory).replaceAll("\n", "");
+ // 0, prefix to turn it into an expression: 0,function(){} would be invalid syntax without the 0,
+ let code: string = "0," + String(factory);
for (let i = 0; i < patches.length; i++) {
const patch = patches[i];
From 545d69aa6df8ea6c2d0712f3c9d02af21e310b90 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Fri, 31 May 2024 05:19:34 -0300
Subject: [PATCH 083/125] Use indexOf instead of includes; reset global find
regex state
---
src/webpack/patchWebpack.ts | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index d728e5a2f4..b0f735d2e3 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -218,9 +218,11 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
const patch = patches[i];
if (patch.predicate && !patch.predicate()) continue;
+ // indexOf is faster than includes because it doesn't check if searchString is a RegExp
+ // https://github.com/moonlight-mod/moonlight/blob/53ae39d4010277f49f3b70bebbd27b9cbcdb1c8b/packages/core/src/patch.ts#L61
const moduleMatches = typeof patch.find === "string"
- ? code.includes(patch.find)
- : patch.find.test(code);
+ ? code.indexOf(patch.find) !== -1
+ : (patch.find.global && (patch.find.lastIndex = 0), patch.find.test(code));
if (!moduleMatches) continue;
From f4cc95da5a0edf7491e58aca6c0ac7c78bbd7acb Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Fri, 31 May 2024 05:29:01 -0300
Subject: [PATCH 084/125] Use indexOf in more places
---
src/webpack/webpack.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts
index f318558702..af57a324eb 100644
--- a/src/webpack/webpack.ts
+++ b/src/webpack/webpack.ts
@@ -48,7 +48,7 @@ export const filters = {
if (typeof m !== "function") return false;
const s = Function.prototype.toString.call(m);
for (const c of code) {
- if (!s.includes(c)) return false;
+ if (s.indexOf(c) === -1) return false;
}
return true;
},
@@ -229,7 +229,7 @@ export const findModuleId = traceFunction("findModuleId", function findModuleId(
const str = String(wreq.m[id]);
for (const c of code) {
- if (!str.includes(c)) continue outer;
+ if (str.indexOf(c) === -1) continue outer;
}
return id;
}
@@ -526,7 +526,7 @@ export function search(...filters: Array) {
const factory = factories[id];
const factoryStr = String(factory);
for (const filter of filters) {
- if (typeof filter === "string" && !factoryStr.includes(filter)) continue outer;
+ if (typeof filter === "string" && factoryStr.indexOf(filter) === -1) continue outer;
if (filter instanceof RegExp && !filter.test(factoryStr)) continue outer;
}
results[id] = factory;
From db63fffe18c1a4c526d32cd0033842645c8b1dfe Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Fri, 31 May 2024 06:16:01 -0300
Subject: [PATCH 085/125] I should still be defining this
---
src/webpack/patchWebpack.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index b0f735d2e3..4a609666fc 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -70,13 +70,13 @@ define(Function.prototype, "m", {
enumerable: false,
set(this: WebpackRequire, bundlePath: WebpackRequire["p"]) {
+ define(this, "p", { value: bundlePath });
+
if (bundlePath !== "/assets/") return;
logger.info("Main Webpack found" + interpolateIfDefined` in ${fileName}` + ", initializing internal references to WebpackRequire");
_initWebpack(this);
clearTimeout(setterTimeout);
-
- define(this, "p", { value: bundlePath });
}
});
// setImmediate to clear this property setter if this is not the main Webpack.
From e21a6be4b640a20e6d72e88419eb3e63f05f4b6b Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Fri, 31 May 2024 06:19:57 -0300
Subject: [PATCH 086/125] sfdsfdsfdsf
---
src/webpack/patchWebpack.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index 4a609666fc..8c974f28eb 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -71,12 +71,12 @@ define(Function.prototype, "m", {
set(this: WebpackRequire, bundlePath: WebpackRequire["p"]) {
define(this, "p", { value: bundlePath });
+ clearTimeout(setterTimeout);
if (bundlePath !== "/assets/") return;
logger.info("Main Webpack found" + interpolateIfDefined` in ${fileName}` + ", initializing internal references to WebpackRequire");
_initWebpack(this);
- clearTimeout(setterTimeout);
}
});
// setImmediate to clear this property setter if this is not the main Webpack.
From 4187932aa71b0d7113adb50120afe46d99fe26a9 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Fri, 31 May 2024 06:27:02 -0300
Subject: [PATCH 087/125] \n
---
src/webpack/patchWebpack.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index 8c974f28eb..a2264c5419 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -190,6 +190,7 @@ const moduleFactoriesHandler: ProxyHandler = {
return true;
}
};
+
/**
* Patches a module factory.
*
From fbb7ee50dd636900467cbae44e3bdd88273f62eb Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Fri, 31 May 2024 17:34:32 -0300
Subject: [PATCH 088/125] Revert indexOf change
---
src/webpack/patchWebpack.ts | 4 +---
src/webpack/webpack.ts | 6 +++---
2 files changed, 4 insertions(+), 6 deletions(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index a2264c5419..abfabb4384 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -219,10 +219,8 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
const patch = patches[i];
if (patch.predicate && !patch.predicate()) continue;
- // indexOf is faster than includes because it doesn't check if searchString is a RegExp
- // https://github.com/moonlight-mod/moonlight/blob/53ae39d4010277f49f3b70bebbd27b9cbcdb1c8b/packages/core/src/patch.ts#L61
const moduleMatches = typeof patch.find === "string"
- ? code.indexOf(patch.find) !== -1
+ ? code.includes(patch.find)
: (patch.find.global && (patch.find.lastIndex = 0), patch.find.test(code));
if (!moduleMatches) continue;
diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts
index af57a324eb..f318558702 100644
--- a/src/webpack/webpack.ts
+++ b/src/webpack/webpack.ts
@@ -48,7 +48,7 @@ export const filters = {
if (typeof m !== "function") return false;
const s = Function.prototype.toString.call(m);
for (const c of code) {
- if (s.indexOf(c) === -1) return false;
+ if (!s.includes(c)) return false;
}
return true;
},
@@ -229,7 +229,7 @@ export const findModuleId = traceFunction("findModuleId", function findModuleId(
const str = String(wreq.m[id]);
for (const c of code) {
- if (str.indexOf(c) === -1) continue outer;
+ if (!str.includes(c)) continue outer;
}
return id;
}
@@ -526,7 +526,7 @@ export function search(...filters: Array) {
const factory = factories[id];
const factoryStr = String(factory);
for (const filter of filters) {
- if (typeof filter === "string" && factoryStr.indexOf(filter) === -1) continue outer;
+ if (typeof filter === "string" && !factoryStr.includes(filter)) continue outer;
if (filter instanceof RegExp && !filter.test(factoryStr)) continue outer;
}
results[id] = factory;
From cbd9a9312cc1bc4acb362f526051caf1d08b9d84 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Fri, 31 May 2024 19:58:26 -0300
Subject: [PATCH 089/125] don't depend in the value of bundlePath
---
src/webpack/patchWebpack.ts | 2 --
1 file changed, 2 deletions(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index abfabb4384..3734331661 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -73,8 +73,6 @@ define(Function.prototype, "m", {
define(this, "p", { value: bundlePath });
clearTimeout(setterTimeout);
- if (bundlePath !== "/assets/") return;
-
logger.info("Main Webpack found" + interpolateIfDefined` in ${fileName}` + ", initializing internal references to WebpackRequire");
_initWebpack(this);
}
From 01fdeb7292d583bce2ccdf7e8c00d3fa8b9902d9 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Thu, 30 May 2024 17:32:54 -0300
Subject: [PATCH 090/125] cya webpackChunkdiscord_app part 2
---
src/globals.d.ts | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/src/globals.d.ts b/src/globals.d.ts
index e20ca4b71a..4456564ccb 100644
--- a/src/globals.d.ts
+++ b/src/globals.d.ts
@@ -64,13 +64,8 @@ declare global {
export var Vesktop: any;
export var VesktopNative: any;
- interface Window {
- webpackChunkdiscord_app: {
- push(chunk: any): any;
- pop(): any;
- };
+ interface Window extends Record {
_: LoDashStatic;
- [k: string]: any;
}
}
From 0aff3c29bc7ef885fc85cc7a2a8d82d7542075b4 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sat, 1 Jun 2024 00:11:00 -0300
Subject: [PATCH 091/125] Fix reporter
---
src/debug/runReporter.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/debug/runReporter.ts b/src/debug/runReporter.ts
index 002fbae708..7b625b01b9 100644
--- a/src/debug/runReporter.ts
+++ b/src/debug/runReporter.ts
@@ -30,7 +30,7 @@ async function runReporter() {
}, "Vencord Reporter");
// @ts-ignore
- Vencord.Webpack._initReporter = function () {
+ 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(() => {
From dd4d80872efbddba326ecd4c22740ecd496a5949 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sat, 1 Jun 2024 00:17:15 -0300
Subject: [PATCH 092/125] fix reporter part 2
---
src/debug/runReporter.ts | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/debug/runReporter.ts b/src/debug/runReporter.ts
index 7b625b01b9..b8bfc759e2 100644
--- a/src/debug/runReporter.ts
+++ b/src/debug/runReporter.ts
@@ -30,7 +30,7 @@ async function runReporter() {
}, "Vencord Reporter");
// @ts-ignore
- Webpack._initReporter = function () {
+ 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(() => {
@@ -242,4 +242,5 @@ async function runReporter() {
}
}
-runReporter();
+// Run after the Vencord object has been created
+setTimeout(runReporter, 0);
From db5397f41a880399a5af00357fd4489bdf9c8581 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sat, 1 Jun 2024 00:22:17 -0300
Subject: [PATCH 093/125] sdfdsf
---
src/debug/runReporter.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/debug/runReporter.ts b/src/debug/runReporter.ts
index b8bfc759e2..05ecf719d9 100644
--- a/src/debug/runReporter.ts
+++ b/src/debug/runReporter.ts
@@ -157,7 +157,7 @@ async function runReporter() {
// Require deferred entry points
for (const deferredRequire of deferredRequires) {
- wreq(deferredRequire as any);
+ wreq(deferredRequire);
}
// All chunks Discord has mapped to asset files, even if they are not used anymore
From 4d8c56689cc1647b2c9810906625de4b25119641 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sat, 1 Jun 2024 01:40:33 -0300
Subject: [PATCH 094/125] Future proof against array modules
---
src/webpack/patchWebpack.ts | 41 ++++++++++++++++++++++---------------
1 file changed, 25 insertions(+), 16 deletions(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index 3734331661..944d83e863 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -42,20 +42,29 @@ const define: Define = (target, p, attributes) => {
});
};
-// 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.
-// If this is the main Webpack, we also set up the internal references to WebpackRequire.
+// wreq.O is the Webpack onChunksLoaded function.
+// It is pretty likely that all important Discord Webpack instances will have this property set,
+// because Discord bundled code is chunked.
+// As of the time of writing, only the main and sentry Webpack instances have this property, and they are the only ones we care about.
+
+// We use this setter to intercept when wreq.O is defined, and apply the patching in the modules factories (wreq.m).
// 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.
-define(Function.prototype, "m", {
+// We wrap wreq.m 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.
+define(Function.prototype, "O", {
enumerable: false,
- set(this: WebpackRequire, moduleFactories: PatchedModuleFactories) {
- // When using React DevTools or other extensions, we may also catch their Webpack here.
- // This ensures we actually got the right ones.
+ set(this: WebpackRequire, onChunksLoaded: WebpackRequire["O"]) {
+ define(this, "O", { value: onChunksLoaded });
+
const { stack } = new Error();
- if (!(stack?.includes("discord.com") || stack?.includes("discordapp.com")) || Array.isArray(moduleFactories)) {
- define(this, "m", { value: moduleFactories });
+ if (!stack?.includes("discord.com") && !stack?.includes("discordapp.com")) {
+ return;
+ }
+
+ if (this.m == null) {
return;
}
@@ -81,24 +90,24 @@ define(Function.prototype, "m", {
// If this is the main Webpack, wreq.m will always be set before the timeout runs.
const setterTimeout = setTimeout(() => Reflect.deleteProperty(this, "p"), 0);
- define(moduleFactories, Symbol.toStringTag, {
+ // Patch the pre-populated factories
+ for (const id in this.m) {
+ defineModulesFactoryGetter(id, Settings.eagerPatches ? patchFactory(id, this.m[id]) : this.m[id]);
+ }
+
+ define(this.m, Symbol.toStringTag, {
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);
+ const proxiedModuleFactories = new Proxy(this.m, 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
- for (const id in moduleFactories) {
- defineModulesFactoryGetter(id, Settings.eagerPatches ? patchFactory(id, moduleFactories[id]) : moduleFactories[id]);
- }
}
});
From 8580332c8222f68804141718038487a769250628 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sat, 1 Jun 2024 01:44:18 -0300
Subject: [PATCH 095/125] cleanup
---
src/webpack/patchWebpack.ts | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index 944d83e863..d3d722b7ab 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -60,11 +60,7 @@ define(Function.prototype, "O", {
define(this, "O", { value: onChunksLoaded });
const { stack } = new Error();
- if (!stack?.includes("discord.com") && !stack?.includes("discordapp.com")) {
- return;
- }
-
- if (this.m == null) {
+ if (this.m == null || !(stack?.includes("discord.com") || stack?.includes("discordapp.com"))) {
return;
}
From 33d5753ca7f7105dc689e3f59994a3c1ae289757 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sat, 1 Jun 2024 05:03:47 -0300
Subject: [PATCH 096/125] fix grammar issues
---
src/webpack/patchWebpack.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index d3d722b7ab..bf4efa5f17 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -47,9 +47,9 @@ const define: Define = (target, p, attributes) => {
// because Discord bundled code is chunked.
// As of the time of writing, only the main and sentry Webpack instances have this property, and they are the only ones we care about.
-// We use this setter to intercept when wreq.O is defined, and apply the patching in the modules factories (wreq.m).
+// We use this setter to intercept when wreq.O is defined, so we can patch the modules factories (wreq.m).
// 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 those.
// We wrap wreq.m 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.
@@ -83,7 +83,7 @@ define(Function.prototype, "O", {
}
});
// 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.p will always be set before the timeout runs.
const setterTimeout = setTimeout(() => Reflect.deleteProperty(this, "p"), 0);
// Patch the pre-populated factories
From 78fd37a4c682ccc54085e5eb91f51b03a6b952ef Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Sat, 1 Jun 2024 19:13:27 +0200
Subject: [PATCH 097/125] fix(css): brand-experiment is now brand-500
---
src/api/Notifications/NotificationComponent.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/api/Notifications/NotificationComponent.tsx b/src/api/Notifications/NotificationComponent.tsx
index caa4b64efe..d07143c450 100644
--- a/src/api/Notifications/NotificationComponent.tsx
+++ b/src/api/Notifications/NotificationComponent.tsx
@@ -113,7 +113,7 @@ export default ErrorBoundary.wrap(function NotificationComponent({
{timeout !== 0 && !permanent && (
)}
From c4c92ed366b6fb0e320c1d1eafe326e54b7fcf66 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sat, 1 Jun 2024 17:51:50 -0300
Subject: [PATCH 098/125] Add shortcut for lazy loading chunks
---
scripts/generateReport.ts | 11 +-
src/debug/loadLazyChunks.ts | 167 ++++++++++++++++++++++++++
src/debug/runReporter.ts | 164 ++-----------------------
src/plugins/consoleShortcuts/index.ts | 2 +
src/plugins/partyMode/index.ts | 3 +-
src/utils/Logger.ts | 2 +-
6 files changed, 189 insertions(+), 160 deletions(-)
create mode 100644 src/debug/loadLazyChunks.ts
diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts
index 0fde486375..d5f9a02531 100644
--- a/scripts/generateReport.ts
+++ b/scripts/generateReport.ts
@@ -242,16 +242,23 @@ page.on("console", async e => {
});
break;
+ case "LazyChunkLoader:":
+ console.error(await getText());
+
+ switch (message) {
+ case "A fatal error occurred:":
+ process.exit(1);
+ }
case "Reporter:":
console.error(await getText());
switch (message) {
+ case "A fatal error occurred:":
+ process.exit(1);
case "Webpack Find Fail:":
process.exitCode = 1;
report.badWebpackFinds.push(otherMessage);
break;
- case "A fatal error occurred:":
- process.exit(1);
case "Finished test":
await browser.close();
await printReport();
diff --git a/src/debug/loadLazyChunks.ts b/src/debug/loadLazyChunks.ts
new file mode 100644
index 0000000000..d8f84335c3
--- /dev/null
+++ b/src/debug/loadLazyChunks.ts
@@ -0,0 +1,167 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2024 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import { Logger } from "@utils/Logger";
+import { canonicalizeMatch } from "@utils/patches";
+import * as Webpack from "@webpack";
+import { wreq } from "@webpack";
+
+const LazyChunkLoaderLogger = new Logger("LazyChunkLoader");
+
+export async function loadLazyChunks() {
+ try {
+ LazyChunkLoaderLogger.log("Loading all chunks...");
+
+ const validChunks = new Set();
+ const invalidChunks = new Set();
+ const deferredRequires = new Set();
+
+ let chunksSearchingResolve: (value: void | PromiseLike) => void;
+ const chunksSearchingDone = new Promise(r => chunksSearchingResolve = r);
+
+ // True if resolved, false otherwise
+ const chunksSearchPromises = [] as Array<() => boolean>;
+
+ const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("[^)]+?"\)[^\]]*?)(?:\]\))?)\.then\(\i\.bind\(\i,"([^)]+?)"\)\)/g);
+
+ async function searchAndLoadLazyChunks(factoryCode: string) {
+ const lazyChunks = factoryCode.matchAll(LazyChunkRegex);
+ const validChunkGroups = new Set<[chunkIds: string[], entryPoint: string]>();
+
+ // Workaround for a chunk that depends on the ChannelMessage component but may be be force loaded before
+ // the chunk containing the component
+ const shouldForceDefer = factoryCode.includes(".Messages.GUILD_FEED_UNFEATURE_BUTTON_TEXT");
+
+ await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => {
+ const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => m[1]) : [];
+
+ if (chunkIds.length === 0) {
+ return;
+ }
+
+ let invalidChunkGroup = false;
+
+ for (const id of chunkIds) {
+ if (wreq.u(id) == null || wreq.u(id) === "undefined.js") continue;
+
+ const isWasm = await fetch(wreq.p + wreq.u(id))
+ .then(r => r.text())
+ .then(t => (IS_WEB && t.includes(".module.wasm")) || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push"));
+
+ if (isWasm && IS_WEB) {
+ invalidChunks.add(id);
+ invalidChunkGroup = true;
+ continue;
+ }
+
+ validChunks.add(id);
+ }
+
+ if (!invalidChunkGroup) {
+ validChunkGroups.add([chunkIds, entryPoint]);
+ }
+ }));
+
+ // Loads all found valid chunk groups
+ await Promise.all(
+ Array.from(validChunkGroups)
+ .map(([chunkIds]) =>
+ Promise.all(chunkIds.map(id => wreq.e(id as any).catch(() => { })))
+ )
+ );
+
+ // Requires the entry points for all valid chunk groups
+ for (const [, entryPoint] of validChunkGroups) {
+ try {
+ if (shouldForceDefer) {
+ deferredRequires.add(entryPoint);
+ continue;
+ }
+
+ if (wreq.m[entryPoint]) wreq(entryPoint as any);
+ } catch (err) {
+ console.error(err);
+ }
+ }
+
+ // setImmediate to only check if all chunks were loaded after this function resolves
+ // We check if all chunks were loaded every time a factory is loaded
+ // If we are still looking for chunks in the other factories, the array will have that factory's chunk search promise not resolved
+ // But, if all chunk search promises are resolved, this means we found every lazy chunk loaded by Discord code and manually loaded them
+ setTimeout(() => {
+ let allResolved = true;
+
+ for (let i = 0; i < chunksSearchPromises.length; i++) {
+ const isResolved = chunksSearchPromises[i]();
+
+ if (isResolved) {
+ // Remove finished promises to avoid having to iterate through a huge array everytime
+ chunksSearchPromises.splice(i--, 1);
+ } else {
+ allResolved = false;
+ }
+ }
+
+ if (allResolved) chunksSearchingResolve();
+ }, 0);
+ }
+
+ Webpack.factoryListeners.add(factory => {
+ let isResolved = false;
+ searchAndLoadLazyChunks(factory.toString()).then(() => isResolved = true);
+
+ chunksSearchPromises.push(() => isResolved);
+ });
+
+ for (const factoryId in wreq.m) {
+ let isResolved = false;
+ searchAndLoadLazyChunks(wreq.m[factoryId].toString()).then(() => isResolved = true);
+
+ chunksSearchPromises.push(() => isResolved);
+ }
+
+ await chunksSearchingDone;
+
+ // Require deferred entry points
+ for (const deferredRequire of deferredRequires) {
+ wreq!(deferredRequire as any);
+ }
+
+ // All chunks Discord has mapped to asset files, even if they are not used anymore
+ const allChunks = [] as string[];
+
+ // Matches "id" or id:
+ for (const currentMatch of wreq!.u.toString().matchAll(/(?:"(\d+?)")|(?:(\d+?):)/g)) {
+ const id = currentMatch[1] ?? currentMatch[2];
+ if (id == null) continue;
+
+ allChunks.push(id);
+ }
+
+ if (allChunks.length === 0) throw new Error("Failed to get all chunks");
+
+ // Chunks that are not loaded (not used) by Discord code anymore
+ const chunksLeft = allChunks.filter(id => {
+ return !(validChunks.has(id) || invalidChunks.has(id));
+ });
+
+ await Promise.all(chunksLeft.map(async id => {
+ const isWasm = await fetch(wreq.p + wreq.u(id))
+ .then(r => r.text())
+ .then(t => (IS_WEB && t.includes(".module.wasm")) || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push"));
+
+ // Loads and requires a chunk
+ if (!isWasm) {
+ await wreq.e(id as any);
+ if (wreq.m[id]) wreq(id as any);
+ }
+ }));
+
+ LazyChunkLoaderLogger.log("Finished loading all chunks!");
+ } catch (e) {
+ LazyChunkLoaderLogger.log("A fatal error occurred:", e);
+ }
+}
diff --git a/src/debug/runReporter.ts b/src/debug/runReporter.ts
index 61c9f162b9..95cda51892 100644
--- a/src/debug/runReporter.ts
+++ b/src/debug/runReporter.ts
@@ -5,171 +5,23 @@
*/
import { Logger } from "@utils/Logger";
-import { canonicalizeMatch } from "@utils/patches";
import * as Webpack from "@webpack";
-import { wreq } from "@webpack";
import { patches } from "plugins";
+import { loadLazyChunks } from "./loadLazyChunks";
+
+
const ReporterLogger = new Logger("Reporter");
async function runReporter() {
- ReporterLogger.log("Starting test...");
-
try {
- const validChunks = new Set();
- const invalidChunks = new Set();
- const deferredRequires = new Set();
-
- let chunksSearchingResolve: (value: void | PromiseLike) => void;
- const chunksSearchingDone = new Promise(r => chunksSearchingResolve = r);
-
- // True if resolved, false otherwise
- const chunksSearchPromises = [] as Array<() => boolean>;
-
- const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("[^)]+?"\)[^\]]*?)(?:\]\))?)\.then\(\i\.bind\(\i,"([^)]+?)"\)\)/g);
-
- async function searchAndLoadLazyChunks(factoryCode: string) {
- const lazyChunks = factoryCode.matchAll(LazyChunkRegex);
- const validChunkGroups = new Set<[chunkIds: string[], entryPoint: string]>();
-
- // Workaround for a chunk that depends on the ChannelMessage component but may be be force loaded before
- // the chunk containing the component
- const shouldForceDefer = factoryCode.includes(".Messages.GUILD_FEED_UNFEATURE_BUTTON_TEXT");
-
- await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => {
- const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => m[1]) : [];
-
- if (chunkIds.length === 0) {
- return;
- }
-
- let invalidChunkGroup = false;
-
- for (const id of chunkIds) {
- if (wreq.u(id) == null || wreq.u(id) === "undefined.js") continue;
-
- const isWasm = await fetch(wreq.p + wreq.u(id))
- .then(r => r.text())
- .then(t => (IS_WEB && t.includes(".module.wasm")) || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push"));
-
- if (isWasm && IS_WEB) {
- invalidChunks.add(id);
- invalidChunkGroup = true;
- continue;
- }
-
- validChunks.add(id);
- }
-
- if (!invalidChunkGroup) {
- validChunkGroups.add([chunkIds, entryPoint]);
- }
- }));
-
- // Loads all found valid chunk groups
- await Promise.all(
- Array.from(validChunkGroups)
- .map(([chunkIds]) =>
- Promise.all(chunkIds.map(id => wreq.e(id as any).catch(() => { })))
- )
- );
-
- // Requires the entry points for all valid chunk groups
- for (const [, entryPoint] of validChunkGroups) {
- try {
- if (shouldForceDefer) {
- deferredRequires.add(entryPoint);
- continue;
- }
-
- if (wreq.m[entryPoint]) wreq(entryPoint as any);
- } catch (err) {
- console.error(err);
- }
- }
+ ReporterLogger.log("Starting test...");
- // setImmediate to only check if all chunks were loaded after this function resolves
- // We check if all chunks were loaded every time a factory is loaded
- // If we are still looking for chunks in the other factories, the array will have that factory's chunk search promise not resolved
- // But, if all chunk search promises are resolved, this means we found every lazy chunk loaded by Discord code and manually loaded them
- setTimeout(() => {
- let allResolved = true;
-
- for (let i = 0; i < chunksSearchPromises.length; i++) {
- const isResolved = chunksSearchPromises[i]();
-
- if (isResolved) {
- // Remove finished promises to avoid having to iterate through a huge array everytime
- chunksSearchPromises.splice(i--, 1);
- } else {
- allResolved = false;
- }
- }
-
- if (allResolved) chunksSearchingResolve();
- }, 0);
- }
-
- Webpack.beforeInitListeners.add(async () => {
- ReporterLogger.log("Loading all chunks...");
-
- Webpack.factoryListeners.add(factory => {
- let isResolved = false;
- searchAndLoadLazyChunks(factory.toString()).then(() => isResolved = true);
-
- chunksSearchPromises.push(() => isResolved);
- });
-
- // 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(wreq.m[factoryId].toString()).then(() => isResolved = true);
-
- chunksSearchPromises.push(() => isResolved);
- }
- }, 0);
- });
-
- await chunksSearchingDone;
-
- // Require deferred entry points
- for (const deferredRequire of deferredRequires) {
- wreq!(deferredRequire as any);
- }
-
- // All chunks Discord has mapped to asset files, even if they are not used anymore
- const allChunks = [] as string[];
-
- // Matches "id" or id:
- for (const currentMatch of wreq!.u.toString().matchAll(/(?:"(\d+?)")|(?:(\d+?):)/g)) {
- const id = currentMatch[1] ?? currentMatch[2];
- if (id == null) continue;
-
- allChunks.push(id);
- }
-
- if (allChunks.length === 0) throw new Error("Failed to get all chunks");
-
- // Chunks that are not loaded (not used) by Discord code anymore
- const chunksLeft = allChunks.filter(id => {
- return !(validChunks.has(id) || invalidChunks.has(id));
- });
-
- await Promise.all(chunksLeft.map(async id => {
- const isWasm = await fetch(wreq.p + wreq.u(id))
- .then(r => r.text())
- .then(t => (IS_WEB && t.includes(".module.wasm")) || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push"));
-
- // Loads and requires a chunk
- if (!isWasm) {
- await wreq.e(id as any);
- if (wreq.m[id]) wreq(id as any);
- }
- }));
+ let loadLazyChunksResolve: (value: void | PromiseLike) => void;
+ const loadLazyChunksDone = new Promise(r => loadLazyChunksResolve = r);
- ReporterLogger.log("Finished loading all chunks!");
+ Webpack.beforeInitListeners.add(() => loadLazyChunks().then(() => loadLazyChunksResolve()));
+ await loadLazyChunksDone;
for (const patch of patches) {
if (!patch.all) {
diff --git a/src/plugins/consoleShortcuts/index.ts b/src/plugins/consoleShortcuts/index.ts
index ee86b5fcfd..0a1323e755 100644
--- a/src/plugins/consoleShortcuts/index.ts
+++ b/src/plugins/consoleShortcuts/index.ts
@@ -25,6 +25,7 @@ import definePlugin, { PluginNative, StartAt } from "@utils/types";
import * as Webpack from "@webpack";
import { extract, filters, findAll, findModuleId, search } from "@webpack";
import * as Common from "@webpack/common";
+import { loadLazyChunks } from "debug/loadLazyChunks";
import type { ComponentType } from "react";
const DESKTOP_ONLY = (f: string) => () => {
@@ -82,6 +83,7 @@ function makeShortcuts() {
wpsearch: search,
wpex: extract,
wpexs: (code: string) => extract(findModuleId(code)!),
+ loadLazyChunks: IS_DEV ? loadLazyChunks : () => { throw new Error("loadLazyChunks is dev only."); },
find,
findAll: findAll,
findByProps,
diff --git a/src/plugins/partyMode/index.ts b/src/plugins/partyMode/index.ts
index 56c19c02c6..c40f2e3c75 100644
--- a/src/plugins/partyMode/index.ts
+++ b/src/plugins/partyMode/index.ts
@@ -18,7 +18,7 @@
import { definePluginSettings, migratePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
-import definePlugin, { OptionType } from "@utils/types";
+import definePlugin, { OptionType, ReporterTestable } from "@utils/types";
import { FluxDispatcher } from "@webpack/common";
const enum Intensity {
@@ -46,6 +46,7 @@ export default definePlugin({
name: "PartyMode",
description: "Allows you to use party mode cause the party never ends ✨",
authors: [Devs.UwUDev],
+ reporterTestable: ReporterTestable.None,
settings,
start() {
diff --git a/src/utils/Logger.ts b/src/utils/Logger.ts
index 5296184d4b..22a3813600 100644
--- a/src/utils/Logger.ts
+++ b/src/utils/Logger.ts
@@ -32,7 +32,7 @@ export class Logger {
constructor(public name: string, public color: string = "white") { }
private _log(level: "log" | "error" | "warn" | "info" | "debug", levelColor: string, args: any[], customFmt = "") {
- if (IS_REPORTER) {
+ if (IS_REPORTER && IS_WEB) {
console[level]("[Vencord]", this.name + ":", ...args);
return;
}
From c50208b0193c072959e7f59dfd990c6871011f62 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sat, 1 Jun 2024 18:10:45 -0300
Subject: [PATCH 099/125] Add shortcut for lazy loading chunks
---
scripts/generateReport.ts | 11 +-
src/api/Settings.ts | 2 +-
src/debug/loadLazyChunks.ts | 167 ++++++++++++++++++++++++++
src/debug/runReporter.ts | 163 ++-----------------------
src/plugins/consoleShortcuts/index.ts | 2 +
src/plugins/index.ts | 1 -
src/plugins/partyMode/index.ts | 3 +-
src/utils/Logger.ts | 2 +-
8 files changed, 189 insertions(+), 162 deletions(-)
create mode 100644 src/debug/loadLazyChunks.ts
diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts
index 0fde486375..d5f9a02531 100644
--- a/scripts/generateReport.ts
+++ b/scripts/generateReport.ts
@@ -242,16 +242,23 @@ page.on("console", async e => {
});
break;
+ case "LazyChunkLoader:":
+ console.error(await getText());
+
+ switch (message) {
+ case "A fatal error occurred:":
+ process.exit(1);
+ }
case "Reporter:":
console.error(await getText());
switch (message) {
+ case "A fatal error occurred:":
+ process.exit(1);
case "Webpack Find Fail:":
process.exitCode = 1;
report.badWebpackFinds.push(otherMessage);
break;
- case "A fatal error occurred:":
- process.exit(1);
case "Finished test":
await browser.close();
await printReport();
diff --git a/src/api/Settings.ts b/src/api/Settings.ts
index b94e6a3fd9..70ba0bd4a0 100644
--- a/src/api/Settings.ts
+++ b/src/api/Settings.ts
@@ -129,7 +129,7 @@ export const SettingsStore = new SettingsStoreClass(settings, {
if (path === "plugins" && key in plugins)
return target[key] = {
- enabled: plugins[key].required ?? plugins[key].enabledByDefault ?? false
+ enabled: IS_REPORTER ?? plugins[key].required ?? plugins[key].enabledByDefault ?? false
};
// Since the property is not set, check if this is a plugin's setting and if so, try to resolve
diff --git a/src/debug/loadLazyChunks.ts b/src/debug/loadLazyChunks.ts
new file mode 100644
index 0000000000..d8f84335c3
--- /dev/null
+++ b/src/debug/loadLazyChunks.ts
@@ -0,0 +1,167 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2024 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import { Logger } from "@utils/Logger";
+import { canonicalizeMatch } from "@utils/patches";
+import * as Webpack from "@webpack";
+import { wreq } from "@webpack";
+
+const LazyChunkLoaderLogger = new Logger("LazyChunkLoader");
+
+export async function loadLazyChunks() {
+ try {
+ LazyChunkLoaderLogger.log("Loading all chunks...");
+
+ const validChunks = new Set();
+ const invalidChunks = new Set();
+ const deferredRequires = new Set();
+
+ let chunksSearchingResolve: (value: void | PromiseLike) => void;
+ const chunksSearchingDone = new Promise(r => chunksSearchingResolve = r);
+
+ // True if resolved, false otherwise
+ const chunksSearchPromises = [] as Array<() => boolean>;
+
+ const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("[^)]+?"\)[^\]]*?)(?:\]\))?)\.then\(\i\.bind\(\i,"([^)]+?)"\)\)/g);
+
+ async function searchAndLoadLazyChunks(factoryCode: string) {
+ const lazyChunks = factoryCode.matchAll(LazyChunkRegex);
+ const validChunkGroups = new Set<[chunkIds: string[], entryPoint: string]>();
+
+ // Workaround for a chunk that depends on the ChannelMessage component but may be be force loaded before
+ // the chunk containing the component
+ const shouldForceDefer = factoryCode.includes(".Messages.GUILD_FEED_UNFEATURE_BUTTON_TEXT");
+
+ await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => {
+ const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => m[1]) : [];
+
+ if (chunkIds.length === 0) {
+ return;
+ }
+
+ let invalidChunkGroup = false;
+
+ for (const id of chunkIds) {
+ if (wreq.u(id) == null || wreq.u(id) === "undefined.js") continue;
+
+ const isWasm = await fetch(wreq.p + wreq.u(id))
+ .then(r => r.text())
+ .then(t => (IS_WEB && t.includes(".module.wasm")) || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push"));
+
+ if (isWasm && IS_WEB) {
+ invalidChunks.add(id);
+ invalidChunkGroup = true;
+ continue;
+ }
+
+ validChunks.add(id);
+ }
+
+ if (!invalidChunkGroup) {
+ validChunkGroups.add([chunkIds, entryPoint]);
+ }
+ }));
+
+ // Loads all found valid chunk groups
+ await Promise.all(
+ Array.from(validChunkGroups)
+ .map(([chunkIds]) =>
+ Promise.all(chunkIds.map(id => wreq.e(id as any).catch(() => { })))
+ )
+ );
+
+ // Requires the entry points for all valid chunk groups
+ for (const [, entryPoint] of validChunkGroups) {
+ try {
+ if (shouldForceDefer) {
+ deferredRequires.add(entryPoint);
+ continue;
+ }
+
+ if (wreq.m[entryPoint]) wreq(entryPoint as any);
+ } catch (err) {
+ console.error(err);
+ }
+ }
+
+ // setImmediate to only check if all chunks were loaded after this function resolves
+ // We check if all chunks were loaded every time a factory is loaded
+ // If we are still looking for chunks in the other factories, the array will have that factory's chunk search promise not resolved
+ // But, if all chunk search promises are resolved, this means we found every lazy chunk loaded by Discord code and manually loaded them
+ setTimeout(() => {
+ let allResolved = true;
+
+ for (let i = 0; i < chunksSearchPromises.length; i++) {
+ const isResolved = chunksSearchPromises[i]();
+
+ if (isResolved) {
+ // Remove finished promises to avoid having to iterate through a huge array everytime
+ chunksSearchPromises.splice(i--, 1);
+ } else {
+ allResolved = false;
+ }
+ }
+
+ if (allResolved) chunksSearchingResolve();
+ }, 0);
+ }
+
+ Webpack.factoryListeners.add(factory => {
+ let isResolved = false;
+ searchAndLoadLazyChunks(factory.toString()).then(() => isResolved = true);
+
+ chunksSearchPromises.push(() => isResolved);
+ });
+
+ for (const factoryId in wreq.m) {
+ let isResolved = false;
+ searchAndLoadLazyChunks(wreq.m[factoryId].toString()).then(() => isResolved = true);
+
+ chunksSearchPromises.push(() => isResolved);
+ }
+
+ await chunksSearchingDone;
+
+ // Require deferred entry points
+ for (const deferredRequire of deferredRequires) {
+ wreq!(deferredRequire as any);
+ }
+
+ // All chunks Discord has mapped to asset files, even if they are not used anymore
+ const allChunks = [] as string[];
+
+ // Matches "id" or id:
+ for (const currentMatch of wreq!.u.toString().matchAll(/(?:"(\d+?)")|(?:(\d+?):)/g)) {
+ const id = currentMatch[1] ?? currentMatch[2];
+ if (id == null) continue;
+
+ allChunks.push(id);
+ }
+
+ if (allChunks.length === 0) throw new Error("Failed to get all chunks");
+
+ // Chunks that are not loaded (not used) by Discord code anymore
+ const chunksLeft = allChunks.filter(id => {
+ return !(validChunks.has(id) || invalidChunks.has(id));
+ });
+
+ await Promise.all(chunksLeft.map(async id => {
+ const isWasm = await fetch(wreq.p + wreq.u(id))
+ .then(r => r.text())
+ .then(t => (IS_WEB && t.includes(".module.wasm")) || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push"));
+
+ // Loads and requires a chunk
+ if (!isWasm) {
+ await wreq.e(id as any);
+ if (wreq.m[id]) wreq(id as any);
+ }
+ }));
+
+ LazyChunkLoaderLogger.log("Finished loading all chunks!");
+ } catch (e) {
+ LazyChunkLoaderLogger.log("A fatal error occurred:", e);
+ }
+}
diff --git a/src/debug/runReporter.ts b/src/debug/runReporter.ts
index 61c9f162b9..6c7a2a03f2 100644
--- a/src/debug/runReporter.ts
+++ b/src/debug/runReporter.ts
@@ -5,171 +5,22 @@
*/
import { Logger } from "@utils/Logger";
-import { canonicalizeMatch } from "@utils/patches";
import * as Webpack from "@webpack";
-import { wreq } from "@webpack";
import { patches } from "plugins";
+import { loadLazyChunks } from "./loadLazyChunks";
+
const ReporterLogger = new Logger("Reporter");
async function runReporter() {
- ReporterLogger.log("Starting test...");
-
try {
- const validChunks = new Set();
- const invalidChunks = new Set();
- const deferredRequires = new Set();
-
- let chunksSearchingResolve: (value: void | PromiseLike) => void;
- const chunksSearchingDone = new Promise(r => chunksSearchingResolve = r);
-
- // True if resolved, false otherwise
- const chunksSearchPromises = [] as Array<() => boolean>;
-
- const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("[^)]+?"\)[^\]]*?)(?:\]\))?)\.then\(\i\.bind\(\i,"([^)]+?)"\)\)/g);
-
- async function searchAndLoadLazyChunks(factoryCode: string) {
- const lazyChunks = factoryCode.matchAll(LazyChunkRegex);
- const validChunkGroups = new Set<[chunkIds: string[], entryPoint: string]>();
-
- // Workaround for a chunk that depends on the ChannelMessage component but may be be force loaded before
- // the chunk containing the component
- const shouldForceDefer = factoryCode.includes(".Messages.GUILD_FEED_UNFEATURE_BUTTON_TEXT");
-
- await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => {
- const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => m[1]) : [];
-
- if (chunkIds.length === 0) {
- return;
- }
-
- let invalidChunkGroup = false;
+ ReporterLogger.log("Starting test...");
- for (const id of chunkIds) {
- if (wreq.u(id) == null || wreq.u(id) === "undefined.js") continue;
-
- const isWasm = await fetch(wreq.p + wreq.u(id))
- .then(r => r.text())
- .then(t => (IS_WEB && t.includes(".module.wasm")) || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push"));
-
- if (isWasm && IS_WEB) {
- invalidChunks.add(id);
- invalidChunkGroup = true;
- continue;
- }
-
- validChunks.add(id);
- }
-
- if (!invalidChunkGroup) {
- validChunkGroups.add([chunkIds, entryPoint]);
- }
- }));
-
- // Loads all found valid chunk groups
- await Promise.all(
- Array.from(validChunkGroups)
- .map(([chunkIds]) =>
- Promise.all(chunkIds.map(id => wreq.e(id as any).catch(() => { })))
- )
- );
-
- // Requires the entry points for all valid chunk groups
- for (const [, entryPoint] of validChunkGroups) {
- try {
- if (shouldForceDefer) {
- deferredRequires.add(entryPoint);
- continue;
- }
-
- if (wreq.m[entryPoint]) wreq(entryPoint as any);
- } catch (err) {
- console.error(err);
- }
- }
-
- // setImmediate to only check if all chunks were loaded after this function resolves
- // We check if all chunks were loaded every time a factory is loaded
- // If we are still looking for chunks in the other factories, the array will have that factory's chunk search promise not resolved
- // But, if all chunk search promises are resolved, this means we found every lazy chunk loaded by Discord code and manually loaded them
- setTimeout(() => {
- let allResolved = true;
-
- for (let i = 0; i < chunksSearchPromises.length; i++) {
- const isResolved = chunksSearchPromises[i]();
-
- if (isResolved) {
- // Remove finished promises to avoid having to iterate through a huge array everytime
- chunksSearchPromises.splice(i--, 1);
- } else {
- allResolved = false;
- }
- }
-
- if (allResolved) chunksSearchingResolve();
- }, 0);
- }
-
- Webpack.beforeInitListeners.add(async () => {
- ReporterLogger.log("Loading all chunks...");
-
- Webpack.factoryListeners.add(factory => {
- let isResolved = false;
- searchAndLoadLazyChunks(factory.toString()).then(() => isResolved = true);
-
- chunksSearchPromises.push(() => isResolved);
- });
-
- // 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(wreq.m[factoryId].toString()).then(() => isResolved = true);
-
- chunksSearchPromises.push(() => isResolved);
- }
- }, 0);
- });
-
- await chunksSearchingDone;
-
- // Require deferred entry points
- for (const deferredRequire of deferredRequires) {
- wreq!(deferredRequire as any);
- }
-
- // All chunks Discord has mapped to asset files, even if they are not used anymore
- const allChunks = [] as string[];
-
- // Matches "id" or id:
- for (const currentMatch of wreq!.u.toString().matchAll(/(?:"(\d+?)")|(?:(\d+?):)/g)) {
- const id = currentMatch[1] ?? currentMatch[2];
- if (id == null) continue;
-
- allChunks.push(id);
- }
-
- if (allChunks.length === 0) throw new Error("Failed to get all chunks");
-
- // Chunks that are not loaded (not used) by Discord code anymore
- const chunksLeft = allChunks.filter(id => {
- return !(validChunks.has(id) || invalidChunks.has(id));
- });
-
- await Promise.all(chunksLeft.map(async id => {
- const isWasm = await fetch(wreq.p + wreq.u(id))
- .then(r => r.text())
- .then(t => (IS_WEB && t.includes(".module.wasm")) || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push"));
-
- // Loads and requires a chunk
- if (!isWasm) {
- await wreq.e(id as any);
- if (wreq.m[id]) wreq(id as any);
- }
- }));
+ let loadLazyChunksResolve: (value: void | PromiseLike) => void;
+ const loadLazyChunksDone = new Promise(r => loadLazyChunksResolve = r);
- ReporterLogger.log("Finished loading all chunks!");
+ Webpack.beforeInitListeners.add(() => loadLazyChunks().then((loadLazyChunksResolve)));
+ await loadLazyChunksDone;
for (const patch of patches) {
if (!patch.all) {
diff --git a/src/plugins/consoleShortcuts/index.ts b/src/plugins/consoleShortcuts/index.ts
index ee86b5fcfd..0a1323e755 100644
--- a/src/plugins/consoleShortcuts/index.ts
+++ b/src/plugins/consoleShortcuts/index.ts
@@ -25,6 +25,7 @@ import definePlugin, { PluginNative, StartAt } from "@utils/types";
import * as Webpack from "@webpack";
import { extract, filters, findAll, findModuleId, search } from "@webpack";
import * as Common from "@webpack/common";
+import { loadLazyChunks } from "debug/loadLazyChunks";
import type { ComponentType } from "react";
const DESKTOP_ONLY = (f: string) => () => {
@@ -82,6 +83,7 @@ function makeShortcuts() {
wpsearch: search,
wpex: extract,
wpexs: (code: string) => extract(findModuleId(code)!),
+ loadLazyChunks: IS_DEV ? loadLazyChunks : () => { throw new Error("loadLazyChunks is dev only."); },
find,
findAll: findAll,
findByProps,
diff --git a/src/plugins/index.ts b/src/plugins/index.ts
index 53ab7983a5..32bfe7e978 100644
--- a/src/plugins/index.ts
+++ b/src/plugins/index.ts
@@ -44,7 +44,6 @@ const settings = Settings.plugins;
export function isPluginEnabled(p: string) {
return (
- IS_REPORTER ||
Plugins[p]?.required ||
Plugins[p]?.isDependency ||
settings[p]?.enabled
diff --git a/src/plugins/partyMode/index.ts b/src/plugins/partyMode/index.ts
index 56c19c02c6..c40f2e3c75 100644
--- a/src/plugins/partyMode/index.ts
+++ b/src/plugins/partyMode/index.ts
@@ -18,7 +18,7 @@
import { definePluginSettings, migratePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
-import definePlugin, { OptionType } from "@utils/types";
+import definePlugin, { OptionType, ReporterTestable } from "@utils/types";
import { FluxDispatcher } from "@webpack/common";
const enum Intensity {
@@ -46,6 +46,7 @@ export default definePlugin({
name: "PartyMode",
description: "Allows you to use party mode cause the party never ends ✨",
authors: [Devs.UwUDev],
+ reporterTestable: ReporterTestable.None,
settings,
start() {
diff --git a/src/utils/Logger.ts b/src/utils/Logger.ts
index 5296184d4b..22a3813600 100644
--- a/src/utils/Logger.ts
+++ b/src/utils/Logger.ts
@@ -32,7 +32,7 @@ export class Logger {
constructor(public name: string, public color: string = "white") { }
private _log(level: "log" | "error" | "warn" | "info" | "debug", levelColor: string, args: any[], customFmt = "") {
- if (IS_REPORTER) {
+ if (IS_REPORTER && IS_WEB) {
console[level]("[Vencord]", this.name + ":", ...args);
return;
}
From d8524b087c850443cee954d8582f18de98a6806e Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sat, 1 Jun 2024 18:39:01 -0300
Subject: [PATCH 100/125] Add shortcut for lazy loading chunks
---
scripts/generateReport.ts | 13 +-
src/api/Settings.ts | 2 +-
src/debug/loadLazyChunks.ts | 167 ++++++++++++++++++++++++++
src/debug/runReporter.ts | 163 ++-----------------------
src/plugins/consoleShortcuts/index.ts | 2 +
src/plugins/index.ts | 1 -
src/plugins/partyMode/index.ts | 3 +-
src/utils/Logger.ts | 2 +-
8 files changed, 191 insertions(+), 162 deletions(-)
create mode 100644 src/debug/loadLazyChunks.ts
diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts
index 0fde486375..cf42107792 100644
--- a/scripts/generateReport.ts
+++ b/scripts/generateReport.ts
@@ -241,17 +241,26 @@ page.on("console", async e => {
error: await maybeGetError(e.args()[3]) ?? "Unknown error"
});
+ break;
+ case "LazyChunkLoader:":
+ console.error(await getText());
+
+ switch (message) {
+ case "A fatal error occurred:":
+ process.exit(1);
+ }
+
break;
case "Reporter:":
console.error(await getText());
switch (message) {
+ case "A fatal error occurred:":
+ process.exit(1);
case "Webpack Find Fail:":
process.exitCode = 1;
report.badWebpackFinds.push(otherMessage);
break;
- case "A fatal error occurred:":
- process.exit(1);
case "Finished test":
await browser.close();
await printReport();
diff --git a/src/api/Settings.ts b/src/api/Settings.ts
index b94e6a3fd9..70ba0bd4a0 100644
--- a/src/api/Settings.ts
+++ b/src/api/Settings.ts
@@ -129,7 +129,7 @@ export const SettingsStore = new SettingsStoreClass(settings, {
if (path === "plugins" && key in plugins)
return target[key] = {
- enabled: plugins[key].required ?? plugins[key].enabledByDefault ?? false
+ enabled: IS_REPORTER ?? plugins[key].required ?? plugins[key].enabledByDefault ?? false
};
// Since the property is not set, check if this is a plugin's setting and if so, try to resolve
diff --git a/src/debug/loadLazyChunks.ts b/src/debug/loadLazyChunks.ts
new file mode 100644
index 0000000000..d8f84335c3
--- /dev/null
+++ b/src/debug/loadLazyChunks.ts
@@ -0,0 +1,167 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2024 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import { Logger } from "@utils/Logger";
+import { canonicalizeMatch } from "@utils/patches";
+import * as Webpack from "@webpack";
+import { wreq } from "@webpack";
+
+const LazyChunkLoaderLogger = new Logger("LazyChunkLoader");
+
+export async function loadLazyChunks() {
+ try {
+ LazyChunkLoaderLogger.log("Loading all chunks...");
+
+ const validChunks = new Set();
+ const invalidChunks = new Set();
+ const deferredRequires = new Set();
+
+ let chunksSearchingResolve: (value: void | PromiseLike) => void;
+ const chunksSearchingDone = new Promise(r => chunksSearchingResolve = r);
+
+ // True if resolved, false otherwise
+ const chunksSearchPromises = [] as Array<() => boolean>;
+
+ const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("[^)]+?"\)[^\]]*?)(?:\]\))?)\.then\(\i\.bind\(\i,"([^)]+?)"\)\)/g);
+
+ async function searchAndLoadLazyChunks(factoryCode: string) {
+ const lazyChunks = factoryCode.matchAll(LazyChunkRegex);
+ const validChunkGroups = new Set<[chunkIds: string[], entryPoint: string]>();
+
+ // Workaround for a chunk that depends on the ChannelMessage component but may be be force loaded before
+ // the chunk containing the component
+ const shouldForceDefer = factoryCode.includes(".Messages.GUILD_FEED_UNFEATURE_BUTTON_TEXT");
+
+ await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => {
+ const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => m[1]) : [];
+
+ if (chunkIds.length === 0) {
+ return;
+ }
+
+ let invalidChunkGroup = false;
+
+ for (const id of chunkIds) {
+ if (wreq.u(id) == null || wreq.u(id) === "undefined.js") continue;
+
+ const isWasm = await fetch(wreq.p + wreq.u(id))
+ .then(r => r.text())
+ .then(t => (IS_WEB && t.includes(".module.wasm")) || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push"));
+
+ if (isWasm && IS_WEB) {
+ invalidChunks.add(id);
+ invalidChunkGroup = true;
+ continue;
+ }
+
+ validChunks.add(id);
+ }
+
+ if (!invalidChunkGroup) {
+ validChunkGroups.add([chunkIds, entryPoint]);
+ }
+ }));
+
+ // Loads all found valid chunk groups
+ await Promise.all(
+ Array.from(validChunkGroups)
+ .map(([chunkIds]) =>
+ Promise.all(chunkIds.map(id => wreq.e(id as any).catch(() => { })))
+ )
+ );
+
+ // Requires the entry points for all valid chunk groups
+ for (const [, entryPoint] of validChunkGroups) {
+ try {
+ if (shouldForceDefer) {
+ deferredRequires.add(entryPoint);
+ continue;
+ }
+
+ if (wreq.m[entryPoint]) wreq(entryPoint as any);
+ } catch (err) {
+ console.error(err);
+ }
+ }
+
+ // setImmediate to only check if all chunks were loaded after this function resolves
+ // We check if all chunks were loaded every time a factory is loaded
+ // If we are still looking for chunks in the other factories, the array will have that factory's chunk search promise not resolved
+ // But, if all chunk search promises are resolved, this means we found every lazy chunk loaded by Discord code and manually loaded them
+ setTimeout(() => {
+ let allResolved = true;
+
+ for (let i = 0; i < chunksSearchPromises.length; i++) {
+ const isResolved = chunksSearchPromises[i]();
+
+ if (isResolved) {
+ // Remove finished promises to avoid having to iterate through a huge array everytime
+ chunksSearchPromises.splice(i--, 1);
+ } else {
+ allResolved = false;
+ }
+ }
+
+ if (allResolved) chunksSearchingResolve();
+ }, 0);
+ }
+
+ Webpack.factoryListeners.add(factory => {
+ let isResolved = false;
+ searchAndLoadLazyChunks(factory.toString()).then(() => isResolved = true);
+
+ chunksSearchPromises.push(() => isResolved);
+ });
+
+ for (const factoryId in wreq.m) {
+ let isResolved = false;
+ searchAndLoadLazyChunks(wreq.m[factoryId].toString()).then(() => isResolved = true);
+
+ chunksSearchPromises.push(() => isResolved);
+ }
+
+ await chunksSearchingDone;
+
+ // Require deferred entry points
+ for (const deferredRequire of deferredRequires) {
+ wreq!(deferredRequire as any);
+ }
+
+ // All chunks Discord has mapped to asset files, even if they are not used anymore
+ const allChunks = [] as string[];
+
+ // Matches "id" or id:
+ for (const currentMatch of wreq!.u.toString().matchAll(/(?:"(\d+?)")|(?:(\d+?):)/g)) {
+ const id = currentMatch[1] ?? currentMatch[2];
+ if (id == null) continue;
+
+ allChunks.push(id);
+ }
+
+ if (allChunks.length === 0) throw new Error("Failed to get all chunks");
+
+ // Chunks that are not loaded (not used) by Discord code anymore
+ const chunksLeft = allChunks.filter(id => {
+ return !(validChunks.has(id) || invalidChunks.has(id));
+ });
+
+ await Promise.all(chunksLeft.map(async id => {
+ const isWasm = await fetch(wreq.p + wreq.u(id))
+ .then(r => r.text())
+ .then(t => (IS_WEB && t.includes(".module.wasm")) || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push"));
+
+ // Loads and requires a chunk
+ if (!isWasm) {
+ await wreq.e(id as any);
+ if (wreq.m[id]) wreq(id as any);
+ }
+ }));
+
+ LazyChunkLoaderLogger.log("Finished loading all chunks!");
+ } catch (e) {
+ LazyChunkLoaderLogger.log("A fatal error occurred:", e);
+ }
+}
diff --git a/src/debug/runReporter.ts b/src/debug/runReporter.ts
index 61c9f162b9..6c7a2a03f2 100644
--- a/src/debug/runReporter.ts
+++ b/src/debug/runReporter.ts
@@ -5,171 +5,22 @@
*/
import { Logger } from "@utils/Logger";
-import { canonicalizeMatch } from "@utils/patches";
import * as Webpack from "@webpack";
-import { wreq } from "@webpack";
import { patches } from "plugins";
+import { loadLazyChunks } from "./loadLazyChunks";
+
const ReporterLogger = new Logger("Reporter");
async function runReporter() {
- ReporterLogger.log("Starting test...");
-
try {
- const validChunks = new Set();
- const invalidChunks = new Set();
- const deferredRequires = new Set();
-
- let chunksSearchingResolve: (value: void | PromiseLike) => void;
- const chunksSearchingDone = new Promise(r => chunksSearchingResolve = r);
-
- // True if resolved, false otherwise
- const chunksSearchPromises = [] as Array<() => boolean>;
-
- const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("[^)]+?"\)[^\]]*?)(?:\]\))?)\.then\(\i\.bind\(\i,"([^)]+?)"\)\)/g);
-
- async function searchAndLoadLazyChunks(factoryCode: string) {
- const lazyChunks = factoryCode.matchAll(LazyChunkRegex);
- const validChunkGroups = new Set<[chunkIds: string[], entryPoint: string]>();
-
- // Workaround for a chunk that depends on the ChannelMessage component but may be be force loaded before
- // the chunk containing the component
- const shouldForceDefer = factoryCode.includes(".Messages.GUILD_FEED_UNFEATURE_BUTTON_TEXT");
-
- await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => {
- const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => m[1]) : [];
-
- if (chunkIds.length === 0) {
- return;
- }
-
- let invalidChunkGroup = false;
+ ReporterLogger.log("Starting test...");
- for (const id of chunkIds) {
- if (wreq.u(id) == null || wreq.u(id) === "undefined.js") continue;
-
- const isWasm = await fetch(wreq.p + wreq.u(id))
- .then(r => r.text())
- .then(t => (IS_WEB && t.includes(".module.wasm")) || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push"));
-
- if (isWasm && IS_WEB) {
- invalidChunks.add(id);
- invalidChunkGroup = true;
- continue;
- }
-
- validChunks.add(id);
- }
-
- if (!invalidChunkGroup) {
- validChunkGroups.add([chunkIds, entryPoint]);
- }
- }));
-
- // Loads all found valid chunk groups
- await Promise.all(
- Array.from(validChunkGroups)
- .map(([chunkIds]) =>
- Promise.all(chunkIds.map(id => wreq.e(id as any).catch(() => { })))
- )
- );
-
- // Requires the entry points for all valid chunk groups
- for (const [, entryPoint] of validChunkGroups) {
- try {
- if (shouldForceDefer) {
- deferredRequires.add(entryPoint);
- continue;
- }
-
- if (wreq.m[entryPoint]) wreq(entryPoint as any);
- } catch (err) {
- console.error(err);
- }
- }
-
- // setImmediate to only check if all chunks were loaded after this function resolves
- // We check if all chunks were loaded every time a factory is loaded
- // If we are still looking for chunks in the other factories, the array will have that factory's chunk search promise not resolved
- // But, if all chunk search promises are resolved, this means we found every lazy chunk loaded by Discord code and manually loaded them
- setTimeout(() => {
- let allResolved = true;
-
- for (let i = 0; i < chunksSearchPromises.length; i++) {
- const isResolved = chunksSearchPromises[i]();
-
- if (isResolved) {
- // Remove finished promises to avoid having to iterate through a huge array everytime
- chunksSearchPromises.splice(i--, 1);
- } else {
- allResolved = false;
- }
- }
-
- if (allResolved) chunksSearchingResolve();
- }, 0);
- }
-
- Webpack.beforeInitListeners.add(async () => {
- ReporterLogger.log("Loading all chunks...");
-
- Webpack.factoryListeners.add(factory => {
- let isResolved = false;
- searchAndLoadLazyChunks(factory.toString()).then(() => isResolved = true);
-
- chunksSearchPromises.push(() => isResolved);
- });
-
- // 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(wreq.m[factoryId].toString()).then(() => isResolved = true);
-
- chunksSearchPromises.push(() => isResolved);
- }
- }, 0);
- });
-
- await chunksSearchingDone;
-
- // Require deferred entry points
- for (const deferredRequire of deferredRequires) {
- wreq!(deferredRequire as any);
- }
-
- // All chunks Discord has mapped to asset files, even if they are not used anymore
- const allChunks = [] as string[];
-
- // Matches "id" or id:
- for (const currentMatch of wreq!.u.toString().matchAll(/(?:"(\d+?)")|(?:(\d+?):)/g)) {
- const id = currentMatch[1] ?? currentMatch[2];
- if (id == null) continue;
-
- allChunks.push(id);
- }
-
- if (allChunks.length === 0) throw new Error("Failed to get all chunks");
-
- // Chunks that are not loaded (not used) by Discord code anymore
- const chunksLeft = allChunks.filter(id => {
- return !(validChunks.has(id) || invalidChunks.has(id));
- });
-
- await Promise.all(chunksLeft.map(async id => {
- const isWasm = await fetch(wreq.p + wreq.u(id))
- .then(r => r.text())
- .then(t => (IS_WEB && t.includes(".module.wasm")) || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push"));
-
- // Loads and requires a chunk
- if (!isWasm) {
- await wreq.e(id as any);
- if (wreq.m[id]) wreq(id as any);
- }
- }));
+ let loadLazyChunksResolve: (value: void | PromiseLike) => void;
+ const loadLazyChunksDone = new Promise(r => loadLazyChunksResolve = r);
- ReporterLogger.log("Finished loading all chunks!");
+ Webpack.beforeInitListeners.add(() => loadLazyChunks().then((loadLazyChunksResolve)));
+ await loadLazyChunksDone;
for (const patch of patches) {
if (!patch.all) {
diff --git a/src/plugins/consoleShortcuts/index.ts b/src/plugins/consoleShortcuts/index.ts
index ee86b5fcfd..0a1323e755 100644
--- a/src/plugins/consoleShortcuts/index.ts
+++ b/src/plugins/consoleShortcuts/index.ts
@@ -25,6 +25,7 @@ import definePlugin, { PluginNative, StartAt } from "@utils/types";
import * as Webpack from "@webpack";
import { extract, filters, findAll, findModuleId, search } from "@webpack";
import * as Common from "@webpack/common";
+import { loadLazyChunks } from "debug/loadLazyChunks";
import type { ComponentType } from "react";
const DESKTOP_ONLY = (f: string) => () => {
@@ -82,6 +83,7 @@ function makeShortcuts() {
wpsearch: search,
wpex: extract,
wpexs: (code: string) => extract(findModuleId(code)!),
+ loadLazyChunks: IS_DEV ? loadLazyChunks : () => { throw new Error("loadLazyChunks is dev only."); },
find,
findAll: findAll,
findByProps,
diff --git a/src/plugins/index.ts b/src/plugins/index.ts
index 53ab7983a5..32bfe7e978 100644
--- a/src/plugins/index.ts
+++ b/src/plugins/index.ts
@@ -44,7 +44,6 @@ const settings = Settings.plugins;
export function isPluginEnabled(p: string) {
return (
- IS_REPORTER ||
Plugins[p]?.required ||
Plugins[p]?.isDependency ||
settings[p]?.enabled
diff --git a/src/plugins/partyMode/index.ts b/src/plugins/partyMode/index.ts
index 56c19c02c6..c40f2e3c75 100644
--- a/src/plugins/partyMode/index.ts
+++ b/src/plugins/partyMode/index.ts
@@ -18,7 +18,7 @@
import { definePluginSettings, migratePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
-import definePlugin, { OptionType } from "@utils/types";
+import definePlugin, { OptionType, ReporterTestable } from "@utils/types";
import { FluxDispatcher } from "@webpack/common";
const enum Intensity {
@@ -46,6 +46,7 @@ export default definePlugin({
name: "PartyMode",
description: "Allows you to use party mode cause the party never ends ✨",
authors: [Devs.UwUDev],
+ reporterTestable: ReporterTestable.None,
settings,
start() {
diff --git a/src/utils/Logger.ts b/src/utils/Logger.ts
index 5296184d4b..22a3813600 100644
--- a/src/utils/Logger.ts
+++ b/src/utils/Logger.ts
@@ -32,7 +32,7 @@ export class Logger {
constructor(public name: string, public color: string = "white") { }
private _log(level: "log" | "error" | "warn" | "info" | "debug", levelColor: string, args: any[], customFmt = "") {
- if (IS_REPORTER) {
+ if (IS_REPORTER && IS_WEB) {
console[level]("[Vencord]", this.name + ":", ...args);
return;
}
From 61e1eada0174b18476d371ba5028dff199301555 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sat, 1 Jun 2024 19:38:01 -0300
Subject: [PATCH 101/125] Fix not restoring some original factories
---
src/debug/loadLazyChunks.ts | 4 +--
src/webpack/patchWebpack.ts | 49 +++++++++++++++++++++++++++----------
2 files changed, 38 insertions(+), 15 deletions(-)
diff --git a/src/debug/loadLazyChunks.ts b/src/debug/loadLazyChunks.ts
index 91946c3731..f4f7f88b3d 100644
--- a/src/debug/loadLazyChunks.ts
+++ b/src/debug/loadLazyChunks.ts
@@ -111,14 +111,14 @@ export async function loadLazyChunks() {
Webpack.factoryListeners.add(factory => {
let isResolved = false;
- searchAndLoadLazyChunks(factory.toString()).then(() => isResolved = true);
+ searchAndLoadLazyChunks(String(factory)).then(() => isResolved = true);
chunksSearchPromises.push(() => isResolved);
});
for (const factoryId in wreq.m) {
let isResolved = false;
- searchAndLoadLazyChunks(wreq.m[factoryId].toString()).then(() => isResolved = true);
+ searchAndLoadLazyChunks(String(wreq.m[factoryId])).then(() => isResolved = true);
chunksSearchPromises.push(() => isResolved);
}
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index bf4efa5f17..c147d1ad05 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -88,6 +88,10 @@ define(Function.prototype, "O", {
// Patch the pre-populated factories
for (const id in this.m) {
+ if (updateExistingFactory(this.m, id, this.m[id])) {
+ continue;
+ }
+
defineModulesFactoryGetter(id, Settings.eagerPatches ? patchFactory(id, this.m[id]) : this.m[id]);
}
@@ -140,6 +144,35 @@ function defineModulesFactoryGetter(id: PropertyKey, factory: PatchedModuleFacto
}
}
+/**
+ * Update a factory that exists in any Webpack instance with a new original factory.
+ *
+ * @target The module factories where this new original factory is being set
+ * @param id The id of the module
+ * @param newFactory The new original factory
+ * @returns Whether the original factory was updated, or false if it doesn't exist in any Webpack instance
+ */
+function updateExistingFactory(target: AnyWebpackRequire["m"], id: PropertyKey, newFactory: ModuleFactory) {
+ let existingFactory: TypedPropertyDescriptor | undefined;
+ for (const wreq of allWebpackInstances) {
+ if (Reflect.getOwnPropertyDescriptor(wreq.m, id) != null) {
+ existingFactory = Reflect.getOwnPropertyDescriptor(wreq.m, id);
+ break;
+ }
+ }
+
+ if (existingFactory != null) {
+ // If existingFactory exists in any Webpack instance, its either wrapped in defineModuleFactoryGetter, or it has already been required.
+ // So define the descriptor of it on this current Webpack instance, call Reflect.set with the new original,
+ // and let the correct logic apply (normal set, or defineModuleFactoryGetter setter)
+
+ Reflect.defineProperty(target, id, existingFactory);
+ return Reflect.set(target, id, newFactory, target);
+ }
+
+ return false;
+}
+
const moduleFactoriesHandler: ProxyHandler = {
/*
If Discord ever decides to set module factories using the variable of the modules object directly instead of wreq.m, we need to switch the proxy to the prototype
@@ -163,26 +196,16 @@ const moduleFactoriesHandler: ProxyHandler = {
return define(target, p, { value: newValue });
}
- const existingFactory = Reflect.get(target, p, receiver);
+ if (updateExistingFactory(target, p, newValue)) {
+ return true;
+ }
if (!Settings.eagerPatches) {
- // If existingFactory exists, its either wrapped in defineModuleFactoryGetter, or it has already been required
- // so call Reflect.set with the new original and let the correct logic apply (normal set, or defineModuleFactoryGetter setter)
- if (existingFactory != null) {
- return Reflect.set(target, p, newValue, receiver);
- }
-
// eagerPatches are disabled, so the factory argument should be the original
defineModulesFactoryGetter(p, newValue);
return true;
}
- // Check if this factory is already patched
- if (existingFactory?.$$vencordOriginal != null) {
- existingFactory.$$vencordOriginal = newValue;
- return true;
- }
-
const patchedFactory = patchFactory(p, newValue);
// If multiple Webpack instances exist, when new a new module is loaded, it will be set in all the module factories objects.
From 4e14232b5aab7a1b5ed84c1ce8ea74ae1c8643e1 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sat, 1 Jun 2024 19:52:17 -0300
Subject: [PATCH 102/125] Fix patching pre-populated factories
---
src/webpack/patchWebpack.ts | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index c147d1ad05..a17a2e5dd0 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -88,7 +88,7 @@ define(Function.prototype, "O", {
// Patch the pre-populated factories
for (const id in this.m) {
- if (updateExistingFactory(this.m, id, this.m[id])) {
+ if (updateExistingFactory(this.m, id, this.m[id], true)) {
continue;
}
@@ -150,11 +150,14 @@ function defineModulesFactoryGetter(id: PropertyKey, factory: PatchedModuleFacto
* @target The module factories where this new original factory is being set
* @param id The id of the module
* @param newFactory The new original factory
+ * @param ignoreExistingInTarget Whether to ignore checking if the factory already exists in the moduleFactoriesTarget
* @returns Whether the original factory was updated, or false if it doesn't exist in any Webpack instance
*/
-function updateExistingFactory(target: AnyWebpackRequire["m"], id: PropertyKey, newFactory: ModuleFactory) {
+function updateExistingFactory(moduleFactoriesTarget: AnyWebpackRequire["m"], id: PropertyKey, newFactory: ModuleFactory, ignoreExistingInTarget: boolean = false) {
let existingFactory: TypedPropertyDescriptor | undefined;
for (const wreq of allWebpackInstances) {
+ if (ignoreExistingInTarget && wreq.m === moduleFactoriesTarget) continue;
+
if (Reflect.getOwnPropertyDescriptor(wreq.m, id) != null) {
existingFactory = Reflect.getOwnPropertyDescriptor(wreq.m, id);
break;
@@ -166,8 +169,8 @@ function updateExistingFactory(target: AnyWebpackRequire["m"], id: PropertyKey,
// So define the descriptor of it on this current Webpack instance, call Reflect.set with the new original,
// and let the correct logic apply (normal set, or defineModuleFactoryGetter setter)
- Reflect.defineProperty(target, id, existingFactory);
- return Reflect.set(target, id, newFactory, target);
+ Reflect.defineProperty(moduleFactoriesTarget, id, existingFactory);
+ return Reflect.set(moduleFactoriesTarget, id, newFactory, moduleFactoriesTarget);
}
return false;
From 6e6ee4db689808c380662de17f5ae9ed0d28036e Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sat, 1 Jun 2024 23:39:58 -0300
Subject: [PATCH 103/125] NoPendingCount: Fix for message requests
---
src/plugins/noPendingCount/index.ts | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/src/plugins/noPendingCount/index.ts b/src/plugins/noPendingCount/index.ts
index 29458df9d6..57a65f52cc 100644
--- a/src/plugins/noPendingCount/index.ts
+++ b/src/plugins/noPendingCount/index.ts
@@ -62,6 +62,16 @@ export default definePlugin({
replace: "return 0;"
}
},
+ // New message requests hook
+ {
+ find: "useNewMessageRequestsCount:",
+ predicate: () => settings.store.hideMessageRequestsCount,
+ replacement: {
+ match: /getNonChannelAckId\(\i\.\i\.MESSAGE_REQUESTS\).+?return /,
+ replace: "$&0;"
+ }
+ },
+ // Old message requests hook
{
find: "getMessageRequestsCount(){",
predicate: () => settings.store.hideMessageRequestsCount,
From 53dd86fa6e8cd8ad8832bf1fd53397a8527289a0 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sun, 2 Jun 2024 18:46:03 -0300
Subject: [PATCH 104/125] Improve typings and other stuff
---
src/debug/loadLazyChunks.ts | 16 +++++++++-------
src/webpack/patchWebpack.ts | 24 ++++++++----------------
src/webpack/webpack.ts | 4 ++--
src/webpack/wreq.d.ts | 18 +++++++++++++++++-
4 files changed, 36 insertions(+), 26 deletions(-)
diff --git a/src/debug/loadLazyChunks.ts b/src/debug/loadLazyChunks.ts
index f4f7f88b3d..4aea9ff5d8 100644
--- a/src/debug/loadLazyChunks.ts
+++ b/src/debug/loadLazyChunks.ts
@@ -8,6 +8,7 @@ import { Logger } from "@utils/Logger";
import { canonicalizeMatch } from "@utils/patches";
import * as Webpack from "@webpack";
import { wreq } from "@webpack";
+import { AnyModuleFactory, ModuleFactory } from "webpack";
const LazyChunkLoaderLogger = new Logger("LazyChunkLoader");
@@ -109,21 +110,22 @@ export async function loadLazyChunks() {
}, 0);
}
- Webpack.factoryListeners.add(factory => {
+ function factoryListener(factory: AnyModuleFactory | ModuleFactory) {
let isResolved = false;
- searchAndLoadLazyChunks(String(factory)).then(() => isResolved = true);
+ searchAndLoadLazyChunks(String(factory))
+ .then(() => isResolved = true)
+ .catch(() => isResolved = true);
chunksSearchPromises.push(() => isResolved);
- });
+ }
+ Webpack.factoryListeners.add(factoryListener);
for (const factoryId in wreq.m) {
- let isResolved = false;
- searchAndLoadLazyChunks(String(wreq.m[factoryId])).then(() => isResolved = true);
-
- chunksSearchPromises.push(() => isResolved);
+ factoryListener(wreq.m[factoryId]);
}
await chunksSearchingDone;
+ Webpack.factoryListeners.delete(factoryListener);
// Require deferred entry points
for (const deferredRequire of deferredRequires) {
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index a17a2e5dd0..7ac7cac001 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -12,15 +12,7 @@ import { PatchReplacement } from "@utils/types";
import { traceFunction } from "../debug/Tracer";
import { patches } from "../plugins";
-import { _initWebpack, factoryListeners, ModuleFactory, moduleListeners, subscriptions, WebpackRequire, wreq } from ".";
-
-type AnyWebpackRequire = Partial & Pick;
-
-type PatchedModuleFactory = ModuleFactory & {
- $$vencordOriginal?: ModuleFactory;
-};
-
-type PatchedModuleFactories = Record;
+import { _initWebpack, AnyModuleFactory, AnyWebpackRequire, factoryListeners, moduleListeners, PatchedModuleFactories, PatchedModuleFactory, subscriptions, WebpackRequire, wreq } from ".";
const logger = new Logger("WebpackInterceptor", "#8caaee");
@@ -56,7 +48,7 @@ const define: Define = (target, p, attributes) => {
define(Function.prototype, "O", {
enumerable: false,
- set(this: WebpackRequire, onChunksLoaded: WebpackRequire["O"]) {
+ set(this: AnyWebpackRequire, onChunksLoaded: AnyWebpackRequire["O"]) {
define(this, "O", { value: onChunksLoaded });
const { stack } = new Error();
@@ -104,7 +96,7 @@ define(Function.prototype, "O", {
const proxiedModuleFactories = new Proxy(this.m, 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: Reflect.setPrototypeOf(this.m, new Proxy(this.m, moduleFactoriesHandler)) });
*/
define(this, "m", { value: proxiedModuleFactories });
@@ -133,7 +125,7 @@ function defineModulesFactoryGetter(id: PropertyKey, factory: PatchedModuleFacto
return (factory = patchFactory(id, factory));
},
- set(v: ModuleFactory) {
+ set(v: AnyModuleFactory) {
if (factory.$$vencordOriginal != null) {
factory.$$vencordOriginal = v;
} else {
@@ -153,7 +145,7 @@ function defineModulesFactoryGetter(id: PropertyKey, factory: PatchedModuleFacto
* @param ignoreExistingInTarget Whether to ignore checking if the factory already exists in the moduleFactoriesTarget
* @returns Whether the original factory was updated, or false if it doesn't exist in any Webpack instance
*/
-function updateExistingFactory(moduleFactoriesTarget: AnyWebpackRequire["m"], id: PropertyKey, newFactory: ModuleFactory, ignoreExistingInTarget: boolean = false) {
+function updateExistingFactory(moduleFactoriesTarget: AnyWebpackRequire["m"], id: PropertyKey, newFactory: AnyModuleFactory, ignoreExistingInTarget: boolean = false) {
let existingFactory: TypedPropertyDescriptor | undefined;
for (const wreq of allWebpackInstances) {
if (ignoreExistingInTarget && wreq.m === moduleFactoriesTarget) continue;
@@ -228,7 +220,7 @@ const moduleFactoriesHandler: ProxyHandler = {
* @param factory The original or patched module factory
* @returns The wrapper for the patched module factory
*/
-function patchFactory(id: PropertyKey, factory: ModuleFactory) {
+function patchFactory(id: PropertyKey, factory: AnyModuleFactory) {
const originalFactory = factory;
for (const factoryListener of factoryListeners) {
@@ -347,7 +339,7 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
// The patched factory wrapper, define it in an object to preserve the name after minification
const patchedFactory: PatchedModuleFactory = {
- PatchedFactory(...args: Parameters) {
+ PatchedFactory(...args: Parameters) {
// 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
for (const wreq of allWebpackInstances) {
@@ -370,7 +362,7 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
`id: ${String(id)}` + interpolateIfDefined`, WebpackInstance origin: ${webpackInstanceFileName}` +
")"
);
- _initWebpack(require);
+ _initWebpack(require as WebpackRequire);
} else if (IS_DEV) {
logger.error("WebpackRequire was not initialized, running modules without patches instead.");
}
diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts
index f318558702..5d09c3b0f4 100644
--- a/src/webpack/webpack.ts
+++ b/src/webpack/webpack.ts
@@ -22,7 +22,7 @@ import { Logger } from "@utils/Logger";
import { canonicalizeMatch } from "@utils/patches";
import { traceFunction } from "../debug/Tracer";
-import { ModuleExports, ModuleFactory, WebpackRequire } from "./wreq";
+import { AnyModuleFactory, ModuleExports, ModuleFactory, WebpackRequire } from "./wreq";
const logger = new Logger("Webpack");
@@ -72,7 +72,7 @@ export type CallbackFn = (module: ModuleExports, id: PropertyKey) => void;
export const subscriptions = new Map