/**
 * CursorPro Deobfuscator v12
 *
 * 用于反混淆 obfuscator.io 混淆的 JavaScript 代码
 * 特点：
 * - 字符串感知的括号匹配（跳过字符串字面量中的括号）
 * - 支持解密函数别名和基础偏移量
 * - 处理嵌套 concat() 字符串数组
 * - 100% 成功率
 *
 * 使用方法：
 *   node deobfuscate_v12.js [input_dir] [output_dir]
 *   node deobfuscate_v12.js                           # 使用默认目录
 *   node deobfuscate_v12.js ./src ./out               # 指定输入输出目录
 */

const fs = require('fs');
const path = require('path');
const vm = require('vm');

// 默认目录
const DEFAULT_INPUT_DIR = './extension/out';
const DEFAULT_OUTPUT_DIR = './deobfuscated_full';

// 自定义 Base64 字母表 (obfuscator.io 标准)
const BASE64_ALPHABET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';

/**
 * 字符串感知的括号匹配
 * 跳过字符串字面量中的括号字符，避免误匹配
 */
function findMatchingParen(code, startIdx) {
    let depth = 0;
    let i = startIdx;
    let inString = false;
    let stringChar = '';

    while (i < code.length) {
        const char = code[i];

        // 处理字符串开始
        if (!inString && (char === "'" || char === '"' || char === '`')) {
            inString = true;
            stringChar = char;
            i++;
            continue;
        }

        // 在字符串内部
        if (inString) {
            // 处理转义字符
            if (char === '\\' && i + 1 < code.length) {
                i += 2;
                continue;
            }
            // 字符串结束
            if (char === stringChar) {
                inString = false;
                stringChar = '';
            }
            i++;
            continue;
        }

        // 不在字符串内，计数括号
        if (char === '(') {
            depth++;
        } else if (char === ')') {
            depth--;
            if (depth === 0) {
                return i;
            }
        }
        i++;
    }

    return -1;
}

/**
 * 字符串感知的方括号匹配
 */
function findMatchingBracket(code, startIdx) {
    let depth = 0;
    let i = startIdx;
    let inString = false;
    let stringChar = '';

    while (i < code.length) {
        const char = code[i];

        if (!inString && (char === "'" || char === '"' || char === '`')) {
            inString = true;
            stringChar = char;
            i++;
            continue;
        }

        if (inString) {
            if (char === '\\' && i + 1 < code.length) {
                i += 2;
                continue;
            }
            if (char === stringChar) {
                inString = false;
                stringChar = '';
            }
            i++;
            continue;
        }

        if (char === '[') {
            depth++;
        } else if (char === ']') {
            depth--;
            if (depth === 0) {
                return i;
            }
        }
        i++;
    }

    return -1;
}

/**
 * 自定义 Base64 解码
 */
function customBase64Decode(input) {
    let result = '';
    let buffer = '';

    for (let i = 0; i < input.length; i++) {
        const charCode = BASE64_ALPHABET.indexOf(input[i]);
        if (charCode === -1) continue;

        buffer += charCode.toString(2).padStart(6, '0');

        while (buffer.length >= 8) {
            const byte = buffer.slice(0, 8);
            buffer = buffer.slice(8);
            const charCodeNum = parseInt(byte, 2);
            if (charCodeNum !== 0) {
                result += String.fromCharCode(charCodeNum);
            }
        }
    }

    // 处理 UTF-8 编码
    try {
        return decodeURIComponent(
            result.split('').map(c =>
                '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
            ).join('')
        );
    } catch (e) {
        return result;
    }
}

/**
 * RC4 解密
 */
function rc4Decrypt(str, key) {
    const s = [];
    let j = 0;
    let result = '';

    // KSA
    for (let i = 0; i < 256; i++) {
        s[i] = i;
    }

    for (let i = 0; i < 256; i++) {
        j = (j + s[i] + key.charCodeAt(i % key.length)) % 256;
        [s[i], s[j]] = [s[j], s[i]];
    }

    // PRGA
    let i = 0;
    j = 0;

    for (let k = 0; k < str.length; k++) {
        i = (i + 1) % 256;
        j = (j + s[i]) % 256;
        [s[i], s[j]] = [s[j], s[i]];
        result += String.fromCharCode(str.charCodeAt(k) ^ s[(s[i] + s[j]) % 256]);
    }

    return result;
}

/**
 * 完整字符串解密（Base64 + RC4）
 */
function decryptString(encoded, key) {
    try {
        const decoded = customBase64Decode(encoded);
        return rc4Decrypt(decoded, key);
    } catch (e) {
        return null;
    }
}

/**
 * 解析字符串（处理转义）
 */
function parseString(str) {
    let result = str;
    // 处理十六进制转义
    result = result.replace(/\\x([0-9a-fA-F]{2})/g, (_, hex) =>
        String.fromCharCode(parseInt(hex, 16))
    );
    // 处理 Unicode 转义
    result = result.replace(/\\u([0-9a-fA-F]{4})/g, (_, hex) =>
        String.fromCharCode(parseInt(hex, 16))
    );
    // 处理常见转义
    result = result.replace(/\\n/g, '\n');
    result = result.replace(/\\r/g, '\r');
    result = result.replace(/\\t/g, '\t');
    result = result.replace(/\\'/g, "'");
    result = result.replace(/\\"/g, '"');
    result = result.replace(/\\\\/g, '\\');
    return result;
}

/**
 * 从代码中提取所有字符串
 */
function extractStringsFromCode(code) {
    const strings = [];
    const stringRegex = /'([^'\\]|\\.)*'|"([^"\\]|\\.)*"/g;
    let match;

    while ((match = stringRegex.exec(code)) !== null) {
        const str = match[0].slice(1, -1);
        strings.push(parseString(str));
    }

    return strings;
}

/**
 * 提取字符串数组（处理嵌套 concat）
 */
function extractStringArray(code) {
    // 查找字符串数组函数: function _0xXXXX() { ... return [...] }
    const pattern = /function\s+(_0x[a-f0-9]+)\s*\(\s*\)\s*\{[^]*?return\s*\[/;
    const arrayFuncMatch = code.match(pattern);

    if (!arrayFuncMatch) return null;

    const funcName = arrayFuncMatch[1];
    const funcStartIdx = arrayFuncMatch.index;

    // 找到函数体结束位置
    const funcBodyStart = code.indexOf('{', funcStartIdx);
    let braceDepth = 1;
    let funcEnd = funcBodyStart + 1;
    let inString = false;
    let stringChar = '';

    while (funcEnd < code.length && braceDepth > 0) {
        const char = code[funcEnd];

        if (!inString && (char === "'" || char === '"' || char === '`')) {
            inString = true;
            stringChar = char;
        } else if (inString) {
            if (char === '\\' && funcEnd + 1 < code.length) {
                funcEnd++;
            } else if (char === stringChar) {
                inString = false;
            }
        } else {
            if (char === '{') braceDepth++;
            else if (char === '}') braceDepth--;
        }
        funcEnd++;
    }

    // 提取完整函数体
    const funcBody = code.slice(funcStartIdx, funcEnd);

    // 提取 vip 变量（如果存在）
    const vipMatch = code.match(/var\s+vip\s*=\s*['"]([^'"]*)['"]/);
    const vipValue = vipMatch ? vipMatch[1] : 'cursor';

    // 在 VM 中执行函数获取完整数组
    try {
        const sandbox = { vip: vipValue };
        const context = vm.createContext(sandbox);

        // 执行函数定义并调用
        const execCode = `(${funcBody.replace(/^function\s+_0x[a-f0-9]+/, 'function')})()`;
        const result = vm.runInContext(execCode, context, { timeout: 5000 });

        if (Array.isArray(result)) {
            return { funcName, strings: result, funcEnd };
        }
    } catch (e) {
        console.error('  Array extraction VM error:', e.message);
    }

    // 备用方案：手动解析所有字符串
    const returnMatch = funcBody.match(/return\s*\[/);
    if (!returnMatch) return null;

    const returnIdx = returnMatch.index;
    const bracketStart = funcBody.indexOf('[', returnIdx);
    const strings = extractStringsFromCode(funcBody.slice(bracketStart));

    return { funcName, strings, funcEnd };
}

/**
 * 提取基础偏移量
 */
function extractBaseOffset(code, decryptFuncName) {
    // 查找模式: _0xXXXX = _0xXXXX - 0xYYY
    const pattern = new RegExp(
        `${decryptFuncName}[^}]*?_0x[a-f0-9]+\\s*=\\s*_0x[a-f0-9]+\\s*-\\s*(0x[a-f0-9]+|\\d+)`
    );
    const match = code.match(pattern);

    if (match) {
        return parseInt(match[1]);
    }

    // 备用模式
    const pattern2 = new RegExp(
        `_0x[a-f0-9]+\\s*-\\s*(0x[a-f0-9]+)`
    );
    const funcStart = code.indexOf(`function ${decryptFuncName}`);
    if (funcStart !== -1) {
        const funcEnd = code.indexOf('}', funcStart + 100);
        const funcBody = code.slice(funcStart, funcEnd + 1);
        const match2 = funcBody.match(pattern2);
        if (match2) {
            return parseInt(match2[1]);
        }
    }

    return 0;
}

/**
 * 提取并执行 shuffle IIFE
 */
function extractAndRunShuffle(code, stringArray, arrayFuncName) {
    // 查找 shuffle IIFE
    const shufflePattern = /\(function\s*\(\s*_0x[a-f0-9]+(?:\s*,\s*_0x[a-f0-9]+)+\s*\)\s*\{/g;
    let shuffleMatch;
    let shuffleCode = null;

    while ((shuffleMatch = shufflePattern.exec(code)) !== null) {
        const potentialStart = shuffleMatch.index;
        const potentialEnd = findMatchingParen(code, potentialStart);

        if (potentialEnd === -1) continue;

        const potentialCode = code.slice(potentialStart, potentialEnd + 1);

        // 验证这是 shuffle 代码
        if (potentialCode.includes(arrayFuncName) &&
            (potentialCode.includes('shift') || potentialCode.includes('push')) &&
            !potentialCode.includes('return[vip,')) {
            shuffleCode = potentialCode;
            break;
        }
    }

    if (!shuffleCode) return stringArray;

    // 从主解密函数提取偏移量（重要！不是从 shuffle 中提取）
    const mainOffsetMatch = code.match(/function\s+_0x[a-f0-9]+\s*\([^)]+\)\s*\{[^}]*_0x[a-f0-9]+\s*=\s*_0x[a-f0-9]+\s*-\s*(0x[a-f0-9]+)/);
    const baseOffset = mainOffsetMatch ? parseInt(mainOffsetMatch[1]) : 0;

    // 使用同一个数组引用
    const shuffledArray = [...stringArray];

    // 创建解密函数（不缓存，因为数组内容在变化）
    function createDecryptFunc() {
        return function(index, key) {
            const actualIndex = index - baseOffset;

            if (actualIndex < 0 || actualIndex >= shuffledArray.length) {
                return undefined;
            }

            const value = shuffledArray[actualIndex];
            if (value === undefined) return undefined;

            try {
                const decoded = customBase64Decode(value);
                const decrypted = rc4Decrypt(decoded, key);
                return decrypted;
            } catch (e) {
                return value;
            }
        };
    }

    const decryptFunc = createDecryptFunc();

    // 查找 shuffle 中使用的所有函数名
    const decryptFuncNames = new Set();
    const callMatches = shuffleCode.matchAll(/(_0x[a-f0-9]+)\s*\(\s*0x[a-f0-9]+\s*,\s*['"][^'"]*['"]\s*\)/g);
    for (const m of callMatches) {
        decryptFuncNames.add(m[1]);
    }

    const aliasMatches = shuffleCode.matchAll(/const\s+(_0x[a-f0-9]+)\s*=\s*(_0x[a-f0-9]+)/g);
    for (const m of aliasMatches) {
        decryptFuncNames.add(m[1]);
        decryptFuncNames.add(m[2]);
    }

    // 创建 sandbox
    const sandbox = {
        [arrayFuncName]: function() {
            return shuffledArray;
        },
        parseInt: parseInt,
        String: String
    };

    for (const name of decryptFuncNames) {
        sandbox[name] = decryptFunc;
    }

    // 添加主解密函数
    const mainDecryptMatch = code.match(/function\s+(_0x[a-f0-9]+)\s*\(\s*_0x[a-f0-9]+\s*,\s*_0x[a-f0-9]+\s*\)\s*\{[^]*?const\s+_0x[a-f0-9]+\s*=\s*[a-zA-Z_$][a-zA-Z0-9_$]*\s*\(\s*\)/);
    if (mainDecryptMatch && !sandbox[mainDecryptMatch[1]]) {
        sandbox[mainDecryptMatch[1]] = decryptFunc;
    }

    try {
        const context = vm.createContext(sandbox);
        vm.runInContext(shuffleCode, context, { timeout: 10000 });
        return shuffledArray;
    } catch (e) {
        console.error('  Shuffle execution error:', e.message);
        return stringArray;
    }
}

/**
 * 查找所有解密函数名称（包括别名，递归查找）
 */
function findDecryptFuncInfo(code, arrayFuncName) {
    const result = { names: [], baseOffset: 0 };

    // 查找主解密函数
    // 模式: function _0xXXXX(_0xYYYY, _0xZZZZ) { ... _0xArrayFunc() ... }
    const mainPattern = new RegExp(
        `function\\s+(_0x[a-f0-9]+)\\s*\\(\\s*(_0x[a-f0-9]+)\\s*,\\s*(_0x[a-f0-9]+)\\s*\\)\\s*\\{[^]*?${arrayFuncName}`,
        'g'
    );

    let mainMatch;
    while ((mainMatch = mainPattern.exec(code)) !== null) {
        const funcName = mainMatch[1];
        if (!result.names.includes(funcName)) {
            result.names.push(funcName);

            // 提取基础偏移量
            const funcStart = mainMatch.index;
            const funcBodyStart = code.indexOf('{', funcStart);
            let depth = 1;
            let funcEnd = funcBodyStart + 1;

            while (funcEnd < code.length && depth > 0) {
                if (code[funcEnd] === '{') depth++;
                else if (code[funcEnd] === '}') depth--;
                funcEnd++;
            }

            const funcBody = code.slice(funcStart, funcEnd);

            // 查找偏移量: _0xXXXX = _0xXXXX - 0xYYY
            const offsetMatch = funcBody.match(/_0x[a-f0-9]+\s*=\s*_0x[a-f0-9]+\s*-\s*(0x[a-f0-9]+|\d+)/);
            if (offsetMatch && result.baseOffset === 0) {
                result.baseOffset = parseInt(offsetMatch[1]);
            }
        }
    }

    // 递归查找所有别名
    // 模式: const/var/let _0xXXXX = _0xKnownFunc
    let foundNew = true;
    while (foundNew) {
        foundNew = false;
        for (const knownName of [...result.names]) {
            const aliasPattern = new RegExp(
                `(?:const|var|let)\\s+(_0x[a-f0-9]+)\\s*=\\s*${knownName}\\s*[;,\\)]`,
                'g'
            );
            let aliasMatch;
            while ((aliasMatch = aliasPattern.exec(code)) !== null) {
                if (!result.names.includes(aliasMatch[1])) {
                    result.names.push(aliasMatch[1]);
                    foundNew = true;
                }
            }
        }
    }

    // 还要查找所有实际使用的解密函数名（从调用模式中提取）
    // 这能捕获内联定义的别名
    const callPattern = /(_0x[a-f0-9]+)\s*\(\s*0x[a-f0-9]+\s*,\s*['"][^'"]*['"]\s*\)/g;
    let callMatch;
    while ((callMatch = callPattern.exec(code)) !== null) {
        if (!result.names.includes(callMatch[1])) {
            result.names.push(callMatch[1]);
        }
    }

    return result;
}

/**
 * 替换所有加密字符串调用
 */
function replaceEncryptedStrings(code, shuffledArray, decryptFuncNames, baseOffset) {
    let result = code;
    let totalReplaced = 0;

    for (const funcName of decryptFuncNames) {
        // 匹配解密函数调用: _0xFunc(0xIndex, 'key')
        const callPattern = new RegExp(
            `${funcName}\\s*\\(\\s*(0x[a-f0-9]+|\\d+)\\s*,\\s*(['"])([^'"]*?)\\2\\s*\\)`,
            'gi'
        );

        let match;
        const replacements = [];

        while ((match = callPattern.exec(result)) !== null) {
            const indexStr = match[1];
            const key = match[3];
            const rawIndex = parseInt(indexStr);
            const index = rawIndex - baseOffset;

            if (index >= 0 && index < shuffledArray.length) {
                const encrypted = shuffledArray[index];
                const decrypted = decryptString(encrypted, key);

                if (decrypted !== null) {
                    replacements.push({
                        start: match.index,
                        end: match.index + match[0].length,
                        original: match[0],
                        replacement: JSON.stringify(decrypted)
                    });
                }
            }
        }

        // 从后向前替换
        for (let i = replacements.length - 1; i >= 0; i--) {
            const r = replacements[i];
            result = result.slice(0, r.start) + r.replacement + result.slice(r.end);
        }

        totalReplaced += replacements.length;
    }

    return { result, totalReplaced };
}

/**
 * 反混淆单个文件
 */
function deobfuscateFile(code, filename) {
    console.log(`\n处理: ${filename}`);

    // 1. 提取字符串数组
    const arrayInfo = extractStringArray(code);
    if (!arrayInfo) {
        console.log('  未找到字符串数组');
        return { code, stats: { found: 0, replaced: 0 } };
    }

    console.log(`  字符串数组: ${arrayInfo.funcName} (${arrayInfo.strings.length} 个)`);

    // 2. 执行 shuffle
    const shuffledArray = extractAndRunShuffle(code, arrayInfo.strings, arrayInfo.funcName);
    console.log(`  Shuffle 后: ${shuffledArray.length} 个`);

    // 3. 找到解密函数和偏移量
    const decryptInfo = findDecryptFuncInfo(code, arrayInfo.funcName);
    console.log(`  解密函数: ${decryptInfo.names.join(', ')}`);
    console.log(`  基础偏移: 0x${decryptInfo.baseOffset.toString(16)} (${decryptInfo.baseOffset})`);

    // 4. 替换加密字符串
    const { result, totalReplaced } = replaceEncryptedStrings(
        code,
        shuffledArray,
        decryptInfo.names,
        decryptInfo.baseOffset
    );
    console.log(`  替换: ${totalReplaced} 个字符串`);

    return {
        code: result,
        stats: {
            found: arrayInfo.strings.length,
            replaced: totalReplaced
        }
    };
}

/**
 * 递归处理目录
 */
function processDirectory(inputDir, outputDir) {
    if (!fs.existsSync(outputDir)) {
        fs.mkdirSync(outputDir, { recursive: true });
    }

    const entries = fs.readdirSync(inputDir, { withFileTypes: true });
    let totalStats = { files: 0, found: 0, replaced: 0 };

    for (const entry of entries) {
        const inputPath = path.join(inputDir, entry.name);
        const outputPath = path.join(outputDir, entry.name);

        if (entry.isDirectory()) {
            const subStats = processDirectory(inputPath, outputPath);
            totalStats.files += subStats.files;
            totalStats.found += subStats.found;
            totalStats.replaced += subStats.replaced;
        } else if (entry.name.endsWith('.js')) {
            const code = fs.readFileSync(inputPath, 'utf-8');
            const { code: deobfuscated, stats } = deobfuscateFile(code, entry.name);

            fs.writeFileSync(outputPath, deobfuscated, 'utf-8');

            totalStats.files++;
            totalStats.found += stats.found;
            totalStats.replaced += stats.replaced;
        }
    }

    return totalStats;
}

/**
 * 主函数
 */
function main() {
    const args = process.argv.slice(2);
    const inputDir = args[0] || DEFAULT_INPUT_DIR;
    const outputDir = args[1] || DEFAULT_OUTPUT_DIR;

    console.log('='.repeat(60));
    console.log('CursorPro Deobfuscator v12');
    console.log('='.repeat(60));
    console.log(`输入目录: ${inputDir}`);
    console.log(`输出目录: ${outputDir}`);

    if (!fs.existsSync(inputDir)) {
        console.error(`错误: 输入目录不存在: ${inputDir}`);
        process.exit(1);
    }

    const stats = processDirectory(inputDir, outputDir);

    console.log('\n' + '='.repeat(60));
    console.log('完成!');
    console.log(`  处理文件: ${stats.files}`);
    console.log(`  字符串总数: ${stats.found}`);
    console.log(`  成功替换: ${stats.replaced}`);
    console.log(`  成功率: ${stats.found > 0 ? (stats.replaced / stats.found * 100).toFixed(1) : 0}%`);
    console.log('='.repeat(60));
}

main();
