177 lines
5.1 KiB
JavaScript
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);
|
|
}
|