Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix crashes #636

Merged
merged 6 commits into from
Jul 6, 2024
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
3 changes: 2 additions & 1 deletion cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@
"Vendicated",
"webauthn",
"weblate",
"Withs"
"Withs",
"YofukashiNo"
],
"ignoreWords": [],
"import": [],
Expand Down
5 changes: 3 additions & 2 deletions src/renderer/coremods/language/plaintextPatches.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ export default [
replacements: [
{
match: /(\.Messages\.LANGUAGE,)\s*children:((?:[^}]*?}){3}\))/,
replace: (_, prefix, ogChild) => `${prefix}children:[${coremodStr}.Card(),${ogChild}]`,
replace: (_, prefix, ogChild) =>
`${prefix}children:[${coremodStr}?.Card() ?? null,${ogChild}]`,
},
{
match: /children:\[(.+?\.localeName[^\]]*?)]/,
replace: (_, ogChild) => `children:[${coremodStr}.Percentage(${ogChild})]`,
replace: (_, ogChild) => `children:[${coremodStr}?.Percentage(${ogChild}) ?? ${ogChild}]`,
},
],
},
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/coremods/messagePopover/plaintextPatches.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default [
match:
/(Fragment,{children:\[)(.{0,200}children:\[.{0,20}?(\w{1,3})\({.{0,5}\s?key:"copy-id".{0,20}channel:(.{1,3})[,}].{0,20}message:(.{1,3})[,}])/,
replace: (_, prefix, suffix, makeButton, channel, message) =>
`${prefix}...replugged.coremods.coremods.messagePopover._buildPopoverElements(${message},${channel},${makeButton}),${suffix}`,
`${prefix}...(replugged.coremods.coremods.messagePopover?._buildPopoverElements(${message},${channel},${makeButton}) ?? []),${suffix}`,
},
],
},
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/coremods/notices/plaintextPatches.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default [
{
match: /(\w+\.base,children:\[)(.+?}\)),/,
replace: (_, prefix, noticeWrapper) =>
`${prefix}${coremodStr}.AnnouncementContainer({originalRes:${noticeWrapper}}),`,
`${prefix}${coremodStr}?.AnnouncementContainer({originalRes:${noticeWrapper}}) ?? ${noticeWrapper},`,
},
],
},
Expand Down
8 changes: 4 additions & 4 deletions src/renderer/coremods/settings/plaintextPatches.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ export default [
find: "getPredicateSections",
replacements: [
{
match: /this\.props\.sections\.filter\((.+?)\)}/,
replace: (_, sections) =>
`${coremodStr}.insertSections(this.props.sections.filter(${sections}))};`,
match: /(this\.props\.sections\.filter\(.+?\))}/,
replace: (_, filteredSections) =>
`${coremodStr}?.insertSections(${filteredSections}) ?? ${filteredSections}};`,
},
],
},
Expand All @@ -18,7 +18,7 @@ export default [
replacements: [
{
match: /appArch,children:.{0,200}?className:\w+\.line,.{0,100}children:\w+}\):null/,
replace: `$&,${coremodStr}.VersionInfo()`,
replace: `$&,${coremodStr}?.VersionInfo() ?? null`,
},
],
},
Expand Down
5 changes: 2 additions & 3 deletions src/renderer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ type DiscordSplashWindow = Window & {

// Splash screen
if ((window as DiscordSplashWindow).DiscordSplash) {
await replugged.ignition.startSplash();
void replugged.ignition.startSplash();
} else {
await replugged.plugins.loadAll();
await replugged.ignition.ignite();
void replugged.ignition.ignite();
}
29 changes: 13 additions & 16 deletions src/renderer/managers/coremods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,20 +77,17 @@ export async function stopAll(): Promise<void> {
await Promise.allSettled(Object.values(coremods).map((c) => c.stop?.()));
}

export function runPlaintextPatches(): Promise<void> {
return new Promise<void>((res) => {
[
experimentsPlaintext,
notrackPlaintext,
noDevtoolsWarningPlaintext,
messagePopover,
notices,
contextMenu,
languagePlaintext,
commandsPlaintext,
settingsPlaintext,
badgesPlaintext,
].forEach(patchPlaintext);
res();
});
export function runPlaintextPatches(): void {
[
experimentsPlaintext,
notrackPlaintext,
noDevtoolsWarningPlaintext,
messagePopover,
notices,
contextMenu,
languagePlaintext,
commandsPlaintext,
settingsPlaintext,
badgesPlaintext,
].forEach(patchPlaintext);
}
27 changes: 11 additions & 16 deletions src/renderer/managers/ignition.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
import { signalStart, waitForReady } from "../modules/webpack/patch-load";
import { error, log } from "../modules/logger";

import { ready as commonReady } from "@common";
import { ready as componentsReady } from "../modules/components";
import * as i18n from "../modules/i18n";
import * as coremods from "./coremods";
import * as plugins from "./plugins";
import * as themes from "./themes";
import * as quickCSS from "./quick-css";
import { loadStyleSheet } from "../util";
import { startAutoUpdateChecking } from "./updater";
import { interceptChunksGlobal } from "../modules/webpack/patch-load";

export async function start(): Promise<void> {
log("Ignition", "Start", void 0, "Igniting Replugged...");
const startTime = performance.now();

loadStyleSheet("replugged://renderer.css");
i18n.load();
await import("../modules/i18n").then((i18n) => i18n.load());

let started = false;
await Promise.race([
Expand Down Expand Up @@ -83,26 +81,23 @@ Load order:

export async function ignite(): Promise<void> {
// This is the function that will be called when loading the window.
// Plaintext patches are executed before Discord's preload.
await coremods.runPlaintextPatches();
// Plaintext patches must run first.
interceptChunksGlobal();
coremods.runPlaintextPatches();
await plugins.loadAll();
await plugins.runPlaintextPatches();
// These next things will happen after Discord's preload is called.
// We schedule them here, but they cannot block the ignite function from returning.
(async () => {
await waitForReady;
signalStart();
await commonReady();
await componentsReady();
await start();
})();
// At this point, Discord's code should run.
// Wait for the designated common modules to load before continuing.
await Promise.all([commonReady(), componentsReady()]);
await start();
}

export async function startSplash(): Promise<void> {
log("Ignition", "Start", void 0, "Igniting Replugged Splash Screen...");
const startTime = performance.now();

await themes.loadMissing().then(themes.loadAllSplash);
await themes.loadMissing();
themes.loadAllSplash();

log(
"Ignition",
Expand Down
26 changes: 14 additions & 12 deletions src/renderer/managers/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,22 +86,24 @@ export async function start(id: string): Promise<void> {
);
plugin.exports = pluginExports;
await pluginExports.start?.();
if (plugin.hasCSS) {
if (styleElements.has(plugin.manifest.id)) {
// Remove old style element in case it wasn't removed properly
styleElements.get(plugin.manifest.id)?.remove();
}

const el = loadStyleSheet(
`replugged://plugin/${plugin.path}/${plugin.manifest.renderer?.replace(
/\.js$/,
".css",
)}`,
);
styleElements.set(plugin.manifest.id, el);
}
})(),
]);
}

if (plugin.hasCSS) {
if (styleElements.has(plugin.manifest.id)) {
// Remove old style element in case it wasn't removed properly
styleElements.get(plugin.manifest.id)?.remove();
}

const el = loadStyleSheet(
`replugged://plugin/${plugin.path}/${plugin.manifest.renderer?.replace(/\.js$/, ".css")}`,
);
styleElements.set(plugin.manifest.id, el);
}

running.add(plugin.manifest.id);
logger.log(`Plugin started: ${plugin.manifest.name}`);
} catch (e: unknown) {
Expand Down
6 changes: 2 additions & 4 deletions src/renderer/modules/i18n.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { i18n } from "@common";
import type { RepluggedTranslations } from "../../types";

const strings = await RepluggedNative.i18n.getStrings();

export let locale: string | undefined;
export const messages = new Map();

export function load(): void {
loadAllStrings(strings);
export async function load(): Promise<void> {
loadAllStrings(await RepluggedNative.i18n.getStrings());

locale = i18n._chosenLocale;

Expand Down
97 changes: 29 additions & 68 deletions src/renderer/modules/webpack/patch-load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,33 +17,6 @@ import { patchModuleSource } from "./plaintext-patch";
export let wpRequire: WebpackRequire | undefined;
export let webpackChunks: WebpackRawModules | undefined;

let signalReady: () => void;
let ready = false;

/**
* @internal
* @hidden
*/
export const waitForReady = new Promise<void>(
(resolve) =>
(signalReady = () => {
ready = true;
resolve();
}),
);

/**
* @internal
* @hidden
*/
export let signalStart: () => void;

/**
* @internal
* @hidden
*/
export const waitForStart = new Promise<void>((resolve) => (signalStart = resolve));

const patchedModules = new Set<string>();

/**
Expand All @@ -53,8 +26,7 @@ const patchedModules = new Set<string>();
*/
export const sourceStrings: Record<number, string> = {};

async function patchChunk(chunk: WebpackChunk): Promise<void> {
await waitForStart;
function patchChunk(chunk: WebpackChunk): void {
const modules = chunk[1];
for (const id in modules) {
if (patchedModules.has(id)) continue;
Expand Down Expand Up @@ -85,13 +57,13 @@ async function patchChunk(chunk: WebpackChunk): Promise<void> {
function patchPush(webpackChunk: WebpackChunkGlobal): void {
let original = webpackChunk.push;

async function handlePush(chunk: WebpackChunk): Promise<unknown> {
await patchChunk(chunk);
function handlePush(chunk: WebpackChunk): unknown {
patchChunk(chunk);
return original.call(webpackChunk, chunk);
}

// https://github.com/Vendicated/Vencord/blob/e4701769a5b8e0a71dba0e26bc311ff6e34eadf7/src/webpack/patchWebpack.ts#L93-L98
handlePush.bind = (...args: unknown[]) => original.bind([...args]);
// From YofukashiNo: https://discord.com/channels/1000926524452647132/1000955965304221728/1258946431348375644
handlePush.bind = original.bind.bind(original);

Object.defineProperty(webpackChunk, "push", {
get: () => handlePush,
Expand All @@ -114,6 +86,9 @@ function loadWebpackModules(chunksGlobal: WebpackChunkGlobal): void {
if (wpRequire.c && !webpackChunks) webpackChunks = wpRequire.c;

if (r) {
// The first batch of modules are added inline via r.m rather than being pushed
patchChunk([[], r.m]);

r.d = (module: unknown, exports: Record<string, () => unknown>) => {
for (const prop in exports) {
if (
Expand All @@ -136,46 +111,32 @@ function loadWebpackModules(chunksGlobal: WebpackChunkGlobal): void {
// Patch previously loaded chunks
if (Array.isArray(chunksGlobal)) {
for (const loadedChunk of chunksGlobal) {
void patchChunk(loadedChunk);
patchChunk(loadedChunk);
}
}

patchPush(chunksGlobal);
signalReady();

// There is some kind of race condition where chunks are not patched ever, so this should make sure everything gets patched
// This is a temporary workaround that should be removed once we figure out the real cause
setInterval(() => {
if (Array.isArray(chunksGlobal)) {
for (const loadedChunk of chunksGlobal) {
void patchChunk(loadedChunk);
}
}
}, 1000);
}

// Intercept the webpack chunk global as soon as Discord creates it

// Because using a timer is bad, thanks Ven
// https:/Vendicated/Vencord/blob/ef353f1d66dbf1d14e528830d267aac518ed1beb/src/webpack/patchWebpack.ts
let webpackChunk: WebpackChunkGlobal | undefined;

if (window.webpackChunkdiscord_app) {
webpackChunk = window.webpackChunkdiscord_app;
loadWebpackModules(webpackChunk!);
} else {
Object.defineProperty(window, "webpackChunkdiscord_app", {
get: () => webpackChunk,
set: (v) => {
// Only modify if the global has actually changed
// We don't need to check if push is the special webpack push,
// because webpack will go over the previously loaded modules
// when it sets the custom push method.
if (v !== webpackChunk && !ready) {
loadWebpackModules(v);
}
webpackChunk = v;
},
configurable: true,
});
export function interceptChunksGlobal(): void {
if (window.webpackChunkdiscord_app) {
loadWebpackModules(window.webpackChunkdiscord_app);
} else {
let webpackChunk: WebpackChunkGlobal | undefined;
Object.defineProperty(window, "webpackChunkdiscord_app", {
get: () => webpackChunk,
set: (v) => {
// Only modify if the global has actually changed
// We don't need to check if push is the special webpack push,
// because webpack will go over the previously loaded modules
// when it sets the custom push method.
if (v !== webpackChunk) {
loadWebpackModules(v);
}
webpackChunk = v;
},
configurable: true,
});
}
}
2 changes: 1 addition & 1 deletion src/types/webpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export type WebpackRawModules = Record<string | number, RawModule>;
export type WebpackRequire = ((e: number) => unknown) & {
c?: WebpackRawModules;
d: (module: unknown, exports: Record<string, () => unknown>) => void;
m: WebpackChunk;
m: WebpackChunk[1];
};

export type WebpackModule = (
Expand Down
Loading