ktworkflow-mcp/scripts/validate-deobfuscation-anchors.ts

177 lines
5.1 KiB
JavaScript

#!/usr/bin/env node
import { readFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
interface CliOptions {
anchorsPath: string;
endAnchor: string;
sourcePath: string;
startAnchor: string;
}
interface AnchorCatalog {
sourceAnchors?: unknown;
}
const dirname = path.dirname(fileURLToPath(import.meta.url));
const workspaceRoot = path.resolve(
process.env.KT_WORKSPACE_ROOT || path.join(dirname, '../../..'),
);
/**
* @param value 原始路径,可为绝对路径或相对 KT 工作区根目录。
* @returns 解析后的绝对路径。
*/
function resolveWorkspacePath(value: string): string {
return path.isAbsolute(value) ? value : path.resolve(workspaceRoot, value);
}
/**
* @param argv 传入脚本的命令行参数。
* @returns 反混淆 anchor 校验配置。
*/
function parseArgs(argv: string[]): CliOptions {
const args = new Map<string, string>();
for (let index = 0; index < argv.length; index += 1) {
const item = argv[index];
if (!item.startsWith('--')) continue;
const [key, inlineValue] = item.slice(2).split('=', 2);
if (inlineValue !== undefined) {
args.set(key, inlineValue);
continue;
}
const next = argv[index + 1];
if (next && !next.startsWith('--')) {
args.set(key, next);
index += 1;
}
}
if (args.has('help') || args.has('h')) {
printHelp();
process.exit(0);
}
const sourcePath = args.get('source');
const anchorsPath = args.get('anchors') || args.get('anchors-json');
const startAnchor = args.get('start');
const endAnchor = args.get('end');
if (!sourcePath || !anchorsPath || !startAnchor || !endAnchor) {
printHelp();
throw new Error('缺少必要参数:--source、--anchors、--start、--end');
}
return {
anchorsPath: resolveWorkspacePath(anchorsPath),
endAnchor,
sourcePath: resolveWorkspacePath(sourcePath),
startAnchor,
};
}
/**
* 输出脚本帮助文本。
*/
function printHelp(): void {
console.log(`KT 反混淆 sourceAnchors 校验
用法:
pnpm run validate-deobfuscation-anchors -- --source Vue/kt-blog-web/public/live2d/wordpress-moc/live2d.min.js --anchors .kt-workspace/deobfuscate/.../renames-matrix44-method-aliases.json --start "function ac()" --end "function aD()"
参数:
--source <path> 源 min.js 文件,绝对路径或相对 KT 根目录
--anchors <path> 包含 sourceAnchors 数组的 JSON 文件
--start <text> 源码切片起始锚点
--end <text> 源码切片结束锚点
`);
}
/**
* @param rawCatalog 原始 JSON 文本。
* @param anchorsPath JSON 文件路径,用于错误说明。
* @returns sourceAnchors 字符串数组。
*/
function readSourceAnchors(rawCatalog: string, anchorsPath: string): string[] {
const catalog = JSON.parse(rawCatalog) as AnchorCatalog;
if (!Array.isArray(catalog.sourceAnchors)) {
throw new Error(`${anchorsPath} 缺少 sourceAnchors 数组`);
}
const invalidAnchors = catalog.sourceAnchors.filter((anchor) => typeof anchor !== 'string');
if (invalidAnchors.length > 0) {
throw new Error(`${anchorsPath} 的 sourceAnchors 只能包含字符串`);
}
return catalog.sourceAnchors as string[];
}
/**
* @param source 源码文本。
* @param startAnchor 切片起始锚点。
* @param endAnchor 切片结束锚点。
* @returns 起止锚点之间的源码切片。
*/
function sliceSourceByAnchors(source: string, startAnchor: string, endAnchor: string): string {
const startIndex = source.indexOf(startAnchor);
if (startIndex < 0) {
throw new Error(`源文件中找不到起始锚点:${startAnchor}`);
}
const endIndex = source.indexOf(endAnchor, startIndex);
if (endIndex <= startIndex) {
throw new Error(`源文件中找不到结束锚点:${endAnchor}`);
}
return source.slice(startIndex, endIndex);
}
/**
* @param sourceSlice 源码切片。
* @param anchors 需要逐项匹配的 sourceAnchors。
* @returns 未能在源码切片中找到的 anchors。
*/
function findMissingAnchors(sourceSlice: string, anchors: string[]): string[] {
return anchors.filter((anchor) => !sourceSlice.includes(anchor));
}
/**
* 校验 sourceAnchors 是否都存在于指定源码切片。
*/
function main(): void {
const options = parseArgs(process.argv.slice(2));
const source = readFileSync(options.sourcePath, 'utf-8');
const rawCatalog = readFileSync(options.anchorsPath, 'utf-8');
const sourceSlice = sliceSourceByAnchors(source, options.startAnchor, options.endAnchor);
const sourceAnchors = readSourceAnchors(rawCatalog, options.anchorsPath);
const missingAnchors = findMissingAnchors(sourceSlice, sourceAnchors);
if (missingAnchors.length > 0) {
throw new Error(`sourceAnchors 未命中源码切片:${missingAnchors.join('; ')}`);
}
console.log(
JSON.stringify(
{
anchors: sourceAnchors.length,
anchorsPath: options.anchorsPath,
ok: true,
sourcePath: options.sourcePath,
},
null,
2,
),
);
}
try {
main();
} catch (error) {
console.error(error instanceof Error ? error.message : error);
process.exit(1);
}