Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 127 additions & 57 deletions packages/core/src/patch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,11 +285,25 @@ function handleModuleDependencies() {
}

const injectedWpModules: IdentifiedWebpackModule[] = [];
function injectModules(entry: WebpackJsonpEntry[1]) {
function injectModules(entry: WebpackJsonpEntry[1], splice?: boolean, fullEntry?: WebpackJsonpEntry) {
const modules: Record<string, WebpackModuleFunc> = {};
const entrypoints: string[] = [];
let inject = false;

for (const [name, func] of Object.entries(moonlight.moonmap.getWebpackModules("window.moonlight.moonmap"))) {
// @ts-expect-error probably should fix the type on this idk
func.__moonlight = true;
injectedWpModules.push({ id: name, run: func });
modules[name] = func;
inject = true;
}

if (webpackRequire != null) {
for (const id of moonlight.moonmap.getLazyModules()) {
webpackRequire.e(id);
}
}

for (const [_modId, mod] of Object.entries(entry)) {
const modStr = mod.toString();
for (const wpModule of webpackModules) {
Expand Down Expand Up @@ -341,42 +355,44 @@ function injectModules(entry: WebpackJsonpEntry[1]) {
if (wpModule.entrypoint) entrypoints.push(id);
}
}
if (!webpackModules.size) break;
}

for (const [name, func] of Object.entries(moonlight.moonmap.getWebpackModules("window.moonlight.moonmap"))) {
// @ts-expect-error probably should fix the type on this idk
func.__moonlight = true;
injectedWpModules.push({ id: name, run: func });
modules[name] = func;
inject = true;
if (!webpackModules.size) break;
}

if (webpackRequire != null) {
for (const id of moonlight.moonmap.getLazyModules()) {
webpackRequire.e(id);
if (inject) {
// biome-ignore lint/correctness/noUnusedFunctionParameters: keep ts happy
let oldEntry = (require: WebpackRequireType) => {};
if (fullEntry?.[2] != null) {
oldEntry = fullEntry[2];
}
}

if (inject) {
logger.debug("Injecting modules:", modules, entrypoints);
window.webpackChunkdiscord_app.push([
[--chunkId],
modules,
(require: WebpackRequireType) =>
const loader = (require: WebpackRequireType) => {
if (require.__moonlight) {
entrypoints.map((id) => {
try {
if (require.m[id] == null) {
logger.error(`Failing to load entrypoint module "${id}" because it's not found in Webpack.`);
} else {
return require(id);
require(id);
}
} catch (err) {
logger.error(`Failed to load entrypoint module "${id}":`, err);
}
return undefined;
})
]);
});
}
oldEntry(require);
};

if (splice && fullEntry != null) {
logger.trace("Splicing custom Webpack modules:", fullEntry[0], modules, entrypoints, entry);
fullEntry[1] = { ...fullEntry[1], ...modules };

if (entrypoints.length > 0) fullEntry[2] = loader;
} else {
logger.trace("Injecting custom Webpack modules:", modules, entrypoints);
window.webpackChunkdiscord_app.push([[--chunkId], modules, entrypoints.length > 0 ? loader : undefined]);
}
}
}

Expand Down Expand Up @@ -414,66 +430,120 @@ export async function installWebpackPatcher() {
run: wpRequireFetcher
});

registerPatch({
find: ".extendSuperProperties({launch_signature:",
replace: [
{
match: /=performance\.now\(\),(\i)=(.+?:null);(\i)\.extendSuperProperties\({launch_signature:\i}\);/,
replacement: (_, sigVar, launch_signature, PropertiesHelper) => `=performance.now(),${sigVar}=null;
let _libdiscoreInitialized=false;
Object.defineProperty(window,"_libdiscoreInitialized",{
set: (x)=>{
_libdiscoreInitialized = x;
const sig = ${launch_signature};
${sigVar} = sig;
${PropertiesHelper}.extendSuperProperties({
launch_signature: sig
});
},
get: () => _libdiscoreInitialized
});`
}
],
ext: "analyticsHelperFix",
id: 0
});

// https://github.com/web-infra-dev/rspack/blob/2d78ee6c08d41ac538628c752fa97a8a75f4192f/crates/rspack_plugin_runtime/src/runtime_module/runtime/jsonp_chunk_loading_with_callback.ejs#L29-L31
let realWebpackJsonp: WebpackJsonp | null = null;
Object.defineProperty(window, "webpackChunkdiscord_app", {
set: (jsonp: WebpackJsonp) => {
// The load process does `chunk = chunk || []` so this doesn't really matter, but just in case
if (realWebpackJsonp) return;
realWebpackJsonp = jsonp;
const realPush = jsonp.push;
if (jsonp.push.__moonlight !== true) {
jsonp.push = (items) => {
moonlight.events.dispatchEvent(WebEventType.ChunkLoad, {
chunkId: items[0],
modules: items[1],
require: items[2]
});

patchModules(items[1]);

try {
const res = realPush.apply(realWebpackJsonp, [items]);
if (!realPush.__moonlight) {
logger.trace("Injecting Webpack modules", items[1]);
injectModules(items[1]);
// .push is overriden for each Webpack entrypoint
let currentPush: WebpackJsonp["push"] = jsonp.push;
Object.defineProperty(jsonp, "push", {
set: (push: WebpackJsonp["push"]) => {
logger.trace("Overwriting Webpack push:", push);
currentPush = push;
},

get: () => {
const fakePush: WebpackJsonp["push"] = (items) => {
moonlight.events.dispatchEvent(WebEventType.ChunkLoad, {
chunkId: items[0],
modules: items[1],
require: items[2]
});

try {
patchModules(items[1]);
} catch (err) {
logger.warn("Failed to patch Webpack modules:", err);
}

return res;
} catch (err) {
logger.error("Failed to inject Webpack modules:", err);
return 0;
}
};
try {
injectModules(items[1], true, items);
logger.trace("Injecting Webpack modules:", items[1]);
const res = currentPush.apply(realWebpackJsonp, [items]);
return res;
} catch (err) {
logger.error("Failed to inject Webpack modules:", err);
return 0;
}
};

jsonp.push.bind = (thisArg: any, ...args: any[]) => {
return realPush.bind(thisArg, ...args);
};
// Multiple Webpack entrypoints load on top of each other by using `bind`, so we must pass through to the next push function so we don't cause an infinite loop
fakePush.bind = (thisArg: any, ...args: any[]) => {
return currentPush.bind(thisArg, ...args);
};

jsonp.push.__moonlight = true;
if (!realPush.__moonlight) {
logger.debug("Injecting Webpack modules with empty entry");
// Inject an empty entry to cause iteration to happen once
// Kind of a dirty hack but /shrug
injectModules({ deez: () => {} });
return fakePush;
}
}
});
},

get: () => {
return realWebpackJsonp;
}
});

// This is triggered when the individual Webpack entrypoints load with their initial modules
Object.defineProperty(Function.prototype, "m", {
configurable: true,
set(modules: any) {
set(modules: { [id: string]: WebpackModuleFunc }) {
const { stack } = new Error();
if (stack!.includes("/assets/") && !Array.isArray(modules)) {
// We should only use the "main" `require` when possible - this is how we distinguish them
if (stack!.includes("/assets/web.")) {
this.__moonlight = true;
}

if (!window.webpackChunkdiscord_app) window.webpackChunkdiscord_app = [];
moonlight.events.dispatchEvent(WebEventType.ChunkLoad, {
modules: modules
});
patchModules(modules);

if (!window.webpackChunkdiscord_app) window.webpackChunkdiscord_app = [];
injectModules(modules);
if (this.__moonlight) {
patchModules(modules);
const fakeEntry: WebpackJsonpEntry = [[chunkId], { ...modules }, undefined];
injectModules(modules, true, fakeEntry);

const newModules = fakeEntry[1];
const modKeys = Object.keys(modules);
const newModKeys = Object.keys(newModules);
const newMods = newModKeys.filter((key) => !modKeys.includes(key));

if (newMods.length > 0) {
logger.trace("Splicing into initial Webpack modules!", newMods);
for (const id of newMods) {
modules[id] = newModules[id];
}
if (fakeEntry[2] != null) window.webpackChunkdiscord_app.push([[chunkId--], {}, fakeEntry[2]]);
}
}
}

Object.defineProperty(this, "m", {
Expand Down
31 changes: 23 additions & 8 deletions packages/node-preload/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,15 +162,30 @@ if (isOverlay) {
process.emit("loaded");

function replayScripts() {
const scripts = [...document.querySelectorAll("script")].filter(
(script) => script.src && blockedScripts.some((url) => url.includes(script.src))
);

blockedScripts.reverse();
for (const url of blockedScripts) {
if (!url.includes("/web.")) continue;
const ignoreScripts = [
// We never blocked this in the first place
"popout.",
// We don't want this to load at all
"sentry."
];

const scripts = [...document.querySelectorAll("script")].filter((script) => {
if (!script.src) return false;

try {
const url = new URL(script.src);
const hasUrl =
url.pathname.match(/\/assets\/[a-zA-Z-]+\./) &&
!url.searchParams.has("inj") &&
(url.host.endsWith("discord.com") || url.host.endsWith("discordapp.com"));
const shouldIgnore = ignoreScripts.some((other) => url.pathname.startsWith(`/assets/${other}`));
return hasUrl && !shouldIgnore;
} catch {
return false;
}
});

const script = scripts.find((script) => url.includes(script.src))!;
for (const script of scripts) {
const newScript = document.createElement("script");
for (const attr of script.attributes) {
if (attr.name === "src") attr.value += "?inj";
Expand Down
7 changes: 6 additions & 1 deletion packages/types/src/discord/webpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export type WebpackRequireType = typeof MappingsWebpackRequire &
c: Record<string, WebpackModule>;
m: Record<string, WebpackModuleFunc>;
e: (module: number | string) => Promise<void>;
__moonlight?: boolean;
};

export type WebpackModule = {
Expand All @@ -18,7 +19,11 @@ export type WebpackModuleFunc = ((module: any, exports: any, require: WebpackReq
__moonlight?: boolean;
};

export type WebpackJsonpEntry = [number[], { [id: string]: WebpackModuleFunc }, (require: WebpackRequireType) => any];
export type WebpackJsonpEntry = [
number[],
{ [id: string]: WebpackModuleFunc },
((require: WebpackRequireType) => any) | undefined
];

export type WebpackJsonp = WebpackJsonpEntry[] & {
push: {
Expand Down
Loading