/**
 * hsw.js AST 解混淆脚本 v4
 *
 * 双解码器支持:
 *   - PW/QQ: base=311, 596条, rotation=275
 *   - Qg:    base=438, 133条, 无旋转
 *
 * AST 遍历 passes:
 *   1. 别名收集 (两个解码器各自的别名链)
 *   2. 常量传播 + 解码替换
 *   3. 属性访问简化: obj["prop"] → obj.prop
 *   4. 字符串拼接折叠: "a".concat("b") → "ab"
 *   5. 死代码清理
 */

const fs = require("fs");
const path = require("path");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const generate = require("@babel/generator").default;
const t = require("@babel/types");

const SRC = path.join(__dirname, "hsw.js");
const OUT = path.join(__dirname, "hsw.deobfuscated.js");

const src = fs.readFileSync(SRC, "utf-8");

// ═══════════════════════════════════════════
// Phase 1: 提取两个字符串数组 + 解码
// ═══════════════════════════════════════════

console.log("[*] Phase 1: 提取字符串数组...");

function decode(encoded) {
  const chars =
    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=";
  let raw = "";
  let bc = 0;
  let bs = 0;
  let ch;
  let idx = 0;
  while ((ch = encoded.charAt(idx++))) {
    ch = chars.indexOf(ch);
    if (~ch) {
      bs = bc % 4 ? 64 * bs + ch : ch;
      if (bc++ % 4)
        raw += String.fromCharCode(255 & (bs >> ((-2 * bc) & 6)));
    }
  }
  let uri = "";
  for (let i = 0; i < raw.length; i++)
    uri += "%" + ("00" + raw.charCodeAt(i).toString(16)).slice(-2);
  return decodeURIComponent(uri);
}

// ── 解码器 1: tH / QQ / PW (base=311, 有旋转) ──

const arr1Match = src.match(
  /tH\s*=\s*function\s*\(\)\s*\{\s*var\s+Ig\s*=\s*\[([\s\S]*?)\];\s*return\s*\(\s*tH/
);
if (!arr1Match) { console.error("[-] 无法提取 tH 数组"); process.exit(1); }

const raw1 = eval(`[${arr1Match[1]}]`);
const decoded1 = raw1.map((s) => { try { return decode(s); } catch { return null; } });
console.log(`[+] 解码器1 (PW/QQ): ${raw1.length} 条, base=311`);

// 暴力搜索旋转
const BASE1 = 311;
const LEN1 = decoded1.length;
const lookup1 = (rot, idx) => decoded1[((((idx - BASE1 + rot) % LEN1) + LEN1) % LEN1)];

const constraints1 = [
  [814, "length"], [507, "concat"], [606, "createElement"],
  [675, "canvas"], [440, "prototype"], [719, "toString"],
  [874, "call"], [532, "create"], [716, "test"],
  [897, "width"], [811, "height"], [873, "toDataURL"],
];

let rot1 = -1;
for (let r = 0; r < LEN1; r++) {
  if (constraints1.every(([i, e]) => lookup1(r, i) === e)) { rot1 = r; break; }
}
if (rot1 === -1) { console.error("[-] PW/QQ 旋转搜索失败"); process.exit(1); }
console.log(`[+] PW/QQ 旋转偏移: ${rot1}`);

const map1 = new Map();
for (let i = BASE1; i < BASE1 + LEN1; i++) {
  const v = lookup1(rot1, i);
  if (v != null) map1.set(i, v);
}

// ── 解码器 2: xf / Qg (base=438, 无旋转) ──

const arr2Match = src.match(
  /xf\s*=\s*function\s*\(\)\s*\{\s*var\s+Ig\s*=\s*\[([\s\S]*?)\];\s*return\s*\(\s*xf/
);
if (!arr2Match) { console.error("[-] 无法提取 xf 数组"); process.exit(1); }

const raw2 = eval(`[${arr2Match[1]}]`);
const decoded2 = raw2.map((s) => { try { return decode(s); } catch { return null; } });
console.log(`[+] 解码器2 (Qg): ${raw2.length} 条, base=438`);

const BASE2 = 438;
const map2 = new Map();
for (let i = 0; i < decoded2.length; i++) {
  if (decoded2[i] != null) map2.set(i + BASE2, decoded2[i]);
}

// 验证 Qg 的一些已知值
const constraints2 = [
  [441, "length"],     // Qg(441) 在 wasm 段出现大量
  [439, "buffer"],     // Qg(439)
  [453, "name"],       // Qg(453)
  [456, "isArray"],    // Array[Qg(456)]
  [457, "exec"],       // regex.exec
  [447, "string"],     // typeof === Qg(447)
  [449, "constructor"],// Ig[Qg(449)]
  [462, "message"],    // Ig[Qg(462)]
  [463, "stack"],      // Ig[Qg(463)]
  [476, "set"],        // cP().set()
  [467, "subarray"],   // Ig[Qg(467)]
];

let c2pass = 0;
for (const [idx, exp] of constraints2) {
  const act = map2.get(idx);
  if (act === exp) c2pass++;
  else console.log(`    [!] Qg(${idx}): 期望 "${exp}", 实际 "${act}"`);
}

if (c2pass < constraints2.length) {
  // 需要旋转
  console.log(`[!] Qg 直接映射验证 ${c2pass}/${constraints2.length}, 尝试旋转搜索...`);
  const LEN2 = decoded2.length;
  let rot2 = -1;
  for (let r = 0; r < LEN2; r++) {
    const lk = (idx) => decoded2[((((idx - BASE2 + r) % LEN2) + LEN2) % LEN2)];
    if (constraints2.every(([i, e]) => lk(i) === e)) { rot2 = r; break; }
  }
  if (rot2 !== -1 && rot2 !== 0) {
    console.log(`[+] Qg 旋转偏移: ${rot2}`);
    map2.clear();
    for (let i = BASE2; i < BASE2 + LEN2; i++) {
      const v = decoded2[((((i - BASE2 + rot2) % LEN2) + LEN2) % LEN2)];
      if (v != null) map2.set(i, v);
    }
  }
}

console.log(`[+] 映射表: PW/QQ=${map1.size}条, Qg=${map2.size}条, 合计=${map1.size + map2.size}条`);

// ── 合并映射: 根据解码器名来分派 ──

// decoderGroup1: PW, QQ, 以及它们的别名 → 查 map1
// decoderGroup2: Qg, 以及它的别名 → 查 map2

const group1Names = new Set(["PW", "QQ"]);
const group2Names = new Set(["Qg"]);

function getMapForDecoder(name) {
  if (group1Names.has(name)) return map1;
  if (group2Names.has(name)) return map2;
  return null;
}

// ═══════════════════════════════════════════
// Phase 2: 解析 AST
// ═══════════════════════════════════════════

console.log("[*] Phase 2: 解析 AST...");

const ast = parser.parse(src, {
  sourceType: "script",
  plugins: [],
  errorRecovery: true,
});
console.log("[+] AST 解析完成");

// ═══════════════════════════════════════════
// Phase 3: Pass 1 — 别名收集 (两个解码器)
// ═══════════════════════════════════════════

console.log("[*] Phase 3: 收集解码器别名...");

// 追踪: var X = PW → X 属于 group1
//        var Y = Qg → Y 属于 group2
//        var Z = X  → Z 属于 X 的 group

const allDecoders = new Set([...group1Names, ...group2Names]);

traverse(ast, {
  VariableDeclarator(path) {
    if (
      t.isIdentifier(path.node.id) &&
      t.isIdentifier(path.node.init) &&
      allDecoders.has(path.node.init.name)
    ) {
      const src = path.node.init.name;
      const dst = path.node.id.name;
      if (group1Names.has(src)) group1Names.add(dst);
      else if (group2Names.has(src)) group2Names.add(dst);
      allDecoders.add(dst);
    }
  },
  AssignmentExpression(path) {
    if (
      t.isIdentifier(path.node.left) &&
      t.isIdentifier(path.node.right) &&
      allDecoders.has(path.node.right.name)
    ) {
      const src = path.node.right.name;
      const dst = path.node.left.name;
      if (group1Names.has(src)) group1Names.add(dst);
      else if (group2Names.has(src)) group2Names.add(dst);
      allDecoders.add(dst);
    }
  },
});

// 多轮别名链传播
let prevSize;
do {
  prevSize = allDecoders.size;
  traverse(ast, {
    noScope: true,
    VariableDeclarator(path) {
      if (
        t.isIdentifier(path.node.id) &&
        t.isIdentifier(path.node.init) &&
        allDecoders.has(path.node.init.name) &&
        !allDecoders.has(path.node.id.name)
      ) {
        const s = path.node.init.name;
        const d = path.node.id.name;
        if (group1Names.has(s)) group1Names.add(d);
        else if (group2Names.has(s)) group2Names.add(d);
        allDecoders.add(d);
      }
    },
    AssignmentExpression(path) {
      if (
        t.isIdentifier(path.node.left) &&
        t.isIdentifier(path.node.right) &&
        allDecoders.has(path.node.right.name) &&
        !allDecoders.has(path.node.left.name)
      ) {
        const s = path.node.right.name;
        const d = path.node.left.name;
        if (group1Names.has(s)) group1Names.add(d);
        else if (group2Names.has(s)) group2Names.add(d);
        allDecoders.add(d);
      }
    },
  });
} while (allDecoders.size > prevSize);

console.log(`[+] 解码器1别名 (${group1Names.size}): ${[...group1Names].join(", ")}`);
console.log(`[+] 解码器2别名 (${group2Names.size}): ${[...group2Names].join(", ")}`);

// ═══════════════════════════════════════════
// Phase 4: Pass 2 — 常量传播 + 解码替换
// ═══════════════════════════════════════════

console.log("[*] Phase 4: 常量传播 + 字符串解码...");

let resolvedCount = 0;
let constPropCount = 0;

function resolveNumericValue(node, scopePath) {
  if (t.isNumericLiteral(node)) return node.value;
  if (t.isUnaryExpression(node) && node.operator === "-" && t.isNumericLiteral(node.argument)) {
    return -node.argument.value;
  }
  if (t.isIdentifier(node)) {
    const binding = scopePath.scope.getBinding(node.name);
    if (binding && binding.constant && binding.path.isVariableDeclarator()) {
      const init = binding.path.node.init;
      if (t.isNumericLiteral(init)) return init.value;
    }
    // 非 constant binding 但 init 是数字且只赋值一次
    if (binding && binding.path.isVariableDeclarator()) {
      const init = binding.path.node.init;
      if (t.isNumericLiteral(init) && !binding.constantViolations.length) {
        return init.value;
      }
    }
  }
  return null;
}

traverse(ast, {
  CallExpression(path) {
    const callee = path.node.callee;
    if (
      t.isIdentifier(callee) &&
      allDecoders.has(callee.name) &&
      path.node.arguments.length >= 1
    ) {
      const arg = path.node.arguments[0];
      const numVal = resolveNumericValue(arg, path);
      if (numVal === null) return;

      const map = getMapForDecoder(callee.name);
      if (!map) return;

      if (map.has(numVal)) {
        path.replaceWith(t.stringLiteral(map.get(numVal)));
        resolvedCount++;
        if (!t.isNumericLiteral(arg)) constPropCount++;
      }
    }
  },
});

console.log(`[+] 解码替换: ${resolvedCount} (常量传播: ${constPropCount})`);

// ═══════════════════════════════════════════
// Phase 5: Pass 3 — 属性简化
// ═══════════════════════════════════════════

console.log("[*] Phase 5: 属性访问简化...");

let memberCount = 0;
const RESERVED = new Set([
  "break","case","catch","continue","debugger","default","delete","do",
  "else","finally","for","function","if","in","instanceof","new","return",
  "switch","this","throw","try","typeof","var","void","while","with",
  "class","const","enum","export","extends","import","super","implements",
  "interface","let","package","private","protected","public","static","yield",
]);

traverse(ast, {
  MemberExpression(path) {
    if (
      path.node.computed &&
      t.isStringLiteral(path.node.property) &&
      /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(path.node.property.value) &&
      !RESERVED.has(path.node.property.value)
    ) {
      path.node.computed = false;
      path.node.property = t.identifier(path.node.property.value);
      memberCount++;
    }
  },
});

console.log(`[+] 属性简化: ${memberCount}`);

// ═══════════════════════════════════════════
// Phase 6: Pass 4 — 字符串拼接折叠
// ═══════════════════════════════════════════

console.log("[*] Phase 6: 字符串拼接折叠...");

let concatCount = 0;
let round;
do {
  round = 0;
  traverse(ast, {
    CallExpression(path) {
      if (
        t.isMemberExpression(path.node.callee) &&
        !path.node.callee.computed &&
        t.isIdentifier(path.node.callee.property, { name: "concat" }) &&
        t.isStringLiteral(path.node.callee.object) &&
        path.node.arguments.length === 1 &&
        t.isStringLiteral(path.node.arguments[0])
      ) {
        path.replaceWith(
          t.stringLiteral(
            path.node.callee.object.value + path.node.arguments[0].value
          )
        );
        round++;
      }
    },
  });
  concatCount += round;
} while (round > 0);

console.log(`[+] 拼接折叠: ${concatCount}`);

// ═══════════════════════════════════════════
// Phase 7: Pass 5 — 常量折叠 (typeof, 简单比较)
// ═══════════════════════════════════════════

console.log("[*] Phase 7: 常量表达式折叠...");

let constFoldCount = 0;

traverse(ast, {
  // "string" == typeof x → 保留 (运行时), 但 typeof "xxx" → "string"
  UnaryExpression(path) {
    if (path.node.operator === "typeof" && t.isStringLiteral(path.node.argument)) {
      path.replaceWith(t.stringLiteral("string"));
      constFoldCount++;
    }
  },
  // void 0 → undefined (可选, 增加可读性)
  // 暂时不做, 可能影响语义
});

console.log(`[+] 常量折叠: ${constFoldCount}`);

// ═══════════════════════════════════════════
// Phase 8: Pass 6 — 清理无用别名声明
// ═══════════════════════════════════════════

console.log("[*] Phase 8: 清理无用别名...");

let deadCount = 0;

traverse(ast, {
  VariableDeclarator(path) {
    if (
      t.isIdentifier(path.node.id) &&
      t.isIdentifier(path.node.init) &&
      allDecoders.has(path.node.init.name) &&
      allDecoders.has(path.node.id.name)
    ) {
      const binding = path.scope.getBinding(path.node.id.name);
      if (binding && binding.references === 0) {
        path.remove();
        deadCount++;
      }
    }
  },
});

console.log(`[+] 清理: ${deadCount}`);

// ═══════════════════════════════════════════
// Phase 9: 生成输出
// ═══════════════════════════════════════════

console.log("[*] Phase 9: 生成代码...");

const output = generate(ast, {
  comments: true,
  compact: false,
  retainLines: false,
  concise: false,
}).code;

fs.writeFileSync(OUT, output, "utf-8");

const origKB = (fs.statSync(SRC).size / 1024).toFixed(1);
const newKB = (Buffer.byteLength(output) / 1024).toFixed(1);

console.log(`\n${"═".repeat(50)}`);
console.log(`[=] 解混淆完成`);
console.log(`    原始:       ${origKB} KB`);
console.log(`    输出:       ${newKB} KB`);
console.log(`    解码替换:   ${resolvedCount} (常量传播 ${constPropCount})`);
console.log(`    属性简化:   ${memberCount}`);
console.log(`    拼接折叠:   ${concatCount}`);
console.log(`    常量折叠:   ${constFoldCount}`);
console.log(`    死代码清理: ${deadCount}`);
console.log(`    PW/QQ 别名: ${group1Names.size} | Qg 别名: ${group2Names.size}`);
console.log(`    输出: ${OUT}`);
console.log(`${"═".repeat(50)}`);

// 导出映射表
const mapOut = path.join(__dirname, "hsw.string-map.json");
const mapObj = { "decoder1_PW_QQ": {}, "decoder2_Qg": {} };
for (const [k, v] of map1) mapObj.decoder1_PW_QQ[k] = v;
for (const [k, v] of map2) mapObj.decoder2_Qg[k] = v;
fs.writeFileSync(mapOut, JSON.stringify(mapObj, null, 2), "utf-8");
console.log(`    映射: ${mapOut}`);
