Compare commits
17 commits
main
...
patcher-re
Author | SHA1 | Date | |
---|---|---|---|
|
fb6aac19ea | ||
|
f067cefd18 | ||
|
8cd8334ada | ||
|
e3a378936e | ||
|
53010b0a8a | ||
|
7a85742850 | ||
|
eb5a3b5308 | ||
|
8602ec2611 | ||
|
172c372e40 | ||
|
f4c8efe4de | ||
|
4716b829c9 | ||
|
494ddb8f19 | ||
|
af5b5a0da3 | ||
|
4df87467a4 | ||
|
1997d613bb | ||
|
973ab989e0 | ||
|
35a6a027fa |
18 changed files with 805 additions and 345 deletions
|
@ -312,7 +312,7 @@ export const commonOpts = {
|
|||
logLevel: "info",
|
||||
bundle: true,
|
||||
watch,
|
||||
minify: !watch,
|
||||
minify: !watch && !IS_REPORTER,
|
||||
sourcemap: watch ? "inline" : "",
|
||||
legalComments: "linked",
|
||||
banner,
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
import { readFileSync } from "fs";
|
||||
import pup, { JSHandle } from "puppeteer-core";
|
||||
|
||||
for (const variable of ["DISCORD_TOKEN", "CHROMIUM_BIN"]) {
|
||||
for (const variable of ["CHROMIUM_BIN"]) {
|
||||
if (!process.env[variable]) {
|
||||
console.error(`Missing environment variable ${variable}`);
|
||||
process.exit(1);
|
||||
|
@ -215,7 +215,7 @@ page.on("console", async e => {
|
|||
|
||||
switch (tag) {
|
||||
case "WebpackInterceptor:":
|
||||
const patchFailMatch = message.match(/Patch by (.+?) (had no effect|errored|found no module) \(Module id is (.+?)\): (.+)/)!;
|
||||
const patchFailMatch = message.match(/Patch by (.+?) (had no effect|errored|found no module|took [\d.]+?ms) \(Module id is (.+?)\): (.+)/)!;
|
||||
if (!patchFailMatch) break;
|
||||
|
||||
console.error(await getText());
|
||||
|
@ -226,7 +226,7 @@ page.on("console", async e => {
|
|||
plugin,
|
||||
type,
|
||||
id,
|
||||
match: regex.replace(/\(\?:\[A-Za-z_\$\]\[\\w\$\]\*\)/g, "\\i"),
|
||||
match: regex,
|
||||
error: await maybeGetError(e.args()[3])
|
||||
});
|
||||
|
||||
|
@ -292,7 +292,7 @@ page.on("error", e => console.error("[Error]", e.message));
|
|||
page.on("pageerror", e => {
|
||||
if (e.message.includes("Sentry successfully disabled")) return;
|
||||
|
||||
if (!e.message.startsWith("Object") && !e.message.includes("Cannot find module")) {
|
||||
if (!e.message.startsWith("Object") && !e.message.includes("Cannot find module") && !/^.{1,2}$/.test(e.message)) {
|
||||
console.error("[Page Error]", e.message);
|
||||
report.otherErrors.push(e.message);
|
||||
} else {
|
||||
|
@ -300,20 +300,9 @@ page.on("pageerror", e => {
|
|||
}
|
||||
});
|
||||
|
||||
async function reporterRuntime(token: string) {
|
||||
Vencord.Webpack.waitFor(
|
||||
"loginToken",
|
||||
m => {
|
||||
console.log("[PUP_DEBUG]", "Logging in with token...");
|
||||
m.loginToken(token);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
await page.evaluateOnNewDocument(`
|
||||
if (location.host.endsWith("discord.com")) {
|
||||
${readFileSync("./dist/browser.js", "utf-8")};
|
||||
(${reporterRuntime.toString()})(${JSON.stringify(process.env.DISCORD_TOKEN)});
|
||||
}
|
||||
`);
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ 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";
|
||||
|
|
|
@ -32,9 +32,10 @@ export interface Settings {
|
|||
autoUpdate: boolean;
|
||||
autoUpdateNotification: boolean,
|
||||
useQuickCss: boolean;
|
||||
eagerPatches: boolean;
|
||||
enabledThemes: string[];
|
||||
enableReactDevtools: boolean;
|
||||
themeLinks: string[];
|
||||
enabledThemes: string[];
|
||||
frameless: boolean;
|
||||
transparent: boolean;
|
||||
winCtrlQ: boolean;
|
||||
|
@ -81,6 +82,7 @@ const DefaultSettings: Settings = {
|
|||
autoUpdateNotification: true,
|
||||
useQuickCss: true,
|
||||
themeLinks: [],
|
||||
eagerPatches: IS_REPORTER,
|
||||
enabledThemes: [],
|
||||
enableReactDevtools: false,
|
||||
frameless: false,
|
||||
|
|
|
@ -23,35 +23,61 @@ if (IS_DEV || IS_REPORTER) {
|
|||
var logger = new Logger("Tracer", "#FFD166");
|
||||
}
|
||||
|
||||
const noop = function () { };
|
||||
|
||||
export const beginTrace = !(IS_DEV || IS_REPORTER) ? noop :
|
||||
export const beginTrace = !(IS_DEV || IS_REPORTER) ? () => { } :
|
||||
function beginTrace(name: string, ...args: any[]) {
|
||||
if (name in traces)
|
||||
if (name in traces) {
|
||||
throw new Error(`Trace ${name} already exists!`);
|
||||
}
|
||||
|
||||
traces[name] = [performance.now(), args];
|
||||
};
|
||||
|
||||
export const finishTrace = !(IS_DEV || IS_REPORTER) ? noop : function finishTrace(name: string) {
|
||||
const end = performance.now();
|
||||
export const finishTrace = !(IS_DEV || IS_REPORTER) ? () => 0 :
|
||||
function finishTrace(name: string) {
|
||||
const end = performance.now();
|
||||
|
||||
const [start, args] = traces[name];
|
||||
delete traces[name];
|
||||
const [start, args] = traces[name];
|
||||
delete traces[name];
|
||||
|
||||
logger.debug(`${name} took ${end - start}ms`, args);
|
||||
};
|
||||
const totalTime = end - start;
|
||||
logger.debug(`${name} took ${totalTime}ms`, args);
|
||||
|
||||
return totalTime;
|
||||
};
|
||||
|
||||
type Func = (...args: any[]) => any;
|
||||
type TraceNameMapper<F extends Func> = (...args: Parameters<F>) => string;
|
||||
|
||||
const noopTracer =
|
||||
<F extends Func>(name: string, f: F, mapper?: TraceNameMapper<F>) => f;
|
||||
function noopTracerWithResults<F extends Func>(name: string, f: F, mapper?: TraceNameMapper<F>) {
|
||||
return function (this: unknown, ...args: Parameters<F>): [ReturnType<F>, number] {
|
||||
return [f.apply(this, args), 0];
|
||||
};
|
||||
}
|
||||
|
||||
function noopTracer<F extends Func>(name: string, f: F, mapper?: TraceNameMapper<F>) {
|
||||
return f;
|
||||
}
|
||||
|
||||
export const traceFunctionWithResults = !(IS_DEV || IS_REPORTER)
|
||||
? noopTracerWithResults
|
||||
: function traceFunctionWithResults<F extends Func>(name: string, f: F, mapper?: TraceNameMapper<F>): (this: unknown, ...args: Parameters<F>) => [ReturnType<F>, number] {
|
||||
return function (this: unknown, ...args: Parameters<F>) {
|
||||
const traceName = mapper?.(...args) ?? name;
|
||||
|
||||
beginTrace(traceName, ...arguments);
|
||||
try {
|
||||
return [f.apply(this, args), finishTrace(traceName)];
|
||||
} catch (e) {
|
||||
finishTrace(traceName);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const traceFunction = !(IS_DEV || IS_REPORTER)
|
||||
? noopTracer
|
||||
: function traceFunction<F extends Func>(name: string, f: F, mapper?: TraceNameMapper<F>): F {
|
||||
return function (this: any, ...args: Parameters<F>) {
|
||||
return function (this: unknown, ...args: Parameters<F>) {
|
||||
const traceName = mapper?.(...args) ?? name;
|
||||
|
||||
beginTrace(traceName, ...arguments);
|
||||
|
|
|
@ -8,10 +8,11 @@ import { Logger } from "@utils/Logger";
|
|||
import { canonicalizeMatch } from "@utils/patches";
|
||||
import * as Webpack from "@webpack";
|
||||
import { wreq } from "@webpack";
|
||||
|
||||
const LazyChunkLoaderLogger = new Logger("LazyChunkLoader");
|
||||
import { AnyModuleFactory, ModuleFactory } from "webpack";
|
||||
|
||||
export async function loadLazyChunks() {
|
||||
const LazyChunkLoaderLogger = new Logger("LazyChunkLoader");
|
||||
|
||||
try {
|
||||
LazyChunkLoaderLogger.log("Loading all chunks...");
|
||||
|
||||
|
@ -25,6 +26,9 @@ export async function loadLazyChunks() {
|
|||
// True if resolved, false otherwise
|
||||
const chunksSearchPromises = [] as Array<() => boolean>;
|
||||
|
||||
/* This regex loads all language packs which makes webpack finds testing extremely slow, so for now, lets use one which doesnt include those
|
||||
const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("?[^)]+?"?\)[^\]]*?)(?:\]\))?)\.then\(\i(?:\.\i)?\.bind\(\i,"?([^)]+?)"?(?:,[^)]+?)?\)\)/g);
|
||||
*/
|
||||
const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("?[^)]+?"?\)[^\]]*?)(?:\]\))?)\.then\(\i\.bind\(\i,"?([^)]+?)"?\)\)/g);
|
||||
|
||||
let foundCssDebuggingLoad = false;
|
||||
|
@ -82,7 +86,7 @@ export async function loadLazyChunks() {
|
|||
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)))
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -94,7 +98,7 @@ export async function loadLazyChunks() {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (wreq.m[entryPoint]) wreq(entryPoint as any);
|
||||
if (wreq.m[entryPoint]) wreq(entryPoint);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
@ -122,32 +126,33 @@ export async function loadLazyChunks() {
|
|||
}, 0);
|
||||
}
|
||||
|
||||
Webpack.factoryListeners.add(factory => {
|
||||
function factoryListener(factory: AnyModuleFactory | ModuleFactory) {
|
||||
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);
|
||||
searchAndLoadLazyChunks(String(factory))
|
||||
.then(() => isResolved = true)
|
||||
.catch(() => isResolved = true);
|
||||
|
||||
chunksSearchPromises.push(() => isResolved);
|
||||
}
|
||||
|
||||
Webpack.factoryListeners.add(factoryListener);
|
||||
for (const factoryId in wreq.m) {
|
||||
factoryListener(wreq.m[factoryId]);
|
||||
}
|
||||
|
||||
await chunksSearchingDone;
|
||||
Webpack.factoryListeners.delete(factoryListener);
|
||||
|
||||
// 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
|
||||
const allChunks = [] as number[];
|
||||
|
||||
// Matches "id" or id:
|
||||
for (const currentMatch of wreq!.u.toString().matchAll(/(?:"([\deE]+?)"(?![,}]))|(?:([\deE]+?):)/g)) {
|
||||
for (const currentMatch of String(wreq.u).matchAll(/(?:"([\deE]+?)"(?![,}]))|(?:([\deE]+?):)/g)) {
|
||||
const id = currentMatch[1] ?? currentMatch[2];
|
||||
if (id == null) continue;
|
||||
|
||||
|
@ -156,7 +161,8 @@ export async function loadLazyChunks() {
|
|||
|
||||
if (allChunks.length === 0) throw new Error("Failed to get all chunks");
|
||||
|
||||
// Chunks that are not loaded (not used) by Discord code anymore
|
||||
// Chunks which our regex could not catch to load
|
||||
// It will always contain WebWorker assets, and also currently contains some language packs which are loaded differently
|
||||
const chunksLeft = allChunks.filter(id => {
|
||||
return !(validChunks.has(id) || invalidChunks.has(id));
|
||||
});
|
||||
|
@ -166,12 +172,9 @@ export async function loadLazyChunks() {
|
|||
.then(r => r.text())
|
||||
.then(t => t.includes("importScripts("));
|
||||
|
||||
// Loads and requires a chunk
|
||||
// Loads the chunk. Currently this only happens with the language packs which are loaded differently
|
||||
if (!isWorkerAsset) {
|
||||
await wreq.e(id as any);
|
||||
// Technically, the id of the chunk does not match the entry point
|
||||
// But, still try it because we have no way to get the actual entry point
|
||||
if (wreq.m[id]) wreq(id as any);
|
||||
await wreq.e(id);
|
||||
}
|
||||
}));
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { Logger } from "@utils/Logger";
|
||||
import * as Webpack from "@webpack";
|
||||
import { patches } from "plugins";
|
||||
import { addPatch, patches } from "plugins";
|
||||
|
||||
import { loadLazyChunks } from "./loadLazyChunks";
|
||||
|
||||
|
@ -16,10 +16,25 @@ async function runReporter() {
|
|||
try {
|
||||
ReporterLogger.log("Starting test...");
|
||||
|
||||
let loadLazyChunksResolve: (value: void | PromiseLike<void>) => void;
|
||||
let loadLazyChunksResolve: (value: void) => void;
|
||||
const loadLazyChunksDone = new Promise<void>(r => loadLazyChunksResolve = r);
|
||||
|
||||
Webpack.beforeInitListeners.add(() => loadLazyChunks().then((loadLazyChunksResolve)));
|
||||
// The main patch for starting the reporter chunk loading
|
||||
addPatch({
|
||||
find: '"Could not find app-mount"',
|
||||
replacement: {
|
||||
match: /(?<="use strict";)/,
|
||||
replace: "Vencord.Webpack._initReporter();"
|
||||
}
|
||||
}, "Vencord Reporter");
|
||||
|
||||
// @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(() => loadLazyChunks().then(loadLazyChunksResolve), 0);
|
||||
};
|
||||
|
||||
await loadLazyChunksDone;
|
||||
|
||||
for (const patch of patches) {
|
||||
|
@ -28,6 +43,12 @@ async function runReporter() {
|
|||
}
|
||||
}
|
||||
|
||||
for (const [plugin, moduleId, match, totalTime] of Vencord.WebpackPatcher.patchTimings) {
|
||||
if (totalTime > 3) {
|
||||
new Logger("WebpackInterceptor").warn(`Patch by ${plugin} took ${Math.round(totalTime * 100) / 100}ms (Module id is ${String(moduleId)}): ${match}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [searchType, args] of Webpack.lazyWebpackSearchHistory) {
|
||||
let method = searchType;
|
||||
|
||||
|
@ -88,4 +109,6 @@ async function runReporter() {
|
|||
}
|
||||
}
|
||||
|
||||
runReporter();
|
||||
// Run after the Vencord object has been created.
|
||||
// We need to add extra properties to it, and it is only created after all of Vencord code has ran
|
||||
setTimeout(runReporter, 0);
|
||||
|
|
|
@ -20,6 +20,7 @@ import { definePluginSettings } from "@api/Settings";
|
|||
import { Devs } from "@utils/constants";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import definePlugin, { OptionType, StartAt } from "@utils/types";
|
||||
import { WebpackRequire } from "webpack";
|
||||
|
||||
const settings = definePluginSettings({
|
||||
disableAnalytics: {
|
||||
|
@ -81,9 +82,9 @@ export default definePlugin({
|
|||
Object.defineProperty(Function.prototype, "g", {
|
||||
configurable: true,
|
||||
|
||||
set(v: any) {
|
||||
set(this: WebpackRequire, globalObj: WebpackRequire["g"]) {
|
||||
Object.defineProperty(this, "g", {
|
||||
value: v,
|
||||
value: globalObj,
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
writable: true
|
||||
|
@ -92,11 +93,11 @@ export default definePlugin({
|
|||
// Ensure this is most likely the Sentry WebpackInstance.
|
||||
// Function.g is a very generic property and is not uncommon for another WebpackInstance (or even a React component: <g></g>) to include it
|
||||
const { stack } = new Error();
|
||||
if (!(stack?.includes("discord.com") || stack?.includes("discordapp.com")) || !String(this).includes("exports:{}") || this.c != null) {
|
||||
if (this.c != null || !stack?.includes("http") || !String(this).includes("exports:{}")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const assetPath = stack?.match(/\/assets\/.+?\.js/)?.[0];
|
||||
const assetPath = stack.match(/http.+?(?=:\d+?:\d+?$)/m)?.[0];
|
||||
if (!assetPath) {
|
||||
return;
|
||||
}
|
||||
|
@ -106,7 +107,8 @@ export default definePlugin({
|
|||
srcRequest.send();
|
||||
|
||||
// Final condition to see if this is the Sentry WebpackInstance
|
||||
if (!srcRequest.responseText.includes("window.DiscordSentry=")) {
|
||||
// This is matching window.DiscordSentry=, but without `window` to avoid issues on some proxies
|
||||
if (!srcRequest.responseText.includes(".DiscordSentry=")) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 = String(mod.original ?? mod).replaceAll("\n", "");
|
||||
let src = String(mod).replaceAll("\n", "");
|
||||
|
||||
if (src.startsWith("function(")) {
|
||||
src = "0," + src;
|
||||
|
|
|
@ -27,7 +27,7 @@ import { addMessageClickListener, addMessagePreEditListener, addMessagePreSendLi
|
|||
import { addMessagePopoverButton, removeMessagePopoverButton } from "@api/MessagePopover";
|
||||
import { Settings, SettingsStore } from "@api/Settings";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { canonicalizeFind } from "@utils/patches";
|
||||
import { canonicalizeFind, canonicalizeReplacement } from "@utils/patches";
|
||||
import { Patch, Plugin, PluginDef, ReporterTestable, StartAt } from "@utils/types";
|
||||
import { FluxDispatcher } from "@webpack/common";
|
||||
import { FluxEvents } from "@webpack/types";
|
||||
|
@ -57,7 +57,7 @@ export function isPluginEnabled(p: string) {
|
|||
) ?? false;
|
||||
}
|
||||
|
||||
export function addPatch(newPatch: Omit<Patch, "plugin">, pluginName: string) {
|
||||
export function addPatch(newPatch: Omit<Patch, "plugin">, pluginName: string, pluginPath = `Vencord.Plugins.plugins[${JSON.stringify(pluginName)}]`) {
|
||||
const patch = newPatch as Patch;
|
||||
patch.plugin = pluginName;
|
||||
|
||||
|
@ -73,10 +73,12 @@ export function addPatch(newPatch: Omit<Patch, "plugin">, pluginName: string) {
|
|||
patch.replacement = [patch.replacement];
|
||||
}
|
||||
|
||||
if (IS_REPORTER) {
|
||||
patch.replacement.forEach(r => {
|
||||
delete r.predicate;
|
||||
});
|
||||
for (const replacement of patch.replacement) {
|
||||
canonicalizeReplacement(replacement, pluginPath);
|
||||
|
||||
if (IS_REPORTER) {
|
||||
delete replacement.predicate;
|
||||
}
|
||||
}
|
||||
|
||||
patch.replacement = patch.replacement.filter(({ predicate }) => !predicate || predicate());
|
||||
|
|
|
@ -100,6 +100,11 @@ export function pluralise(amount: number, singular: string, plural = singular +
|
|||
return amount === 1 ? `${amount} ${singular}` : `${amount} ${plural}`;
|
||||
}
|
||||
|
||||
export function interpolateIfDefined(strings: TemplateStringsArray, ...args: any[]) {
|
||||
if (args.some(arg => arg == null)) return "";
|
||||
return strings.reduce((acc, str, i) => `${acc}${str}${args[i] ?? ""}`, "");
|
||||
}
|
||||
|
||||
export function tryOrElse<T>(func: () => T, fallback: T): T {
|
||||
try {
|
||||
const res = func();
|
||||
|
|
|
@ -41,16 +41,17 @@ export function canonicalizeMatch<T extends RegExp | string>(match: T): T {
|
|||
}
|
||||
|
||||
const canonSource = partialCanon.replaceAll("\\i", String.raw`(?:[A-Za-z_$][\w$]*)`);
|
||||
return new RegExp(canonSource, match.flags) as T;
|
||||
const canonRegex = new RegExp(canonSource, match.flags);
|
||||
canonRegex.toString = match.toString.bind(match);
|
||||
|
||||
return canonRegex as T;
|
||||
}
|
||||
|
||||
export function canonicalizeReplace<T extends string | ReplaceFn>(replace: T, pluginName: string): T {
|
||||
const self = `Vencord.Plugins.plugins[${JSON.stringify(pluginName)}]`;
|
||||
|
||||
export function canonicalizeReplace<T extends string | ReplaceFn>(replace: T, pluginPath: string): T {
|
||||
if (typeof replace !== "function")
|
||||
return replace.replaceAll("$self", self) as T;
|
||||
return replace.replaceAll("$self", pluginPath) as T;
|
||||
|
||||
return ((...args) => replace(...args).replaceAll("$self", self)) as T;
|
||||
return ((...args) => replace(...args).replaceAll("$self", pluginPath)) as T;
|
||||
}
|
||||
|
||||
export function canonicalizeDescriptor<T>(descriptor: TypedPropertyDescriptor<T>, canonicalize: (value: T) => T) {
|
||||
|
@ -65,12 +66,12 @@ export function canonicalizeDescriptor<T>(descriptor: TypedPropertyDescriptor<T>
|
|||
return descriptor;
|
||||
}
|
||||
|
||||
export function canonicalizeReplacement(replacement: Pick<PatchReplacement, "match" | "replace">, plugin: string) {
|
||||
export function canonicalizeReplacement(replacement: Pick<PatchReplacement, "match" | "replace">, pluginPath: string) {
|
||||
const descriptors = Object.getOwnPropertyDescriptors(replacement);
|
||||
descriptors.match = canonicalizeDescriptor(descriptors.match, canonicalizeMatch);
|
||||
descriptors.replace = canonicalizeDescriptor(
|
||||
descriptors.replace,
|
||||
replace => canonicalizeReplace(replace, plugin),
|
||||
replace => canonicalizeReplace(replace, pluginPath),
|
||||
);
|
||||
Object.defineProperties(replacement, descriptors);
|
||||
}
|
||||
|
|
|
@ -43,6 +43,10 @@ export interface PatchReplacement {
|
|||
replace: string | ReplaceFn;
|
||||
/** A function which returns whether this patch replacement should be applied */
|
||||
predicate?(): boolean;
|
||||
/** The minimum build number for this patch to be applied */
|
||||
fromBuild?: number;
|
||||
/** The maximum build number for this patch to be applied */
|
||||
toBuild?: number;
|
||||
}
|
||||
|
||||
export interface Patch {
|
||||
|
@ -59,6 +63,10 @@ export interface Patch {
|
|||
group?: boolean;
|
||||
/** A function which returns whether this patch should be applied */
|
||||
predicate?(): boolean;
|
||||
/** The minimum build number for this patch to be applied */
|
||||
fromBuild?: number;
|
||||
/** The maximum build number for this patch to be applied */
|
||||
toBuild?: number;
|
||||
}
|
||||
|
||||
export interface PluginAuthor {
|
||||
|
|
|
@ -25,7 +25,7 @@ export const Menu = {} as t.Menu;
|
|||
// Relies on .name properties added by the MenuItemDemanglerAPI
|
||||
waitFor(m => m.name === "MenuCheckboxItem", (_, id) => {
|
||||
// we have to do this manual require by ID because m is in this case the MenuCheckBoxItem instead of the entire module
|
||||
const module = wreq(id as any);
|
||||
const module = wreq(id);
|
||||
|
||||
for (const e of Object.values(module)) {
|
||||
if (typeof e === "function" && e.name.startsWith("Menu")) {
|
||||
|
|
|
@ -18,3 +18,4 @@
|
|||
|
||||
export * as Common from "./common";
|
||||
export * from "./webpack";
|
||||
export * from "./wreq.d";
|
||||
|
|
|
@ -1,353 +1,536 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated, Nuckyz, and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { WEBPACK_CHUNK } from "@utils/constants";
|
||||
import { Settings } from "@api/Settings";
|
||||
import { makeLazy } from "@utils/lazy";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { canonicalizeReplacement } from "@utils/patches";
|
||||
import { interpolateIfDefined } from "@utils/misc";
|
||||
import { PatchReplacement } from "@utils/types";
|
||||
import { WebpackInstance } from "discord-types/other";
|
||||
|
||||
import { traceFunction } from "../debug/Tracer";
|
||||
import { traceFunctionWithResults } from "../debug/Tracer";
|
||||
import { patches } from "../plugins";
|
||||
import { _initWebpack, _shouldIgnoreModule, beforeInitListeners, factoryListeners, moduleListeners, subscriptions, wreq } from ".";
|
||||
import { _initWebpack, _shouldIgnoreModule, AnyModuleFactory, AnyWebpackRequire, factoryListeners, findModuleId, ModuleExports, moduleListeners, waitForSubscriptions, WebpackRequire, WrappedModuleFactory, wreq } from ".";
|
||||
|
||||
const logger = new Logger("WebpackInterceptor", "#8caaee");
|
||||
|
||||
let webpackChunk: any[];
|
||||
/** A set with all the Webpack instances */
|
||||
export const allWebpackInstances = new Set<AnyWebpackRequire>();
|
||||
export const patchTimings = [] as Array<[plugin: string, moduleId: PropertyKey, match: string | RegExp, totalTime: number]>;
|
||||
|
||||
// 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,
|
||||
/** Whether we tried to fallback to factory WebpackRequire, or disabled patches */
|
||||
let wreqFallbackApplied = false;
|
||||
/** Whether we should be patching factories.
|
||||
*
|
||||
* This should be disabled if we start searching for the module to get the build number, and then resumed once it's done.
|
||||
* */
|
||||
let shouldPatchFactories = true;
|
||||
|
||||
get: () => webpackChunk,
|
||||
set: v => {
|
||||
if (v?.push) {
|
||||
if (!v.push.$$vencordOriginal) {
|
||||
logger.info(`Patching ${WEBPACK_CHUNK}.push`);
|
||||
patchPush(v);
|
||||
const getBuildNumber = makeLazy(() => {
|
||||
try {
|
||||
shouldPatchFactories = false;
|
||||
|
||||
// @ts-ignore
|
||||
delete window[WEBPACK_CHUNK];
|
||||
window[WEBPACK_CHUNK] = v;
|
||||
}
|
||||
const hardcodedModuleAttempt = wreq(128014)?.b;
|
||||
if (typeof hardcodedModuleAttempt === "function" && typeof hardcodedModuleAttempt() === "number") {
|
||||
return hardcodedModuleAttempt() as number;
|
||||
}
|
||||
|
||||
webpackChunk = v;
|
||||
const moduleId = findModuleId("Trying to open a changelog for an invalid build number");
|
||||
if (moduleId == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const exports = Object.values<ModuleExports>(wreq(moduleId));
|
||||
if (exports.length !== 1 || typeof exports[0] !== "function") {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const buildNumber = exports[0]();
|
||||
return typeof buildNumber === "number" ? buildNumber : -1;
|
||||
} catch {
|
||||
return -1;
|
||||
} finally {
|
||||
shouldPatchFactories = true;
|
||||
}
|
||||
});
|
||||
|
||||
// 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
|
||||
Object.defineProperty(Function.prototype, "m", {
|
||||
configurable: true,
|
||||
type Define = typeof Reflect.defineProperty;
|
||||
const define: Define = (target, p, attributes) => {
|
||||
if (Object.hasOwn(attributes, "value")) {
|
||||
attributes.writable = true;
|
||||
}
|
||||
|
||||
set(v: any) {
|
||||
Object.defineProperty(this, "m", {
|
||||
value: v,
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
writable: true
|
||||
});
|
||||
return Reflect.defineProperty(target, p, {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
...attributes
|
||||
});
|
||||
};
|
||||
|
||||
// When using react devtools or other extensions, we may also catch their webpack here.
|
||||
// This ensures we actually got the right one
|
||||
// wreq.m is the Webpack object containing module factories. It is pre-populated with module factories, and is also populated via webpackGlobal.push
|
||||
// We use this setter to intercept when wreq.m is defined and apply the patching in its module factories.
|
||||
// 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, "m", {
|
||||
enumerable: false,
|
||||
|
||||
set(this: AnyWebpackRequire, originalModules: AnyWebpackRequire["m"]) {
|
||||
define(this, "m", { value: originalModules });
|
||||
|
||||
// Ensure this is one of Discord main Webpack instances.
|
||||
// We may catch Discord bundled libs, React Devtools or other extensions Webpack instances here.
|
||||
const { stack } = new Error();
|
||||
if (!(stack?.includes("discord.com") || stack?.includes("discordapp.com")) || Array.isArray(v)) {
|
||||
if (!stack?.includes("http") || stack.match(/at \d+? \(/) || !String(this).includes("exports:{}")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fileName = stack.match(/\/assets\/(.+?\.js)/)?.[1] ?? "";
|
||||
logger.info("Found Webpack module factory", fileName);
|
||||
const fileName = stack.match(/\/assets\/(.+?\.js)/)?.[1];
|
||||
logger.info("Found Webpack module factories" + interpolateIfDefined` in ${fileName}`);
|
||||
|
||||
patchFactories(v);
|
||||
allWebpackInstances.add(this);
|
||||
|
||||
// Define a setter for the bundlePath property of WebpackRequire. Only the main Webpack has this property.
|
||||
// Define a setter for the ensureChunk property of WebpackRequire. Only the main Webpack (which is the only that includes chunk loading) has this property.
|
||||
// So if the setter is called, this means we can initialize the internal references to WebpackRequire.
|
||||
Object.defineProperty(this, "p", {
|
||||
configurable: true,
|
||||
|
||||
set(this: WebpackInstance, bundlePath: string) {
|
||||
Object.defineProperty(this, "p", {
|
||||
value: bundlePath,
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
writable: true
|
||||
});
|
||||
define(this, "e", {
|
||||
enumerable: false,
|
||||
|
||||
set(this: WebpackRequire, ensureChunk: WebpackRequire["e"]) {
|
||||
define(this, "e", { value: ensureChunk });
|
||||
clearTimeout(setterTimeout);
|
||||
if (bundlePath !== "/assets/") return;
|
||||
|
||||
logger.info(`Main Webpack found in ${fileName}, initializing internal references to WebpackRequire`);
|
||||
logger.info("Main WebpackInstance found" + interpolateIfDefined` in ${fileName}` + ", initializing internal references to WebpackRequire");
|
||||
_initWebpack(this);
|
||||
|
||||
for (const beforeInitListener of beforeInitListeners) {
|
||||
beforeInitListener(this);
|
||||
}
|
||||
}
|
||||
});
|
||||
// setImmediate to clear this property setter if this is not the main Webpack.
|
||||
// If this is the main Webpack, wreq.p will always be set before the timeout runs.
|
||||
const setterTimeout = setTimeout(() => Reflect.deleteProperty(this, "p"), 0);
|
||||
// If this is the main Webpack, wreq.e will always be set before the timeout runs.
|
||||
const setterTimeout = setTimeout(() => Reflect.deleteProperty(this, "e"), 0);
|
||||
|
||||
// Patch the pre-populated factories
|
||||
for (const id in originalModules) {
|
||||
if (updateExistingFactory(originalModules, id, originalModules[id], true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
notifyFactoryListeners(originalModules[id]);
|
||||
defineModulesFactoryGetter(id, Settings.eagerPatches && shouldPatchFactories ? wrapAndPatchFactory(id, originalModules[id]) : originalModules[id]);
|
||||
}
|
||||
|
||||
define(originalModules, 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(originalModules, moduleFactoriesHandler);
|
||||
/*
|
||||
If Webpack 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(originalModules, new Proxy(originalModules, moduleFactoriesHandler));
|
||||
*/
|
||||
|
||||
define(this, "m", { value: proxiedModuleFactories });
|
||||
}
|
||||
});
|
||||
|
||||
function patchPush(webpackGlobal: any) {
|
||||
function handlePush(chunk: any) {
|
||||
try {
|
||||
patchFactories(chunk[1]);
|
||||
} catch (err) {
|
||||
logger.error("Error in handlePush", err);
|
||||
const moduleFactoriesHandler: ProxyHandler<AnyWebpackRequire["m"]> = {
|
||||
/*
|
||||
If Webpack 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;
|
||||
},
|
||||
// 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 define(target, p, { value: newValue });
|
||||
}
|
||||
|
||||
return handlePush.$$vencordOriginal.call(webpackGlobal, chunk);
|
||||
if (updateExistingFactory(target, p, newValue)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
notifyFactoryListeners(newValue);
|
||||
defineModulesFactoryGetter(p, Settings.eagerPatches && shouldPatchFactories ? wrapAndPatchFactory(p, newValue) : newValue);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @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: AnyModuleFactory, ignoreExistingInTarget: boolean = false) {
|
||||
let existingFactory: TypedPropertyDescriptor<AnyModuleFactory> | undefined;
|
||||
let moduleFactoriesWithFactory: AnyWebpackRequire["m"] | 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);
|
||||
moduleFactoriesWithFactory = wreq.m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
if (existingFactory != null) {
|
||||
// If existingFactory exists in any Webpack instance, it's either wrapped in defineModuleFactoryGetter, or it has already been required.
|
||||
// So define the descriptor of it on this current Webpack instance (if it doesn't exist already), call Reflect.set with the new original,
|
||||
// and let the correct logic apply (normal set, or defineModuleFactoryGetter setter)
|
||||
|
||||
Object.defineProperty(webpackGlobal, "push", {
|
||||
configurable: true,
|
||||
|
||||
get: () => handlePush,
|
||||
set(v) {
|
||||
handlePush.$$vencordOriginal = v;
|
||||
if (moduleFactoriesWithFactory !== moduleFactoriesTarget) {
|
||||
Reflect.defineProperty(moduleFactoriesTarget, id, existingFactory);
|
||||
}
|
||||
});
|
||||
|
||||
// Persist $$vencordPatchedSource in the new original factory, if the patched one has already been required
|
||||
if (IS_DEV && existingFactory.value != null) {
|
||||
newFactory.$$vencordPatchedSource = existingFactory.value.$$vencordPatchedSource;
|
||||
}
|
||||
|
||||
return Reflect.set(moduleFactoriesTarget, id, newFactory, moduleFactoriesTarget);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
let webpackNotInitializedLogged = false;
|
||||
/**
|
||||
* Notify all factory listeners.
|
||||
*
|
||||
* @param factory The original factory to notify for
|
||||
*/
|
||||
function notifyFactoryListeners(factory: AnyModuleFactory) {
|
||||
for (const factoryListener of factoryListeners) {
|
||||
try {
|
||||
factoryListener(factory);
|
||||
} catch (err) {
|
||||
logger.error("Error in Webpack factory listener:\n", err, factoryListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function patchFactories(factories: Record<string, (module: any, exports: any, require: WebpackInstance) => void>) {
|
||||
for (const id in factories) {
|
||||
let mod = factories[id];
|
||||
/**
|
||||
* Define the getter for returning the patched version of the module factory.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @param id The id of the module
|
||||
* @param factory The original or patched module factory
|
||||
*/
|
||||
function defineModulesFactoryGetter(id: PropertyKey, factory: WrappedModuleFactory) {
|
||||
const descriptor: PropertyDescriptor = {
|
||||
get() {
|
||||
// $$vencordOriginal means the factory is already patched
|
||||
if (!shouldPatchFactories || factory.$$vencordOriginal != null) {
|
||||
return factory;
|
||||
}
|
||||
|
||||
const originalMod = mod;
|
||||
const patchedBy = new Set();
|
||||
return (factory = wrapAndPatchFactory(id, factory));
|
||||
},
|
||||
set(newFactory: AnyModuleFactory) {
|
||||
if (IS_DEV && factory.$$vencordPatchedSource != null) {
|
||||
newFactory.$$vencordPatchedSource = factory.$$vencordPatchedSource;
|
||||
}
|
||||
|
||||
const factory = factories[id] = function (module: any, exports: any, require: WebpackInstance) {
|
||||
if (wreq == null && IS_DEV) {
|
||||
if (!webpackNotInitializedLogged) {
|
||||
webpackNotInitializedLogged = true;
|
||||
if (factory.$$vencordOriginal != null) {
|
||||
factory.toString = newFactory.toString.bind(newFactory);
|
||||
factory.$$vencordOriginal = newFactory;
|
||||
} else {
|
||||
factory = newFactory;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 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 wreq of allWebpackInstances) {
|
||||
define(wreq.m, id, descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps and patches a module 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 wrapAndPatchFactory(id: PropertyKey, originalFactory: AnyModuleFactory) {
|
||||
const patchedFactory = patchFactory(id, originalFactory);
|
||||
|
||||
const wrappedFactory: WrappedModuleFactory = function (...args) {
|
||||
// Restore the original factory in all the module factories objects. We want to make sure the original factory is restored properly, no matter what is the Webpack instance
|
||||
for (const wreq of allWebpackInstances) {
|
||||
define(wreq.m, id, { value: wrappedFactory.$$vencordOriginal });
|
||||
}
|
||||
|
||||
// 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 as WebpackRequire);
|
||||
} else if (IS_DEV) {
|
||||
logger.error("WebpackRequire was not initialized, running modules without patches instead.");
|
||||
return wrappedFactory.$$vencordOriginal!.apply(this, args);
|
||||
}
|
||||
} else if (IS_DEV) {
|
||||
return wrappedFactory.$$vencordOriginal!.apply(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
return void originalMod(module, exports, require);
|
||||
let factoryReturn: unknown;
|
||||
try {
|
||||
// Call the patched factory
|
||||
factoryReturn = patchedFactory.apply(this, args);
|
||||
} catch (err) {
|
||||
// Just re-throw Discord errors
|
||||
if (patchedFactory === originalFactory) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
try {
|
||||
mod(module, exports, require);
|
||||
} catch (err) {
|
||||
// Just rethrow discord errors
|
||||
if (mod === originalMod) throw err;
|
||||
logger.error("Error in patched module factory:\n", err);
|
||||
return wrappedFactory.$$vencordOriginal!.apply(this, args);
|
||||
}
|
||||
|
||||
logger.error("Error in patched module", err);
|
||||
return void originalMod(module, exports, require);
|
||||
}
|
||||
exports = module.exports;
|
||||
if (exports == null) return factoryReturn;
|
||||
|
||||
exports = module.exports;
|
||||
if (typeof require === "function") {
|
||||
const shouldIgnoreModule = _shouldIgnoreModule(exports);
|
||||
|
||||
if (!exports) return;
|
||||
|
||||
if (require.c) {
|
||||
const shouldIgnoreModule = _shouldIgnoreModule(exports);
|
||||
|
||||
if (shouldIgnoreModule) {
|
||||
if (shouldIgnoreModule) {
|
||||
if (require.c != null) {
|
||||
Object.defineProperty(require.c, id, {
|
||||
value: require.c[id],
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
writable: true
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (const callback of moduleListeners) {
|
||||
try {
|
||||
callback(exports, id);
|
||||
} catch (err) {
|
||||
logger.error("Error in Webpack module listener:\n", err, callback);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [filter, callback] of subscriptions) {
|
||||
try {
|
||||
if (exports && filter(exports)) {
|
||||
subscriptions.delete(filter);
|
||||
callback(exports, id);
|
||||
}
|
||||
|
||||
if (typeof exports !== "object") {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const exportKey in exports) {
|
||||
if (exports[exportKey] && filter(exports[exportKey])) {
|
||||
subscriptions.delete(filter);
|
||||
callback(exports[exportKey], id);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error("Error while firing callback for Webpack subscription:\n", err, filter, callback);
|
||||
}
|
||||
}
|
||||
} as any as { toString: () => string, original: any, (...args: any[]): void; $$vencordPatchedSource?: string; };
|
||||
|
||||
factory.toString = originalMod.toString.bind(originalMod);
|
||||
factory.original = originalMod;
|
||||
|
||||
for (const factoryListener of factoryListeners) {
|
||||
try {
|
||||
factoryListener(originalMod);
|
||||
} catch (err) {
|
||||
logger.error("Error in Webpack factory listener:\n", err, factoryListener);
|
||||
return factoryReturn;
|
||||
}
|
||||
}
|
||||
|
||||
// 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 (const callback of moduleListeners) {
|
||||
try {
|
||||
callback(exports, id);
|
||||
} catch (err) {
|
||||
logger.error("Error in Webpack module listener:\n", err, callback);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < patches.length; i++) {
|
||||
const patch = patches[i];
|
||||
for (const [filter, callback] of waitForSubscriptions) {
|
||||
try {
|
||||
|
||||
const moduleMatches = typeof patch.find === "string"
|
||||
? code.includes(patch.find)
|
||||
: patch.find.test(code);
|
||||
if (filter(exports)) {
|
||||
waitForSubscriptions.delete(filter);
|
||||
callback(exports, id);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!moduleMatches) continue;
|
||||
if (typeof exports !== "object") {
|
||||
continue;
|
||||
}
|
||||
|
||||
patchedBy.add(patch.plugin);
|
||||
for (const exportKey in exports) {
|
||||
const exportValue = exports[exportKey];
|
||||
|
||||
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[]) {
|
||||
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.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;
|
||||
if (exportValue != null && filter(exportValue)) {
|
||||
waitForSubscriptions.delete(filter);
|
||||
callback(exportValue, id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error("Error while firing callback for Webpack waitFor subscription:\n", err, filter, callback);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
return factoryReturn;
|
||||
};
|
||||
|
||||
if (IS_DEV) {
|
||||
const changeSize = code.length - lastCode.length;
|
||||
const match = lastCode.match(replacement.match)!;
|
||||
wrappedFactory.toString = originalFactory.toString.bind(originalFactory);
|
||||
wrappedFactory.$$vencordOriginal = originalFactory;
|
||||
|
||||
// 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;
|
||||
if (IS_DEV && patchedFactory !== originalFactory) {
|
||||
const patchedSource = String(patchedFactory);
|
||||
|
||||
const context = lastCode.slice(start, end);
|
||||
const patchedContext = code.slice(start, endPatched);
|
||||
wrappedFactory.$$vencordPatchedSource = patchedSource;
|
||||
originalFactory.$$vencordPatchedSource = patchedSource;
|
||||
}
|
||||
|
||||
// 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);
|
||||
return wrappedFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Patches a module factory.
|
||||
*
|
||||
* @param id The id of the module
|
||||
* @param factory The original module factory
|
||||
* @returns The patched module factory
|
||||
*/
|
||||
function patchFactory(id: PropertyKey, factory: AnyModuleFactory) {
|
||||
// 0, prefix to turn it into an expression: 0,function(){} would be invalid syntax without the 0,
|
||||
let code: string = "0," + String(factory);
|
||||
let patchedFactory = factory;
|
||||
|
||||
const patchedBy = new Set<string>();
|
||||
|
||||
for (let i = 0; i < patches.length; i++) {
|
||||
const patch = patches[i];
|
||||
|
||||
const moduleMatches = typeof patch.find === "string"
|
||||
? code.includes(patch.find)
|
||||
: (patch.find.global && (patch.find.lastIndex = 0), patch.find.test(code));
|
||||
|
||||
if (!moduleMatches) continue;
|
||||
|
||||
if (
|
||||
!Settings.eagerPatches &&
|
||||
(patch.fromBuild != null && getBuildNumber() !== -1 && getBuildNumber() < patch.fromBuild) ||
|
||||
(patch.toBuild != null && getBuildNumber() !== -1 && getBuildNumber() > patch.toBuild)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
patchedBy.add(patch.plugin);
|
||||
|
||||
const executePatch = traceFunctionWithResults(`patch by ${patch.plugin}`, (match: string | RegExp, replace: string) => {
|
||||
if (match instanceof RegExp && match.global) {
|
||||
match.lastIndex = 0;
|
||||
}
|
||||
|
||||
return code.replace(match, replace);
|
||||
});
|
||||
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 (
|
||||
!Settings.eagerPatches &&
|
||||
(replacement.fromBuild != null && getBuildNumber() !== -1 && getBuildNumber() < replacement.fromBuild) ||
|
||||
(replacement.toBuild != null && getBuildNumber() !== -1 && getBuildNumber() > replacement.toBuild)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const lastCode = code;
|
||||
const lastFactory = factory;
|
||||
|
||||
try {
|
||||
const [newCode, totalTime] = executePatch(replacement.match, replacement.replace as string);
|
||||
|
||||
if (IS_REPORTER) {
|
||||
patchTimings.push([patch.plugin, id, replacement.match, totalTime]);
|
||||
}
|
||||
|
||||
if (newCode === code) {
|
||||
if (!patch.noWarn) {
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
patchedBy.delete(patch.plugin);
|
||||
|
||||
if (patch.group) {
|
||||
logger.warn(`Undoing patch group ${patch.find} by ${patch.plugin} because replacement ${replacement.match} errored`);
|
||||
mod = previousMod;
|
||||
logger.warn(`Undoing patch group ${patch.find} by ${patch.plugin} because replacement ${replacement.match} had no effect`);
|
||||
code = previousCode;
|
||||
patchedFactory = previousFactory;
|
||||
patchedBy.delete(patch.plugin);
|
||||
break;
|
||||
}
|
||||
|
||||
mod = lastMod;
|
||||
code = lastCode;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!patch.all) patches.splice(i--, 1);
|
||||
}
|
||||
code = newCode;
|
||||
patchedFactory = (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);
|
||||
|
||||
if (IS_DEV) {
|
||||
if (mod !== originalMod) {
|
||||
factory.$$vencordPatchedSource = String(mod);
|
||||
} else if (wreq != null) {
|
||||
const existingFactory = wreq.m[id];
|
||||
if (IS_DEV) {
|
||||
const changeSize = code.length - lastCode.length;
|
||||
const match = lastCode.match(replacement.match)!;
|
||||
|
||||
if (existingFactory != null) {
|
||||
factory.$$vencordPatchedSource = existingFactory.$$vencordPatchedSource;
|
||||
// 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: string[] = [];
|
||||
for (const d of diff) {
|
||||
const color = d.removed
|
||||
? "red"
|
||||
: d.added
|
||||
? "lime"
|
||||
: "grey";
|
||||
fmt += "%c%s";
|
||||
elements.push("color:" + color, d.value);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
patchedBy.delete(patch.plugin);
|
||||
|
||||
if (patch.group) {
|
||||
logger.warn(`Undoing patch group ${patch.find} by ${patch.plugin} because replacement ${replacement.match} errored`);
|
||||
code = previousCode;
|
||||
patchedFactory = previousFactory;
|
||||
break;
|
||||
}
|
||||
|
||||
code = lastCode;
|
||||
patchedFactory = lastFactory;
|
||||
}
|
||||
}
|
||||
|
||||
if (!patch.all) patches.splice(i--, 1);
|
||||
}
|
||||
|
||||
return patchedFactory;
|
||||
}
|
||||
|
|
|
@ -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 { AnyModuleFactory, ModuleExports, WebpackRequire } from "./wreq";
|
||||
|
||||
const logger = new Logger("Webpack");
|
||||
|
||||
|
@ -33,8 +33,8 @@ export let _resolveReady: () => void;
|
|||
*/
|
||||
export const onceReady = new Promise<void>(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;
|
||||
|
||||
|
@ -86,16 +86,25 @@ export const filters = {
|
|||
}
|
||||
};
|
||||
|
||||
export type CallbackFn = (mod: any, id: string) => void;
|
||||
export type CallbackFn = (module: ModuleExports, id: PropertyKey) => void;
|
||||
export type FactoryListernFn = (factory: AnyModuleFactory) => void;
|
||||
|
||||
export const subscriptions = new Map<FilterFn, CallbackFn>();
|
||||
export const waitForSubscriptions = new Map<FilterFn, CallbackFn>();
|
||||
export const moduleListeners = new Set<CallbackFn>();
|
||||
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<FactoryListernFn>();
|
||||
|
||||
export function _initWebpack(webpackRequire: WebpackInstance) {
|
||||
export function _initWebpack(webpackRequire: WebpackRequire) {
|
||||
wreq = webpackRequire;
|
||||
|
||||
if (webpackRequire.c == null) return;
|
||||
cache = webpackRequire.c;
|
||||
|
||||
Reflect.defineProperty(webpackRequire.c, Symbol.toStringTag, {
|
||||
value: "ModuleCache",
|
||||
configurable: true,
|
||||
writable: true,
|
||||
enumerable: false
|
||||
});
|
||||
}
|
||||
|
||||
// Credits to Zerebos for implementing this in BD, thus giving the idea for us to implement it too
|
||||
|
@ -618,7 +627,7 @@ export function waitFor(filter: string | PropsFilter | FilterFn, callback: Callb
|
|||
if (existing) return void callback(existing, id);
|
||||
}
|
||||
|
||||
subscriptions.set(filter, callback);
|
||||
waitForSubscriptions.set(filter, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -634,7 +643,7 @@ export function search(...code: CodeFilter) {
|
|||
const factories = wreq.m;
|
||||
|
||||
for (const id in factories) {
|
||||
const factory = factories[id].original ?? factories[id];
|
||||
const factory = factories[id];
|
||||
|
||||
if (stringMatches(factory.toString(), code))
|
||||
results[id] = factory;
|
||||
|
|
205
src/webpack/wreq.d.ts
vendored
Normal file
205
src/webpack/wreq.d.ts
vendored
Normal file
|
@ -0,0 +1,205 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated, Nuckyz and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export type ModuleExports = any;
|
||||
|
||||
export type Module = {
|
||||
id: PropertyKey;
|
||||
loaded: boolean;
|
||||
exports: ModuleExports;
|
||||
};
|
||||
|
||||
/** 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 | "__webpack_queues__";
|
||||
export type WebpackExports = unique symbol | "__webpack_exports__";
|
||||
export type WebpackError = unique symbol | "__webpack_error__";
|
||||
|
||||
export type AsyncModulePromise = Promise<ModuleExports> & {
|
||||
[WebpackQueues]: (fnQueue: ((queue: any[]) => any)) => any;
|
||||
[WebpackExports]: ModuleExports;
|
||||
[WebpackError]?: any;
|
||||
};
|
||||
|
||||
export type AsyncModuleBody = (
|
||||
handleAsyncDependencies: (deps: AsyncModulePromise[]) =>
|
||||
Promise<() => ModuleExports[]> | (() => ModuleExports[]),
|
||||
asyncResult: (error?: any) => void
|
||||
) => Promise<void>;
|
||||
|
||||
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: (this: ChunkHandlers, chunkId: PropertyKey, promises: Promise<void[]>) => 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: (this: ChunkHandlers, chunkId: PropertyKey, promises: Promise<void[]>) => void,
|
||||
};
|
||||
|
||||
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: (this: OnChunksLoaded, chunkId: PropertyKey) => boolean;
|
||||
// };
|
||||
|
||||
export type WebpackRequire = ((moduleId: PropertyKey) => ModuleExports) & {
|
||||
/** The module factories, where all modules that have been loaded are stored (pre-loaded or loaded by lazy chunks) */
|
||||
m: Record<PropertyKey, ModuleFactory>;
|
||||
/** The module cache, where all modules which have been WebpackRequire'd are stored */
|
||||
c: Record<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.keys(fromObject).forEach(key => {
|
||||
// * if (key !== "default" && !Object.hasOwn(toObject, key)) {
|
||||
// * Object.defineProperty(toObject, key, {
|
||||
// * get: () => fromObject[key],
|
||||
// * enumerable: true
|
||||
// * });
|
||||
// * }
|
||||
// * });
|
||||
// * @returns fromObject
|
||||
// */
|
||||
// es: (this: WebpackRequire, fromObject: AnyRecord, toObject: AnyRecord) => AnyRecord;
|
||||
/**
|
||||
* Creates an async module. A module that which has top level await, or requires an export from an async module.
|
||||
*
|
||||
* 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
|
||||
* 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 */
|
||||
n: (this: WebpackRequire, exports: any) => () => 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: (this: WebpackRequire, value: any, mode: number) => any;
|
||||
/**
|
||||
* 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 = { exportName: () => someExportedValue };
|
||||
* for (const key in definition) {
|
||||
* if (Object.hasOwn(definition, key) && !Object.hasOwn(exports, key)) {
|
||||
* 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: 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: (this: WebpackRequire, chunkId: PropertyKey) => Promise<void[]>;
|
||||
/** Get the filename for the css part of a chunk */
|
||||
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 */
|
||||
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 */
|
||||
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: (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: ModuleExports) => void;
|
||||
/** Node.js module decorator. Decorates a module as a Node.js module */
|
||||
nmd: (this: WebpackRequire, 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".
|
||||
* @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<any>;
|
||||
/** Bundle public path, where chunk files are stored. Used by other methods which load chunks to obtain the full asset url */
|
||||
p: string;
|
||||
/** The runtime id of the current runtime */
|
||||
j: string;
|
||||
/** Document baseURI or WebWorker location.href */
|
||||
b: string;
|
||||
};
|
||||
|
||||
// Utility section for Vencord
|
||||
|
||||
export type AnyWebpackRequire = ((moduleId: PropertyKey) => ModuleExports) & Partial<Omit<WebpackRequire, "m">> & {
|
||||
/** The module factories, where all modules that have been loaded are stored (pre-loaded or loaded by lazy chunks) */
|
||||
m: Record<PropertyKey, AnyModuleFactory>;
|
||||
};
|
||||
|
||||
/** exports can be anything, however initially it is always an empty object */
|
||||
export type AnyModuleFactory = ((this: ModuleExports, module: Module, exports: ModuleExports, require: AnyWebpackRequire) => void) & {
|
||||
$$vencordPatchedSource?: string;
|
||||
};
|
||||
|
||||
export type WrappedModuleFactory = AnyModuleFactory & {
|
||||
$$vencordOriginal?: AnyModuleFactory;
|
||||
$$vencordPatchedSource?: string;
|
||||
};
|
||||
|
||||
export type WrappedModuleFactories = Record<PropertyKey, WrappedModuleFactory>;
|
Loading…
Add table
Reference in a new issue