From 8b1b28fd81e9575ac9668e6ec6017954e97f13c3 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 25 Jul 2024 20:29:19 +0200 Subject: [PATCH] Add basic support for Vencord conversion target --- .gitignore | 1 + src/constants.ts | 3 + src/converter.ts | 2 +- src/converters/vencord.ts | 119 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 src/converters/vencord.ts diff --git a/.gitignore b/.gitignore index da5b716..d506253 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ dist src/converter copy.ts test/sample/betterdiscord/index.js test/sample/replugged/index.js +test/sample/vencord/index.js diff --git a/src/constants.ts b/src/constants.ts index cdf273c..fe7b07b 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,3 +1,6 @@ export const IMPLEMENTATION_STORES_PATH_SOURCE = "globalThis"; export const IMPLEMENTATION_STORES_PATH_VAR_NAME = "implementationStores"; export const IMPLEMENTATION_STORES_PATH_REQ = IMPLEMENTATION_STORES_PATH_VAR_NAME + "_require"; +export const BV_PATCHER_COMMIT_HASH = "4f566d27074995882422ce3874236a514890263a"; +export const BV_PATCHER_SOURCE_URL = (commitHash: string) => `https://github.com/Davilarek/Vencord/raw/${commitHash}/src/plugins/bdCompatLayer/stuffFromBD.js`; +export const VENCORD_PATCHER_GLOBAL_NAME = "VencordSharedPatcher"; diff --git a/src/converter.ts b/src/converter.ts index 47fe08d..f573c10 100644 --- a/src/converter.ts +++ b/src/converter.ts @@ -225,5 +225,5 @@ export default async function (ast: ParseResult, targetedDiscordModApiLibr parsedBodyWithoutOurImports.unshift(...await addCode(targetedDiscordModApiLibrary.default)); if ((targetedDiscordModApiLibrary as { default: IModImplementation } & { convertFormat: (ast_: Statement[]) => Statement[] }).convertFormat == undefined) return parsedBodyWithoutOurImports; - return (targetedDiscordModApiLibrary as { default: IModImplementation } & { convertFormat: (ast_: Statement[]) => Statement[] }).convertFormat(parsedBodyWithoutOurImports); + return await (targetedDiscordModApiLibrary as { default: IModImplementation } & { convertFormat: (ast_: Statement[]) => Statement[] }).convertFormat(parsedBodyWithoutOurImports); } diff --git a/src/converters/vencord.ts b/src/converters/vencord.ts new file mode 100644 index 0000000..a6de610 --- /dev/null +++ b/src/converters/vencord.ts @@ -0,0 +1,119 @@ +import { IBaseWebpackApi } from "../api/Webpack.js"; +import { parse } from "@babel/parser"; +import { IBasePatcherApi } from "../api/Patcher.js"; +import { IModImplementation } from "../api/ModImplementation.js"; +import { createFunctionFromObjectProperty, createFunctionWithWrapperNeeded } from "../api/RuntimeGenerators.js"; +import { addComment, assignmentExpression, ClassDeclaration, classExpression, emptyStatement, ExportDefaultDeclaration, identifier, isExportDefaultDeclaration, logicalExpression, memberExpression, removeComments, Statement, toExpression, toStatement, traverse, variableDeclaration, variableDeclarator } from "@babel/types"; +import { BV_PATCHER_COMMIT_HASH, BV_PATCHER_SOURCE_URL, VENCORD_PATCHER_GLOBAL_NAME } from "../constants.js"; + +class VCWebpackApi implements IBaseWebpackApi { + get getModule() { + return createFunctionFromObjectProperty("Vencord.Webpack", "find"); + // return undefined as unknown as any; + // return createFunctionThatIsMissing(); + // return { wrapperName: "getModule" } as any; + } +} + +class VCPatcherApi implements IBasePatcherApi { + get constructor_() { + return createFunctionWithWrapperNeeded(VENCORD_PATCHER_GLOBAL_NAME, "undefined", "Patcher_constructor") as any; + } + // constructor() { + // return this.constructor_; + // } + internalId: undefined; + get unpatchAll() { + return createFunctionWithWrapperNeeded(VENCORD_PATCHER_GLOBAL_NAME, "unpatchAll", "unpatchAllWrapper"); + } + get after() { + return createFunctionWithWrapperNeeded(VENCORD_PATCHER_GLOBAL_NAME, "after", "afterWrapper"); + } + get before() { + return createFunctionWithWrapperNeeded(VENCORD_PATCHER_GLOBAL_NAME, "before", "beforeWrapper"); + } + get instead() { + return createFunctionWithWrapperNeeded(VENCORD_PATCHER_GLOBAL_NAME, "instead", "insteadWrapper"); + } +} + +export async function convertFormat(ast: Statement[]) { + const response = await fetch(BV_PATCHER_SOURCE_URL(BV_PATCHER_COMMIT_HASH)); + const parsed = parse((await response.text()).replace("BdApi.Webpack.findByUniqueProperties", "Vencord.Webpack.findByProps"), { sourceType: "module" }).program.body; + const patcherClass = parsed.find(x => x.type == "ClassDeclaration" && x.id?.name == "Patcher") as ClassDeclaration; + if (!patcherClass) + return ast; + const originalComment = patcherClass.leadingComments; + traverse(patcherClass, { + enter(node) { + removeComments(node); + }, + }); + // patcherClass.leadingComments = originalComment; + const memberExpr = memberExpression(identifier("window"), identifier(VENCORD_PATCHER_GLOBAL_NAME)); + const assigExpr = assignmentExpression("=", memberExpr, logicalExpression("??", memberExpr, classExpression(null, null, patcherClass.body))); + assigExpr.leadingComments = originalComment; + ast.unshift(toStatement(assigExpr)); + ast.unshift(addComment(emptyStatement(), "leading", " eslint-disable simple-header/header ")); + + for (let i = 0; i < ast.length; i++) { + if (isExportDefaultDeclaration(ast[i])) { + const exportDefault = ast[i] as ExportDefaultDeclaration; + const declaration = exportDefault.declaration as ClassDeclaration; + const variableName = 'defaultExport'; + const varDeclarator = variableDeclarator(identifier(variableName), toExpression(declaration)); + const varDeclaration = variableDeclaration('const', [varDeclarator]); + ast[i] = varDeclaration; + const pluginTemplateCode = ` + import definePlugin from "@utils/types"; + import { wreq, beforeInitListeners } from "@webpack"; + if (Array.prototype.find.call([...beforeInitListeners], x => x && x.toString().includes("writeable")) === undefined) { + beforeInitListeners.add(() => { + console.log("Making exports writeable..."); + if (wreq.d.toString().includes("set:")) + { + console.log("Already writeable"); + return; + } + wreq.d = (target, exports) => { + for (const key in exports) { + if (!Reflect.has(exports, key)) continue; + Object.defineProperty(target, key, { + get: () => exports[key](), + set: v => { exports[key] = () => v; }, + enumerable: true, + configurable: false + }); + } + }; + }); + } + const thePlugin = { + name: "${declaration.id?.name}", + description: "${declaration.id?.name} plugin", + authors: [ + { + id: 0n, + name: "Anonymous" + } + ], + instance: (() => new ${variableName}())(), + start() { + this.instance.start(); + }, + stop() { + this.instance.stop(); + }, + }; + export default definePlugin(thePlugin);`; + ast.push(...parse(pluginTemplateCode, { sourceType: "module" }).program.body); + break; + } + } + return ast; +} + +export default { + WebpackApi: new VCWebpackApi(), + PatcherApi: VCPatcherApi.prototype, +} as IModImplementation;