Skip to content

Commit

Permalink
Merge pull request #8 from LosersUnited/feat-code-injection
Browse files Browse the repository at this point in the history
Add replacement implementation stores for client mods that don't have specific functions
  • Loading branch information
Davilarek authored Jun 25, 2024
2 parents bc40618 + ec51f87 commit 548bd0a
Show file tree
Hide file tree
Showing 10 changed files with 442 additions and 15 deletions.
103 changes: 102 additions & 1 deletion src/api/RuntimeGenerators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function createFunctionFromPath(path: string) {
* @returns A function that returns the value of the specified property from the object.
*/
export function createFunctionFromObjectProperty(objectName: string, property: string) {
const generatedFunction: () => any = new Function(`return () => ${objectName}.${property}`)();
const generatedFunction: (...args: any[]) => any = new Function(`return () => ${objectName}.${property}`)();
Object.defineProperty(generatedFunction, "object", {
value: objectName,
});
Expand All @@ -27,3 +27,104 @@ export function createFunctionFromObjectProperty(objectName: string, property: s
});
return generatedFunction;
}
export function createFunctionWithWrapperNeeded(objectName: string, property: string, wrapperName: string) {
const result = createFunctionFromObjectProperty(objectName, property);
Object.defineProperty(result, "wrapperName", { value: wrapperName });
return result;
}

import { FunctionImplementation, __requireInternal, doesImplement, implementationStores, initStores } from "../common/index.js";
import { createJavaScriptFromObject, getKeyValue } from "../utils.js";
import { parse } from "@babel/parser";
import { IModImplementation } from "./ModImplementation.js";
import { IMPLEMENTATION_STORES_PATH_REQ, IMPLEMENTATION_STORES_PATH_SOURCE, IMPLEMENTATION_STORES_PATH_VAR_NAME } from "../constants.js";
/**
* this is really wrong, TODO: fix this piece of... garbage
*/
export async function addCode(mod: IModImplementation) {
// let rawCode = "globalThis.implementationStores = {\n"; // TODO: fix, this is awful
// for (const key in implementationStores) {
// if (Object.prototype.hasOwnProperty.call(implementationStores, key)) {
// const element = implementationStores[key].implementationStore;
// rawCode += `\t${key}: {\n`;
// for (const key2 in element) {
// if (Object.prototype.hasOwnProperty.call(element, key2)) {
// }
// }
// }
// }
await initStores();
const constructed: {
[key: string]: {
[key: string]: FunctionImplementation;
// [key: string]: string;
}
// [key: string]: string,
} = {};
for (const key in implementationStores) {
if (Object.prototype.hasOwnProperty.call(implementationStores, key)) {
// constructed[key] = {
// ...implementationStores[key].implementationStore,
// };
constructed[key] = {};
for (const key2 in implementationStores[key].implementationStore) {
if (Object.prototype.hasOwnProperty.call(implementationStores[key].implementationStore, key2)) {
const element = implementationStores[key].implementationStore[key2];
if (doesImplement(mod, key, key2)) continue;
if (element.isWrapper === true) {
const categoryObj = getKeyValue(mod, key as keyof IModImplementation);
const unWrapped = getKeyValue(categoryObj, element.supplies as never) as any;
if (unWrapped.wrapperName != key2) continue;
}
if (element.func.toString().includes(__requireInternal.name)) {
// const regex = new RegExp(__requireInternal.name + "\\(([^)]+)\\)", 'g');
// const match = element.func.toString().match(regex);
// if (!match)
// continue;
// const args = match[1].split(',').map(value => value.trim());
// args.shift();
// console.log(args);
// // const constructed2 = element.func.toString().replace(new RegExp(__requireInternal.name + "\\([^,]+,\\s*", 'g'), "globalThis.implementationStores_require(") + "}.func";
// // element.func = new Function("return {" + constructed2)();
// // element.func = (mod as { [key: string]: any })[args[0]][args[1]];
const regexPattern = new RegExp(`${__requireInternal.name}\\(([^)]+)\\)`, "g");
let match;
while ((match = regexPattern.exec(element.func.toString())) !== null) {
const argsStr = match[1];
const args = argsStr.split(',').map(x => x.trim()).map(x => x.startsWith("\"") && x.endsWith("\"") ? x.slice(1, -1) : x);
args.shift();

// const replacement = "globalThis.implementationStores_require(" + someValues.join(",") + ")";
// const result = element.func.toString().replace(match[0], replacement);
try {
const findResult = (mod as { [key: string]: any })[args[0]][args[1]];
if (findResult.wrapperName && args[2] != "true") {
args[1] = findResult.wrapperName;
throw new Error();
}
// const findResult = __requireInternal(mod, args[0], args[1]) as unknown as any;
// element.func = findResult;
element.func = new Function("return {" + element.func.toString().replace(match[0], `${findResult.object}.${findResult.property}`) + "}.func")();
}
catch (error) {
// console.error(mod, args, error);
console.error((mod as { [key: string]: any })[args[0]], (mod as { [key: string]: any })[args[0]][args[1]]);
const replacement = `${IMPLEMENTATION_STORES_PATH_SOURCE}.${IMPLEMENTATION_STORES_PATH_REQ}(${IMPLEMENTATION_STORES_PATH_SOURCE}.${IMPLEMENTATION_STORES_PATH_VAR_NAME}["${args[0]}"]["${args[1]}"])`;
element.func = new Function("return {" + element.func.toString().replace(match[0], replacement) + "}.func")();
}
}
}
constructed[key][key2] = element;
}
}
}
}
// const rawCode = "globalThis.implementationStores = {\n" + getMain(serializer).serialize(constructed) + "\n}";
const req = (target: any) => new Function("return {" + target.func + "}.func;")();
const rawCode =
`${IMPLEMENTATION_STORES_PATH_SOURCE}.${IMPLEMENTATION_STORES_PATH_VAR_NAME} = (${createJavaScriptFromObject(constructed, true)});
${IMPLEMENTATION_STORES_PATH_SOURCE}.${IMPLEMENTATION_STORES_PATH_REQ} = ${req.toString()};`;
// console.log(rawCode);
const rawCodeAst = parse(rawCode);
return rawCodeAst.program.body;
}
2 changes: 1 addition & 1 deletion src/api/Webpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ class DummyWebpackApi implements IBaseWebpackApi {
}
}

export const WebpackApi = new DummyWebpackApi();
export const WebpackApi = new DummyWebpackApi();
13 changes: 8 additions & 5 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import { readFileSync, readdirSync, writeFileSync } from "fs";
import * as url from 'url';
import converter from "./converter.js";
import { File } from "@babel/types";
import { myPackageName } from "./utils.js";
import { getMain, myPackageName } from "./utils.js";
import { transformSync } from "@babel/core";
import { IModImplementation } from "./api/ModImplementation.js";
// import { addCode } from "./api/RuntimeGenerators.js";

if (process.argv.length != 5) {
console.error(`Usage:\n\t${myPackageName} <input file> <target client mod> <output file>\nExample:\n\t${myPackageName} ./index.js BetterDiscord ./dist/index.js`);
Expand Down Expand Up @@ -39,21 +40,23 @@ if (!isClientModSupported) {
console.error("Supported client mods: " + supportedClientMods.join(", "));
process.exit(1);
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
// addCode().then(_ => process.exit(0));
// // process.exit(0);
const filler = import(url.pathToFileURL(`${__dirname}/converters/${targetDiscordMod}.js`).href);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
filler.then((x: { default: IModImplementation }) => {
filler.then(async (x: { default: IModImplementation }) => {
if (x.default.importsForbidden)
console.warn('\x1b[33m%s\x1b[0m', `Warning: Target mod ${targetDiscordMod} requires your code to be bundled into single file`);
const out = converter(ast as File & { errors: [] }, x);
const out = await converter(ast as File & { errors: [] }, x);
const outMod = {
...ast,
program: {
...ast.program,
body: out,
},
} as File;
const generate = typeof generate_ == "function" ? generate_ : (generate_ as { default: typeof generate_ }).default;
const generate = getMain(generate_);
const final = generate(outMod).code;
console.log("generated code: ", final);
writeFileSync(process.argv[4], final);
Expand Down
82 changes: 82 additions & 0 deletions src/common/WebpackApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { FunctionImplementation, __requireInternal } from "./index.js";
import { IModImplementation } from "../api/ModImplementation.js";

// const reservedData = {} as { [key: string]: any };

export let targetMod: IModImplementation;

const implementationStore = {
getModule: new FunctionImplementation({
depends: [],
supplies: "getModule",
// get data() {
// return reservedData[this.supplies];
// },
// set data(v: any) {
// reservedData[this.supplies] = v;
// },
data: {},
func(filter: (mod: any) => boolean) {
if (this.data.req == undefined) {
// @ts-expect-error Non-standard property
window.webpackChunkdiscord_app.push(
[[Symbol()],
{},
(r: { c: any; }) =>
this.data = this.data ?? {
req: r,
},
]);
// @ts-expect-error Non-standard property
window.webpackChunkdiscord_app.pop();
}
// @ts-expect-error too lazy
return Object.values(this.data.req).find(filter)?.exports;
},
}),
getModuleRawToExportedWrapper: new FunctionImplementation({
depends: [],
supplies: "getModule",
data: null,
isWrapper: true,
func(filter: (mod: any) => boolean) {
const originalGetModule = __requireInternal(targetMod, "WebpackApi", "getModule", true);
return originalGetModule!((x: { exports: any; }) => Object.values(x?.exports || {})?.some(filter));
},
}),
getByStrings: new FunctionImplementation({
depends: ["getModule"],
supplies: "getByStrings",
data: null,
func(...strings) {
/* __requireInternal(targetMod, "WebpackApi", "test")!(); */
const getModule = __requireInternal(targetMod, "WebpackApi", "getModule");
if (!getModule)
throw new Error("Unimplemented");
return getModule((module: any) => {
if (!module?.toString || typeof (module?.toString) !== "function") return; // Not stringable
let moduleString = "";
try { moduleString = module?.toString([]); }
catch (err) { moduleString = module?.toString(); }
if (!moduleString) return false; // Could not create string
for (const s of strings) {
if (!moduleString.includes(s)) return false;
}
return true;
});
},
}),
test: new FunctionImplementation({
data: null,
depends: ["getByStrings"],
supplies: "test",
// eslint-disable-next-line @typescript-eslint/no-unused-vars
func(...args: any[]) {
debugger;
return "the test worked";
},
}),
} as { [key: string]: FunctionImplementation };
export {
implementationStore,
};
82 changes: 82 additions & 0 deletions src/common/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { IModImplementation } from "../api/ModImplementation";
import { getKeyValue } from "../utils.js";

export interface IFunctionImplementation {
supplies: string,
depends: string[],
data: any,
func: (...args: any[]) => any,
isWrapper?: boolean,
}
class FunctionImplementation implements IFunctionImplementation {
supplies: string;
depends: string[];
data: any;
func: (...args: any[]) => any;
isWrapper?: boolean | undefined;
// constructor(supplies: string, depends: string[], data: any, func: (...args: any[]) => any) {
// this.supplies = supplies;
// this.depends = depends;
// this.data = data;
// this.func = func;
// }
constructor(options: IFunctionImplementation) {
const { supplies, depends, data, func, isWrapper } = options;
this.supplies = supplies!;
this.depends = depends!;
Object.defineProperty(this, "data", { value: data, enumerable: data !== null });
this.func = func!;
// this.isWrapper = isWrapper === true;
Object.defineProperty(this, "isWrapper", { value: isWrapper, enumerable: isWrapper === true });
}
}
export {
FunctionImplementation,
};
// import * as WebpackImplementations from "./Webpack.js";
import { createFunctionFromObjectProperty } from "../api/RuntimeGenerators.js";
import { readdirSync } from "fs";
import * as url from 'url';
import * as path from 'path';
import { IMPLEMENTATION_STORES_PATH_SOURCE, IMPLEMENTATION_STORES_PATH_VAR_NAME } from "../constants.js";
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));

export const implementationStores = {
// "Webpack": WebpackImplementations,
} as { [category: string]: { implementationStore: { [key: string]: FunctionImplementation }, targetMod: IModImplementation } };
export async function initStores() {
const stores = readdirSync(`${__dirname}`).filter(x => !x.startsWith("index."));
console.log(stores);
for (let index = 0; index < stores.length; index++) {
const filler = import(url.pathToFileURL(`${__dirname}/${stores[index]}`).href);
const mod = await filler;
implementationStores[path.parse(stores[index]).name] = mod;
}
console.log("done");
}
export function doesImplement(mod: IModImplementation, category: string, method: string) {
const categoryObj = getKeyValue(mod, category as keyof IModImplementation);
return getKeyValue(categoryObj, method as never) != undefined;
}

export function __requireInternal(mod: IModImplementation, category: string, method: string, ignoreWrappers: boolean = false) {
if (doesImplement(mod, category, method)) {
const categoryObj = getKeyValue(mod, category as keyof IModImplementation);
const result = getKeyValue(categoryObj, method as never);
if (ignoreWrappers)
return result;
else {
if (result["wrapperName"]) {
method = result["wrapperName"];
}
else
return result;
}
}
if (implementationStores[category].targetMod !== undefined)
implementationStores[category].targetMod = mod;
const foundImplementation = implementationStores[category].implementationStore[method];
if (foundImplementation == undefined)
return null; // depends failed
return createFunctionFromObjectProperty(`${IMPLEMENTATION_STORES_PATH_SOURCE}.${IMPLEMENTATION_STORES_PATH_VAR_NAME}.${category}`, method);
}
3 changes: 3 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
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";
Loading

0 comments on commit 548bd0a

Please sign in to comment.