Support all player variants

This commit is contained in:
Simon Sawicki
2025-08-25 19:37:57 +02:00
parent 4de9cf806f
commit d45a043628
6 changed files with 284 additions and 191 deletions

View File

@@ -1,97 +1,119 @@
import {
type ArrowFunctionExpression,
type Node,
type BlockStatement,
} from "npm:@babel/types@7.28.2";
import { matchesStructure } from "./utils.ts";
import { type DeepPartial } from "./types.ts";
const identifier: DeepPartial<Node> = {
type: "AssignmentExpression",
operator: "=",
left: {
type: "Identifier",
},
right: {
type: "FunctionExpression",
params: [{}],
body: {
type: "BlockStatement",
body: [
{
type: "ReturnStatement",
// {
argument:
// or: [
{
type: "CallExpression",
callee: {
type: "MemberExpression",
object: {
type: "Identifier",
// XXX: get switch function identifier, use here
// name: "Lb",
},
property: {
type: "MemberExpression",
object: {
type: "Identifier",
// XXX: get global string store identifier, use here
// name: "Y",
},
property: {
type: "NumericLiteral",
},
},
},
arguments: [
{ type: "ThisExpression" },
{ type: "NumericLiteral" },
{
type: "Identifier",
// XXX: get parameter name, use here
// name: "Y",
},
],
},
// XXX: possible to be a direct call, ignore that for now
// {
// type: "CallExpression",
// callee: {
// type: "Identifier",
// // XXX: get global string store identifier, use here
// // name: "Y",
// },
// arguments: [
// { type: "NumericLiteral" },
// {
// type: "Identifier",
// // XXX: get parameter name, use here
// // name: "Y",
// },
// ],
// },
// ],
// },
type: "VariableDeclaration",
declarations: [
{
type: "VariableDeclarator",
id: {
type: "Identifier",
},
init: {
type: "ArrayExpression",
elements: [
{
type: "Identifier",
},
],
},
},
],
kind: "var",
};
const catchBlockBody = [
{
"type": "ReturnStatement",
"argument": {
"type": "BinaryExpression",
"operator": "+",
"left": {
"type": "MemberExpression",
"object": {
"type": "Identifier",
},
],
"property": {
"type": "NumericLiteral",
},
},
"right": {
"type": "Identifier",
},
},
},
};
] as const;
export function extract(node: Node): ArrowFunctionExpression | null {
if (!matchesStructure(node, identifier)) {
// Fallback search for try { } catch { return X[12] + Y }
let name: string | undefined | null = null;
let block: BlockStatement | null = null;
switch (node.type) {
case "ExpressionStatement": {
if (node.expression.type === "AssignmentExpression" &&
node.expression.left.type === "Identifier" &&
node.expression.right.type === "FunctionExpression" &&
node.expression.right.params.length === 1) {
name = node.expression.left.name;
block = node.expression.right.body;
}
break;
}
case "FunctionDeclaration": {
if (node.params.length === 1) {
name = node.id?.name;
block = node.body;
}
break;
}
}
if (!block || !name) {
return null;
}
const tryNode = block.body.at(-2);
if (
tryNode?.type !== "TryStatement" ||
tryNode.handler?.type !== "CatchClause"
) {
return null;
}
const catchBody = tryNode.handler!.body.body;
if (matchesStructure(catchBody, catchBlockBody)) {
return makeSolverFuncFromName(name);
}
return null;
}
if (node.type !== "AssignmentExpression" || node.left.type !== "Identifier") {
if (node.type !== "VariableDeclaration") {
return null;
}
// TODO: verify identifiers here
const declaration = node.declarations[0];
if (
declaration.type !== "VariableDeclarator" || !declaration.init ||
declaration.init.type !== "ArrayExpression" ||
declaration.init.elements.length !== 1
) {
return null;
}
const [firstElement] = declaration.init.elements;
if (!firstElement || firstElement.type !== "Identifier") {
return null;
}
return makeSolverFuncFromName(firstElement.name);
}
function makeSolverFuncFromName(name: string): ArrowFunctionExpression {
return {
type: "ArrowFunctionExpression",
params: [
{
type: "Identifier",
name: "nsig",
name: "x",
},
],
async: false,
@@ -100,12 +122,12 @@ export function extract(node: Node): ArrowFunctionExpression | null {
type: "CallExpression",
callee: {
type: "Identifier",
name: node.left.name,
name: name,
},
arguments: [
{
type: "Identifier",
name: "nsig",
name: "x",
},
],
},

View File

@@ -1,95 +1,121 @@
import {
type ArrowFunctionExpression,
type FunctionExpression,
type Expression,
type Node,
ExpressionStatement,
} from "npm:@babel/types@7.28.2";
import { matchesStructure } from "./utils.ts";
import { type DeepPartial } from "./types.ts";
const identifier: DeepPartial<Node> = {
type: "AssignmentExpression",
operator: "=",
left: {
type: "Identifier",
},
right: {
type: "FunctionExpression",
params: [{}, {}, {}],
body: {
type: "BlockStatement",
body: [
{ type: "ExpressionStatement" },
{ type: "ExpressionStatement" },
{ type: "ExpressionStatement" },
{ type: "ExpressionStatement" },
{
type: "ExpressionStatement",
expression: {
type: "LogicalExpression",
left: {
type: "Identifier",
// name: "M",
},
operator: "&&",
right: {
type: "SequenceExpression",
expressions: [
{
type: "AssignmentExpression",
operator: "=",
left: {
const logicalExpression: DeepPartial<ExpressionStatement> = {
type: "ExpressionStatement",
expression: {
type: "LogicalExpression",
left: {
type: "Identifier",
},
operator: "&&",
right: {
type: "SequenceExpression",
expressions: [
{
type: "AssignmentExpression",
operator: "=",
left: {
type: "Identifier",
},
right: {
type: "CallExpression",
callee: {
type: "Identifier",
},
right: {
type: "CallExpression",
callee: {
type: "Identifier",
},
arguments: [
{ type: "NumericLiteral" },
{
type: "CallExpression",
callee: {
type: "Identifier",
name: "decodeURIComponent",
arguments: {
or: [
[
{ type: "NumericLiteral" },
{
type: "CallExpression",
callee: {
type: "Identifier",
name: "decodeURIComponent",
},
arguments: [{ type: "Identifier" }],
},
arguments: [{ type: "Identifier" }],
},
],
[
{
type: "CallExpression",
callee: {
type: "Identifier",
name: "decodeURIComponent",
},
arguments: [{ type: "Identifier" }],
},
],
],
},
},
{
type: "CallExpression",
},
],
extra: {
parenthesized: true,
},
{
type: "CallExpression",
},
],
extra: {
parenthesized: true,
},
},
},
{ type: "ReturnStatement" },
],
}
const identifier = {
or: [{
type: "ExpressionStatement",
expression: {
type: "AssignmentExpression",
operator: "=",
left: {
type: "Identifier",
},
right: {
type: "FunctionExpression",
params: [{}, {}, {}],
},
},
},
};
}, {
type: "FunctionDeclaration",
params: [{}, {}, {}],
}],
} as const;
export function extract(node: Node): ArrowFunctionExpression | null {
if (!matchesStructure(node, identifier)) {
if (!matchesStructure(node, identifier as unknown as DeepPartial<Node>)) {
return null;
}
const block = (node.type === "ExpressionStatement" &&
node.expression.type === "AssignmentExpression" &&
node.expression.right.type === "FunctionExpression")
? node.expression.right.body
: node.type === "FunctionDeclaration"
? node.body
: null;
const relevantExpression = block?.body.at(-2);
if (!matchesStructure(relevantExpression!, logicalExpression)) {
return null;
}
// shut the type checker up
if (
node.type !== "AssignmentExpression" ||
node.right.type !== "FunctionExpression" ||
node.right.body.body[4].type !== "ExpressionStatement" ||
node.right.body.body[4].expression.type !== "LogicalExpression" ||
node.right.body.body[4].expression.right.type !== "SequenceExpression" ||
node.right.body.body[4].expression.right.expressions[0].type !==
relevantExpression?.type !== "ExpressionStatement" ||
relevantExpression.expression.type !==
"LogicalExpression" ||
relevantExpression.expression.right.type !==
"SequenceExpression" ||
relevantExpression.expression.right.expressions[0].type !==
"AssignmentExpression"
) {
return null;
}
const call = node.right.body.body[4].expression.right.expressions[0].right;
const call = relevantExpression.expression.right.expressions[0].right;
if (call.type !== "CallExpression" || call.callee.type !== "Identifier") {
return null;
}
@@ -110,13 +136,20 @@ export function extract(node: Node): ArrowFunctionExpression | null {
type: "Identifier",
name: call.callee.name,
},
arguments: [
call.arguments[0],
{
type: "Identifier",
name: "sig",
},
],
arguments: call.arguments.length === 1
? [
{
type: "Identifier",
name: "sig",
},
]
: [
call.arguments[0],
{
type: "Identifier",
name: "sig",
},
],
},
};
}

View File

@@ -19,48 +19,71 @@ export function preprocessPlayer(data: string): string {
attachComment: false,
});
const body = ast.program.body;
if (body.length !== 2 || body[1].type !== "ExpressionStatement") {
const block = (() => {
switch (body.length) {
case 1: {
const func = body[0];
if (
func?.type === "ExpressionStatement" &&
func.expression.type === "CallExpression" &&
func.expression.callee.type === "MemberExpression" &&
func.expression.callee.object.type === "FunctionExpression"
) {
return func.expression.callee.object.body;
}
break;
}
case 2: {
const func = body[1];
if (
func?.type === "ExpressionStatement" &&
func.expression.type === "CallExpression" &&
func.expression.callee.type === "FunctionExpression"
) {
const block = func.expression.callee.body;
// Skip `var window = this;`
block.body.splice(0, 1);
return block;
}
break;
}
}
throw "unexpected structure";
}
const func = body[1];
if (
func.expression.type !== "CallExpression" ||
func.expression.callee.type !== "FunctionExpression"
) {
throw "unexpected structure";
}
})();
const found = {
nsig: [] as ArrowFunctionExpression[],
sig: [] as ArrowFunctionExpression[],
};
const plainExpressions = func.expression.callee.body.body.filter(
(node, idx) => {
if (idx === 0) {
// Ignore `var window = this;`
return false;
}
if (node.type === "ExpressionStatement") {
if (node.expression.type === "AssignmentExpression") {
const nsig = extractNsig(node.expression);
if (nsig) {
found.nsig.push(nsig);
}
const sig = extractSig(node.expression);
if (sig) {
found.sig.push(sig);
}
return true;
}
return node.expression.type === "StringLiteral";
}
return true;
const plainExpressions = block.body.filter((node) => {
const nsig = extractNsig(node);
if (nsig) {
found.nsig.push(nsig);
}
);
func.expression.callee.body.body = plainExpressions;
const sig = extractSig(node);
if (sig) {
found.sig.push(sig);
}
if (node.type === "ExpressionStatement") {
if (node.expression.type === "AssignmentExpression") {
return true;
}
return node.expression.type === "StringLiteral";
}
return true;
});
block.body = plainExpressions;
for (const [name, options] of Object.entries(found)) {
if (options.length !== 1) {
continue;
// TODO: this is cringe fix plz
const unique = new Set(options.map((x) => JSON.stringify(x)));
if (unique.size !== 1) {
const message = `found ${unique.size} ${name} function possibilities`;
throw message +
(unique.size
? `: ${options.map((x) => generate(x)["code"]).join(", ")}`
: "");
}
plainExpressions.push({
type: "ExpressionStatement",

View File

@@ -1,4 +1,8 @@
export type DeepPartial<T> = T extends object ? {
[P in keyof T]?: DeepPartial<T[P]>;
}
: T;
export type DeepPartial<T> = T extends object ? Or<
{
[P in keyof T]?: DeepPartial<T[P]>;
}
>
: Or<T>;
type Or<T> = T | { or: T[] };

View File

@@ -5,7 +5,7 @@ import { type DeepPartial } from "./types.ts";
export function matchesStructure<T extends Node>(
obj: Node | Node[],
structure: DeepPartial<T> | readonly DeepPartial<T>[],
): obj is T {
): boolean {
if (Array.isArray(structure)) {
if (!Array.isArray(obj)) {
return false;
@@ -20,10 +20,8 @@ export function matchesStructure<T extends Node>(
return !structure;
}
if ("or" in structure) {
// Allow `{ or: [a, b] }` so we can handle some special cases
return (structure.or! as DeepPartial<Node>[]).some((node) =>
matchesStructure(obj, node)
);
// Handle `{ or: [a, b] }`
return structure.or.some((node) => matchesStructure(obj, node));
}
for (const [key, value] of Object.entries(structure)) {
if (!matchesStructure(obj[key as keyof typeof obj], value)) {

View File

@@ -11,7 +11,6 @@ export const tests: {
}[] = [
{
player: "3d3ba064",
variants: ["tce"],
nsig: [
{ input: "ZdZIqFPQK-Ty8wId", expected: "qmtUsIz04xxiNW" },
{ input: "4GMrWHyKI5cEvhDO", expected: "N9gmEX7YhKTSmw" },
@@ -27,7 +26,6 @@ export const tests: {
},
{
player: "5ec65609",
variants: ["tce"],
nsig: [{ input: "0eRGgQWJGfT5rFHFj", expected: "4SvMpDQH-vBJCw" }],
sig: [
{
@@ -40,7 +38,6 @@ export const tests: {
},
{
player: "6742b2b9",
variants: ["tce"],
nsig: [
{ input: "_HPB-7GFg1VTkn9u", expected: "qUAsPryAO_ByYg" },
{ input: "K1t_fcB6phzuq2SF", expected: "Y7PcOt3VE62mog" },
@@ -54,6 +51,22 @@ export const tests: {
},
],
},
{
player: "23ccdd25",
nsig: [
// Synthetic test
{ input: "0eRGgQWJGfT5rFHFj", expected: "orSsTqUaUO-j" },
],
sig: [
// Synthetic test
{
input:
"MMGZJMUucirzS_SnrSPYsc85CJNnTUi6GgR5NKn-znQEICACojE8MHS6S7uYq4TGjQX_D4aPk99hNU6wbTvorvVVMgIARwsSdQfJAA",
expected:
"ZJMUucirzS_SnrSPYsc85CJNnTUi6GgR5NKn-znQEICACojE8MHS6S7uYq4TGjQX_D4aPk99hAU6wbTvorvVVMgIARwsSdQfJAN",
},
],
},
];
export const players = new Map([