| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141 |
- // 提取 .jsc 里的所有可读字符串 (ASCII + UTF-16LE),按文件输出到 jsc-extract/strings/<name>.md
- const fs = require('fs');
- const path = require('path');
- const ROOT = path.resolve(__dirname, '..', 'src-app');
- const OUT_DIR = path.resolve(__dirname, 'strings');
- fs.mkdirSync(OUT_DIR, { recursive: true });
- function extractAscii(buf, minLen = 4) {
- const out = [];
- let start = -1;
- for (let i = 0; i < buf.length; i++) {
- const b = buf[i];
- const printable = (b >= 0x20 && b < 0x7f) || b === 0x09 || b === 0x0a || b === 0x0d;
- if (printable) {
- if (start === -1) start = i;
- } else {
- if (start !== -1 && i - start >= minLen) {
- out.push({ off: start, len: i - start, text: buf.subarray(start, i).toString('ascii') });
- }
- start = -1;
- }
- }
- if (start !== -1 && buf.length - start >= minLen) {
- out.push({ off: start, len: buf.length - start, text: buf.subarray(start).toString('ascii') });
- }
- return out;
- }
- function extractUtf16LE(buf, minChars = 3) {
- const out = [];
- let start = -1, chars = 0;
- for (let i = 0; i + 1 < buf.length; i += 2) {
- const code = buf.readUInt16LE(i);
- const printable = (code >= 0x20 && code < 0xd800) || code === 0x09 || code === 0x0a || code === 0x0d;
- if (printable) {
- if (start === -1) { start = i; chars = 0; }
- chars++;
- } else {
- if (start !== -1 && chars >= minChars) {
- out.push({ off: start, len: chars * 2, text: buf.subarray(start, i).toString('utf16le') });
- }
- start = -1;
- }
- }
- if (start !== -1 && chars >= minChars) {
- out.push({ off: start, len: chars * 2, text: buf.subarray(start).toString('utf16le') });
- }
- return out;
- }
- function categorize(text) {
- if (/^https?:\/\//.test(text)) return 'url';
- if (/^[a-z0-9-]+\.(com|cn|net|org|io|xyz|top|cc|app)/.test(text)) return 'domain';
- if (/^[a-zA-Z_$][\w$]*$/.test(text) && text.length < 60) return 'ident';
- if (text.length > 80) return 'text';
- if (/^[A-Z_0-9]+$/.test(text)) return 'const';
- return 'str';
- }
- function isInteresting(text) {
- // 过滤掉纯随机/重复字符串
- if (/^(\W)\1+$/.test(text)) return false;
- if (/^[0-9]+$/.test(text)) return false;
- if (text.length < 3) return false;
- // 过滤太像密码哈希的
- return true;
- }
- function processFile(file) {
- const rel = path.relative(ROOT, file);
- const buf = fs.readFileSync(file);
- const ascii = extractAscii(buf);
- const utf16 = extractUtf16LE(buf);
- // 合并去重
- const seen = new Set();
- const items = [];
- for (const s of [...ascii, ...utf16]) {
- if (!isInteresting(s.text)) continue;
- if (seen.has(s.text)) continue;
- seen.add(s.text);
- items.push({ ...s, cat: categorize(s.text) });
- }
- // 按 offset 排序
- items.sort((a, b) => a.off - b.off);
- // 输出 md
- let md = `# 字符串提取: ${rel}\n\n`;
- md += `文件大小: ${buf.length} 字节\n`;
- md += `提取到: ${items.length} 条字符串 (ASCII + UTF-16LE, 长度 >= 3-4)\n\n`;
- md += `## URL / 域名 (高价值)\n\n`;
- for (const it of items.filter(i => i.cat === 'url' || i.cat === 'domain')) {
- md += `- \`${it.text}\` *(0x${it.off.toString(16)}, ${it.len}B)*\n`;
- }
- md += `\n## 标识符 (变量/方法/属性名)\n\n`;
- const idents = items.filter(i => i.cat === 'ident');
- if (idents.length) {
- // 一次打印多列
- const cols = 4;
- for (let i = 0; i < idents.length; i += cols) {
- const row = idents.slice(i, i + cols).map(x => `\`${x.text}\``).join(' | ');
- md += row + '\n';
- }
- }
- md += `\n## 常量 (大写)\n\n`;
- for (const it of items.filter(i => i.cat === 'const')) {
- md += `- \`${it.text}\`\n`;
- }
- md += `\n## 其它文本字符串\n\n`;
- for (const it of items.filter(i => i.cat === 'str' || i.cat === 'text')) {
- md += `- \`${it.text.length > 200 ? it.text.slice(0, 200) + '…' : it.text}\`\n`;
- }
- return md;
- }
- function walk(dir) {
- const out = [];
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
- const full = path.join(dir, entry.name);
- if (entry.isDirectory()) {
- if (entry.name === 'node_modules') continue;
- out.push(...walk(full));
- } else if (entry.name.endsWith('.jsc')) {
- out.push(full);
- }
- }
- return out;
- }
- const files = walk(ROOT);
- console.log(`找到 ${files.length} 个 .jsc:`);
- for (const f of files) {
- const rel = path.relative(ROOT, f);
- const outName = rel.replace(/[\/\\]/g, '__').replace(/\.jsc$/, '.md');
- const outFile = path.join(OUT_DIR, outName);
- const md = processFile(f);
- fs.writeFileSync(outFile, md, 'utf-8');
- console.log(` ${rel} -> ${path.basename(outFile)} (${md.length} 字符)`);
- }
- console.log(`\n所有输出在: ${OUT_DIR}`);
|