This commit is contained in:
thororen1234 2025-02-07 19:23:42 -05:00
parent dc4e9cf775
commit eefecda52e
7 changed files with 4 additions and 302 deletions

View file

@ -11,7 +11,7 @@ import { Patch } from "@utils/types";
import { TypeWebpackSearchHistory } from "@webpack";
interface EvaledPatch extends Patch {
id: number | string;
id: PropertyKey;
}
interface ErroredPatch extends EvaledPatch {
oldModule: string,

View file

@ -1,158 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { definePluginSettings } from "@api/Settings";
import { Devs, WEBPACK_CHUNK } from "@utils/constants";
import { makeLazy } from "@utils/lazy";
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types";
import { findByProps, wreq } from "@webpack";
import { Button, Flex, Forms, Switch, Text, Timestamp, useState } from "@webpack/common";
import TarFile from "./tar";
import * as Webpack from "./webpack";
export const settings = definePluginSettings({
patched: {
type: OptionType.BOOLEAN,
default: true,
description: "Include patched modules",
restartNeeded: true,
},
});
export default definePlugin({
name: "WebpackTarball",
description: "Converts Discord's webpack sources into a tarball.",
authors: [Devs.Kyuuhachi],
settings,
toolboxActions: {
"Webpack Tarball"() {
const key = openModal(props => (
<TarModal
modalProps={props}
close={() => closeModal(key)}
/>
));
}
},
});
export const getBuildNumber = makeLazy(() => {
try {
const metrics = findByProps("_getMetricWithDefaults")._flush.toString();
const [, builtAt, buildNumber] = metrics.match(/\{built_at:"(\d+)",build_number:"(\d+)"\}/);
return { buildNumber, builtAt: new Date(Number(builtAt)) };
} catch (e) {
console.error("failed to get build number:", e);
return { buildNumber: "unknown", builtAt: new Date() };
}
});
async function saveTar(patched: boolean) {
const tar = new TarFile();
const { buildNumber, builtAt } = getBuildNumber();
const mtime = (builtAt.getTime() / 1000) | 0;
const root = patched ? `vencord-${buildNumber}` : `discord-${buildNumber}`;
for (const [id, module] of Object.entries(wreq.m)) {
const patchedSrc = Function.toString.call(module);
const originalSrc = module.toString();
if (patched && patchedSrc !== originalSrc)
tar.addTextFile(
`${root}/${id}.v.js`,
`webpack[${JSON.stringify(id)}] = ${patchedSrc}\n`,
{ mtime },
);
tar.addTextFile(
`${root}/${id}.js`,
`webpack[${JSON.stringify(id)}] = ${originalSrc}\n`,
{ mtime },
);
}
tar.save(`${root}.tar`);
}
function TarModal({ modalProps, close }: { modalProps: ModalProps; close(): void; }) {
const { buildNumber, builtAt } = getBuildNumber();
const [, rerender] = useState({});
const [isLoading, setLoading] = useState(false);
const paths = Webpack.getChunkPaths(wreq);
const status = Object.entries(Webpack.getLoadedChunks(wreq))
.filter(([k]) => wreq.o(paths, k))
.map(([, v]) => v);
const loading = status.length;
const loaded = status.filter(v => v === 0 || v === undefined).length;
const errored = status.filter(v => v === undefined).length;
const all = Object.keys(paths).length;
const { patched } = settings.use(["patched"]);
return (
<ModalRoot {...modalProps}>
<ModalHeader>
<Forms.FormTitle tag="h2">
Webpack Tarball
</Forms.FormTitle>
<Text variant="text-md/normal">
<Timestamp timestamp={new Date(builtAt)} isInline={false}>
{"Build number "}
{buildNumber}
</Timestamp>
</Text>
<ModalCloseButton onClick={close} />
</ModalHeader>
<ModalContent>
<div style={{ marginTop: "8px", marginBottom: "24px" }}>
<Forms.FormTitle>
Lazy chunks
</Forms.FormTitle>
<Flex align={Flex.Align.CENTER}>
<Text
variant="text-md/normal"
style={{ flexGrow: 1 }}
>
{loaded}/{all}
{errored ? ` (${errored} errors)` : null}
</Text>
<Button
disabled={loading === all || isLoading}
onClick={async () => {
setLoading(true);
// @ts-ignore
await Webpack.protectWebpack(window[WEBPACK_CHUNK], async () => {
await Webpack.forceLoadAll(wreq, rerender);
});
}}
>
{loaded === all ? "Loaded" : loading === all ? "Loading" : "Load all"}
</Button>
</Flex>
</div>
<Switch
value={patched}
onChange={v => settings.store.patched = v}
hideBorder
>
{settings.def.patched.description}
</Switch>
</ModalContent>
<ModalFooter>
<Button
onClick={() => {
saveTar(patched);
close();
}}
>
Create
</Button>
</ModalFooter>
</ModalRoot>
);
}

View file

@ -1,74 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
export type Metadata = { mtime?: number; };
export default class TarFile {
buffers: ArrayBuffer[];
constructor() {
this.buffers = [];
}
addTextFile(name: string, text: string, metadata: Metadata = {}) {
this.addFile(name, new TextEncoder().encode(text), metadata);
}
addFile(name: string, data: Uint8Array, { mtime = 0 }: Metadata = {}) {
this.buffers.push(this.header([
[100, name.toString()], // name
[8, 0o644], // mode
[8, 0o1000], // uid
[8, 0o1000], // gid
[12, data.length], // size
[12, mtime], // mtime
[8, null], // checksum
[1, "0"], // type
[100, ""], // name of linked file (??)
[255, ""], // padding
]));
this.buffers.push(data.buffer as ArrayBuffer);
this.buffers.push(new ArrayBuffer(-data.length & 0x1FF));
}
header(fields: [number, number | string | null][]) {
const buffer = new ArrayBuffer(512);
const u1 = new Uint8Array(buffer);
let checksum = 0;
let checksumPos: number = null!;
let pos = 0;
for (const [size, val] of fields) {
let string: string;
if (val === null) {
checksumPos = pos;
string = " ".repeat(size);
} else if (typeof val === "string") {
string = val;
} else if (typeof val === "number") {
string = val.toString(8).padStart(size - 1, "0");
} else {
throw new Error("invalid value", val);
}
if (string.length > size) throw new Error(`${string} is longer than ${size} characters`);
Array.from(string).forEach((c, i) => checksum += u1[pos + i] = c.charCodeAt(0));
pos += size;
}
Array.from("\0".repeat(8)).forEach((c, i) => u1[checksumPos + i] = c.charCodeAt(0));
Array.from(checksum.toString(8).padStart(7, "0")).forEach((c, i) => u1[checksumPos + i] = c.charCodeAt(0));
return buffer;
}
save(filename: string) {
const a = document.createElement("a");
a.href = URL.createObjectURL(new Blob(this.buffers, { "type": "application/x-tar" }));
a.download = filename;
a.style.display = "none";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(a.href);
}
}

View file

@ -1,66 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import type { WebpackInstance } from "discord-types/other";
export async function protectWebpack<T>(webpack: any[], body: () => Promise<T>): Promise<T> {
const prev_m = Object.getOwnPropertyDescriptor(Function.prototype, "m")!;
Object.defineProperty(Function.prototype, "m", {
get() { throw "get require.m"; },
set() { throw "set require.m"; },
enumerable: true,
configurable: true,
});
try {
return await body();
} finally {
Object.defineProperty(Function.prototype, "m", prev_m);
}
}
export function getLoadedChunks(wreq: WebpackInstance): { [chunkId: string | symbol]: 0 | undefined; } {
const { o } = wreq;
try {
wreq.o = (a: any) => { throw a; };
wreq.f.j();
} catch (e: any) {
return e;
} finally {
wreq.o = o;
}
throw new Error("getLoadedChunks failed");
}
export function getChunkPaths(wreq: WebpackInstance): { [chunkId: string]: string; } {
const sym = Symbol("getChunkPaths");
try {
Object.defineProperty(Object.prototype, sym, {
get() { throw this; },
set() { },
configurable: true,
});
wreq.u(sym);
} catch (e: any) {
return e;
} finally {
// @ts-ignore
delete Object.prototype[sym];
}
throw new Error("getChunkPaths failed");
}
export async function forceLoadAll(wreq: WebpackInstance, on_chunk: (id: string) => void = () => { }) {
const chunks = getChunkPaths(wreq);
const loaded = getLoadedChunks(wreq);
const ids = Object.keys(chunks).filter(id => loaded[id] !== 0);
await Promise.all(ids.map(async id => {
try {
await wreq.e(id as any);
} catch { }
on_chunk(id);
}));
}

View file

@ -229,6 +229,7 @@ export default definePlugin({
if (IS_DEV) return " (Dev)";
if (IS_WEB) return " (Web)";
if (IS_VESKTOP) return ` (Vesktop v${VesktopNative.app.getVersion()})`;
if (IS_EQUIBOP) return `Equibop v${VesktopNative.app.getVersion()}`;
if (IS_STANDALONE) return " (Standalone)";
return "";
},

View file

@ -315,7 +315,7 @@ export 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;