this repo has no description

LunAST: Trigger processors from other processors

Changed files
+196 -66
packages
core
src
lunast
+25 -11
packages/core/src/patch.ts
···
}
}
+
// Populate the module cache
+
for (const [id, func] of Object.entries(entry)) {
+
if (!Object.hasOwn(moduleCache, id) && func.__moonlight !== true) {
+
moduleCache[id] = func.toString().replace(/\n/g, "");
+
}
+
}
+
for (const [id, func] of Object.entries(entry)) {
if (func.__moonlight === true) continue;
-
let moduleString = Object.hasOwn(moduleCache, id)
-
? moduleCache[id]
-
: func.toString().replace(/\n/g, "");
+
let moduleString = moduleCache[id];
for (let i = 0; i < patches.length; i++) {
const patch = patches[i];
···
}
}
+
moduleCache[id] = moduleString;
+
try {
-
let parsed = moonlight.lunast.parseScript(id, `(\n${moduleString}\n)`);
+
const parsed = moonlight.lunast.parseScript(id, moduleString);
if (parsed != null) {
-
// parseScript adds an extra ; for some reason
-
parsed = parsed.trimEnd().substring(0, parsed.lastIndexOf(";"));
-
if (patchModule(id, "lunast", parsed)) {
-
moduleString = parsed;
+
for (const [parsedId, parsedScript] of Object.entries(parsed)) {
+
// parseScript adds an extra ; for some reason
+
const fixedScript = parsedScript
+
.trimEnd()
+
.substring(0, parsedScript.lastIndexOf(";"));
+
+
if (patchModule(parsedId, "lunast", fixedScript)) {
+
moduleCache[parsedId] = fixedScript;
+
}
}
}
} catch (e) {
···
!entry[id].__moonlight
) {
const wrapped =
-
`(${moduleString}).apply(this, arguments)\n` +
+
`(${moduleCache[id]}).apply(this, arguments)\n` +
`//# sourceURL=Webpack-Module-${id}`;
entry[id] = new Function(
"module",
···
entry[id].__moonlight = true;
}
}
-
-
moduleCache[id] = moduleString;
}
}
···
*/
export async function installWebpackPatcher() {
await handleModuleDependencies();
+
+
moonlight.lunast.setModuleSourceGetter((id) => {
+
return moduleCache[id] ?? null;
+
});
let realWebpackJsonp: WebpackJsonp | null = null;
Object.defineProperty(window, "webpackChunkdiscord_app", {
+1 -1
packages/lunast/README.md
···
Not really. LunAST runs in roughly ~10ms on [my](https://github.com/NotNite) machine, with filtering for what modules to parse. Parsing every module takes only a second. There are future plans to cache and parallelize the process, so that load times are only slow once.
-
You can measure how long LunAST took to process with the `moonlight.lunast.elapsed` variable
+
You can measure how long LunAST took to process with the `moonlight.lunast.elapsed` variable.
### Does this mean patches are dead?
+49 -20
packages/lunast/src/index.ts
···
>;
private processors: Processor[];
private defaultRequire?: (id: string) => any;
+
private getModuleSource?: (id: string) => string;
elapsed: number;
···
return "dev";
}
-
public parseScript(id: string, code: string): string | null {
+
public parseScript(id: string, code: string): Record<string, string> {
const start = performance.now();
const available = [...this.processors]
.sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0))
-
.filter((x) =>
-
x.find != null
-
? typeof x.find === "string"
-
? code.indexOf(x.find) !== -1
-
: x.find.test(code)
-
: true
-
)
-
.filter((x) =>
-
x.dependencies != null
-
? x.dependencies.every((dep) => this.successful.has(dep))
-
: true
-
);
-
if (available.length === 0) return null;
+
.filter((x) => {
+
if (x.find == null) return true;
+
const finds = Array.isArray(x.find) ? x.find : [x.find];
+
return finds.every((find) =>
+
typeof find === "string" ? code.indexOf(find) !== -1 : find.test(code)
+
);
+
})
+
.filter((x) => x.manual !== true);
+
+
const ret = this.parseScriptInternal(id, code, available);
+
+
const end = performance.now();
+
this.elapsed += end - start;
+
+
return ret;
+
}
+
+
// This is like this so processors can trigger other processors while they're parsing
+
private parseScriptInternal(
+
id: string,
+
code: string,
+
processors: Processor[]
+
) {
+
const ret: Record<string, string> = {};
+
if (processors.length === 0) return ret;
-
const module = parseFixed(code);
+
// Wrap so the anonymous function is valid JS
+
const module = parseFixed(`(\n${code}\n)`);
let dirty = false;
const state: ProcessorState = {
id,
···
lunast: this,
markDirty: () => {
dirty = true;
+
},
+
trigger: (id, tag) => {
+
const source = this.getModuleSourceById(id);
+
if (source == null) return;
+
if (this.successful.has(tag)) return;
+
const processor = this.processors.find((x) => x.name === tag);
+
if (processor == null) return;
+
const theirRet = this.parseScriptInternal(id, source, [processor]);
+
Object.assign(ret, theirRet);
}
};
-
for (const processor of available) {
+
for (const processor of processors) {
if (processor.process(state)) {
this.processors.splice(this.processors.indexOf(processor), 1);
this.successful.add(processor.name);
···
}
const str = dirty ? generate(module) : null;
-
-
const end = performance.now();
-
this.elapsed += end - start;
+
if (str != null) ret[id] = str;
-
return str;
+
return ret;
}
public getType(name: string) {
···
// TODO: call this with require we obtain from the webpack entrypoint
public setDefaultRequire(require: (id: string) => any) {
this.defaultRequire = require;
+
}
+
+
public setModuleSourceGetter(getSource: (id: string) => string) {
+
this.getModuleSource = getSource;
+
}
+
+
public getModuleSourceById(id: string) {
+
return this.getModuleSource?.(id) ?? null;
}
public remap<Id extends keyof Remapped>(
+52 -5
packages/lunast/src/modules/test.ts
···
import { traverse, is } from "estree-toolkit";
-
import { getPropertyGetters, register, magicAST } from "../utils";
+
import { getPropertyGetters, register, magicAST, getImports } from "../utils";
import { BlockStatement } from "estree-toolkit/dist/generated/types";
// These aren't actual modules yet, I'm just using this as a testbed for stuff
···
"balls"
)`)!;
for (const data of Object.values(getters)) {
-
if (!is.identifier(data.argument)) continue;
+
if (!is.identifier(data.expression)) continue;
-
const node = data.scope.getOwnBinding(data.argument.name);
+
const node = data.scope.getOwnBinding(data.expression.name);
if (!node) continue;
const body = node.path.get<BlockStatement>("body");
···
const fields = [];
for (const [name, data] of Object.entries(getters)) {
-
if (!is.identifier(data.argument)) continue;
-
const node = data.scope.getOwnBinding(data.argument.name);
+
if (!is.identifier(data.expression)) continue;
+
const node = data.scope.getOwnBinding(data.expression.name);
if (!node) continue;
let isSupportsCopy = false;
···
return false;
}
});*/
+
+
// Triggering a processor from another processor
+
register({
+
name: "FluxDispatcherParent",
+
find: ["isDispatching", "dispatch", "googlebot"],
+
process({ id, ast, lunast, trigger }) {
+
const imports = getImports(ast);
+
// This is so stupid lol
+
const usages = Object.entries(imports)
+
.map(([name, data]): [string, number] => {
+
if (!is.identifier(data.expression)) return [name, 0];
+
const binding = data.scope.getOwnBinding(data.expression.name);
+
if (!binding) return [name, 0];
+
return [name, binding.references.length];
+
})
+
.sort(([, a], [, b]) => b! - a!)
+
.map(([name]) => name);
+
+
const dispatcher = usages[1].toString();
+
trigger(dispatcher, "FluxDispatcher");
+
return true;
+
}
+
});
+
+
register({
+
name: "FluxDispatcher",
+
manual: true,
+
process({ id, ast, lunast }) {
+
lunast.addModule({
+
name: "FluxDispatcher",
+
id,
+
type: "FluxDispatcher"
+
});
+
+
lunast.addType({
+
name: "FluxDispatcher",
+
fields: [
+
{
+
name: "default",
+
unmapped: "Z"
+
}
+
]
+
});
+
+
return true;
+
}
+
});
+3 -2
packages/lunast/src/remap.ts
···
export type Processor = {
name: string;
-
find?: string | RegExp; // TODO: allow multiple finds
+
find?: (string | RegExp)[] | (string | RegExp);
priority?: number;
-
dependencies?: string[]; // FIXME: this can skip modules
+
manual?: boolean;
process: (state: ProcessorState) => boolean;
};
export type ProcessorState = {
···
ast: Program;
lunast: LunAST;
markDirty: () => void;
+
trigger: (id: string, tag: string) => void;
};
+66 -27
packages/lunast/src/utils.ts
···
import type { Processor } from "./remap";
-
import { traverse, is, Scope, Binding } from "estree-toolkit";
+
import { traverse, is, Scope, Binding, NodePath } from "estree-toolkit";
// FIXME something's fishy with these types
import type {
Expression,
···
}
export type ExpressionWithScope = {
-
argument: Expression;
+
expression: Expression;
scope: Scope;
};
+
function getParent(path: NodePath) {
+
let parent = path.parentPath;
+
while (!is.program(parent)) {
+
parent = parent?.parentPath ?? null;
+
if (
+
parent == null ||
+
parent.node == null ||
+
![
+
"FunctionExpression",
+
"ExpressionStatement",
+
"CallExpression",
+
"Program"
+
].includes(parent.node.type)
+
) {
+
return null;
+
}
+
}
+
+
if (!is.functionExpression(path.parent)) return null;
+
return path.parent;
+
}
+
export function getExports(ast: Program) {
const ret: Record<string, ExpressionWithScope> = {};
···
$: { scope: true },
BlockStatement(path) {
if (path.scope == null) return;
+
const parent = getParent(path);
+
if (parent == null) return;
-
// Walk up to make sure we are indeed the top level
-
let parent = path.parentPath;
-
while (!is.program(parent)) {
-
parent = parent?.parentPath ?? null;
-
if (
-
parent == null ||
-
parent.node == null ||
-
![
-
"FunctionExpression",
-
"ExpressionStatement",
-
"CallExpression",
-
"Program"
-
].includes(parent.node.type)
-
) {
-
return;
-
}
-
}
-
-
if (!is.functionExpression(path.parent)) return;
-
-
for (let i = 0; i < path.parent.params.length; i++) {
-
const param = path.parent.params[i];
+
for (let i = 0; i < parent.params.length; i++) {
+
const param = parent.params[i];
if (!is.identifier(param)) continue;
const binding: Binding | undefined = path.scope!.getBinding(param.name);
if (!binding) continue;
···
if (!is.identifier(property.key)) continue;
if (!is.expression(property.value)) continue;
ret[property.key.name] = {
-
argument: property.value,
+
expression: property.value,
scope: path.scope
};
}
···
if (!is.assignmentExpression(assignmentExpression)) continue;
ret[reference.parentPath.node.property.name] = {
-
argument: assignmentExpression.right,
+
expression: assignmentExpression.right,
scope: path.scope
};
}
···
);
if (!returnStatement || !returnStatement.argument) continue;
ret[property.key.name] = {
-
argument: returnStatement.argument,
+
expression: returnStatement.argument,
scope: path.scope
};
}
···
return null;
return expressionStatement.expression.callee.body;
}
+
+
export function getImports(ast: Program) {
+
const ret: Record<string, ExpressionWithScope> = {};
+
+
traverse(ast, {
+
$: { scope: true },
+
BlockStatement(path) {
+
if (path.scope == null) return;
+
const parent = getParent(path);
+
if (parent == null) return;
+
+
const require = parent.params[2];
+
if (!is.identifier(require)) return;
+
const references = path.scope.getOwnBinding(require.name)?.references;
+
if (references == null) return;
+
for (const reference of references) {
+
if (!is.callExpression(reference.parentPath)) continue;
+
if (reference.parentPath.node?.arguments.length !== 1) continue;
+
if (!is.variableDeclarator(reference.parentPath.parentPath)) continue;
+
if (!is.identifier(reference.parentPath.parentPath.node?.id)) continue;
+
+
const moduleId = reference.parentPath.node.arguments[0];
+
if (!is.literal(moduleId)) continue;
+
if (moduleId.value == null) continue;
+
+
ret[moduleId.value.toString()] = {
+
expression: reference.parentPath.parentPath.node.id,
+
scope: path.scope
+
};
+
}
+
}
+
});
+
+
return ret;
+
}