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


//js混淆代码读取
process.argv.length > 2 ? encodeFile = process.argv[2] : encodeFile = "./main.js";
process.argv.length > 3 ? decodeFile = process.argv[3] : decodeFile = "./decodeResult.js";

//将源代码解析为AST
let sourceCode = fs.readFileSync(encodeFile, {encoding: "utf-8"});

let ast = parser.parse(sourceCode, {sourceType:'script'});


console.time("处理完毕，耗时");


function getRandomName(length) {

    let initArr = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
    let puzzleArr = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
    let ranInx = Math.floor(Math.random() * initArr.length);
    let randomName = initArr[ranInx];

    for (var i = 1; i < length; i++) {
        ranInx = Math.floor(Math.random() * puzzleArr.length);
        randomName += puzzleArr[ranInx];
    }

    return randomName;
}


let allNewNames = new Map();  //定义一个全局变量，保存需要处理的函数

function getUnusedIdentifier() {//获取未被使用的名称,返回 Identifier 类型。
    do {
        var newName = "$C_" + getRandomName(3);

    } while (allNewNames.has(newName))

    allNewNames.set(newName, 1);

    let UnusedIdentifier = types.Identifier(newName);

    return UnusedIdentifier;
}


const renameFunc =
    {
        FunctionDeclaration(path) {
            let {node, parentPath} = path;
            let {id, params, body} = node;
            let name = id.name;

            if (params.length != 2 ||
                !types.isVariableDeclaration(body.body[0]) || !types.isReturnStatement(body.body[1])) {
                return;
            }

            let binding = parentPath.scope.getBinding(name);

            if (!binding || !binding.constant) {
                return;
            }
            let newNameId = getUnusedIdentifier();
            console.log('rename',name,newNameId)
            console.log(generator(path.parent).code)
            for (let referPath of binding.referencePaths) {
                referPath.replaceWith(newNameId);
            }

            let newName = newNameId.name;
            allNewNames.set(newName, name)
            path.node.id.name = newName;
        }
    }

traverse(ast, renameFunc);

allNewNames.clear();


let funcNames = []
// traverse(ast, {
//     FunctionDeclaration(path) {
//         let node = path.node;
//         if (node.id.name === 'Aa') {
//             let code = generator(node).code;
//             if (code.includes('Aa = function ')) {
//                 console.log(node.id.name)
//                 let scope = path.parentPath.parentPath.scope;
//                 getBinding(scope, node.id.name, e => {
//                     // console.log(e.parent.type, generator(e.parent).code)
//                     if (types.isVariableDeclarator(e.parent)) {
//                         let initIdName = e.parent.id.name;
//                         funcNames.push(initIdName)
//                         getBinding(e.parentPath.parentPath.parentPath.parentPath.scope, initIdName, (i => {
//                             // console.log(initIdName, i.parent.type )
//
//                             if (types.isCallExpression(i.parent)) {
//                                 // console.log(generator(i.parent).code)
//                                 let {arguments} = i.parent
//                                 if (arguments.length === 1 && types.isNumericLiteral(arguments[0])) {
//                                     let value = Aa(arguments[0].value)
//                                     console.log(arguments[0].value, value)
//                                     i.parentPath.replaceWith(types.StringLiteral(value))
//
//                                 }
//                             }
//
//                         }))
//                     } else if (types.isCallExpression(e.parent)) {
//                         let {arguments} = e.parent
//                         if (arguments.length === 1 && types.isNumericLiteral(arguments[0])) {
//                             let value = Io(arguments[0].value)
//                             e.parentPath.replaceWith(types.StringLiteral(value))
//
//                         }
//                     }
//                 })
//
//
//             }
//         }
//
//
//     }
// })

console.log(funcNames)
// traverse(ast, {
//     CallExpression(path) {
//         let node = path.node;
//         let {callee, arguments} = node;
//         if (funcNames.includes(callee.name) && arguments.length === 1 && types.isNumericLiteral(arguments[0])){
//             let value = Aa(arguments[0].value)
//             console.log(callee.name, arguments[0].value , value)
//             if (value)path.replaceWith(types.StringLiteral(value))
//
//         }
//     }
// })
function n(n) {

    for (var i = atob(n), l = "IkEiYF9", a = "", c = 0; c < i.length; ++c) {
        var u = l.charCodeAt(c % 7);
        a += String.fromCharCode(u ^ i.charCodeAt(c))
    }
    return a
}

function getBinding(scope, name, func) {
    let binding = scope.getBinding(name);
    console.log(`scope.get(${name}).length = ${binding.references}`)
    if (binding.references > 0) {
        for (const referencePath of binding.referencePaths) {
            func(referencePath)
        }
    }

}

// traverse(ast, {
//     FunctionDeclaration(path) {
//         let node = path.node;
//         if (node.id.name === 'i') {
//             let code = generator(node).code;
//             if (code.includes('charCodeAt')) {
//                 // console.log(node.id.name)
//                 let scope = path.parentPath.parentPath.scope;
//                 getBinding(scope, node.id.name, e => {
//                     // console.log(e.parent.type, generator(e.parent).code)
//                     if (types.isVariableDeclarator(e.parent)) {
//                         let initIdName = e.parent.id.name;
//                         getBinding(e.parentPath.parentPath.parentPath.scope, initIdName, (i => {
//                             // console.log(initIdName, i.parent.type )
//
//                             if (types.isCallExpression(i.parent)) {
//                                 // console.log(generator(i.parent).code)
//                                 let {arguments} = i.parent
//                                 if (arguments.length === 1 && types.isStringLiteral(arguments[0])) {
//                                     let value = n(arguments[0].value)
//                                     // console.log(arguments[0].value, value)
//                                     i.parentPath.replaceWith(types.StringLiteral(value))
//
//                                 }
//                             }
//
//                         }))
//                     } else if (types.isCallExpression(e.parent)) {
//                         let {arguments} = e.parent
//                         if (arguments.length === 1 && types.isStringLiteral(arguments[0])) {
//                             let value = n(arguments[0].value)
//                             e.parentPath.replaceWith(types.StringLiteral(value))
//
//                         }
//                     }
//                 })
//
//
//             }
//         }
//
//
//     }
// })

function H(t) {
    return f(U) === "function" ? U(t) : function (t) {
        var e,
            n,
            r,
            a,
            o = [],
            i = 0,
            c = t.length;
        try {
            if (/[^+/=0-9A-Za-z]/.test(t) || /=/.test(t) && (/=[^=]/.test(t) || /={3}/.test(t))) return null;
            for (c % 4 > 0 && (c = (t += p.Array(4 - c % 4 + 1).join("=")).length); i < c;) {
                for (n = [], a = i; i < a + 4;) n.push("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(t.charAt(i++)));
                for (r = [((e = (n[0] << 18) + (n[1] << 12) + ((63 & n[2]) << 6) + (63 & n[3])) & 255 << 16) >> 16, 64 === n[2] ? -1 : (65280 & e) >> 8, 64 === n[3] ? -1 : 255 & e], a = 0; a < 3; ++a) (r[a] >= 0 || 0 === a) && o.push(String.fromCharCode(r[a]));
            }
            return o.join("");
        } catch (t) {
            return null;
        }
    }(t);
}

// traverse(ast, {
//     CallExpression(path) {
//         let node = path.node;
//         if (node.callee.name === 'H') {
//             let {arguments} = node
//             if (arguments.length === 1 && types.isStringLiteral(arguments[0])) {
//                 let value = atob(arguments[0].value)
//                 path.replaceWith(types.StringLiteral(value))
//
//             }
//         }
//     }
// })

// 常量还原
function isNodePure(node, scope) {
    if (types.isLiteral(node)) {
        return true;
    }

    if (types.isUnaryExpression(node)) {
        return isNodePure(node.argument, scope)
    }

    if (types.isIdentifier(node)) {//处理 var c = String;
        if (scope && scope.isPure(node, true)) {
            return true;
        }

        if (typeof this[node.name] != 'undefined') {
            return true;
        }

        return false;
    }

    if (types.isMemberExpression(node)) {//处理 var d = String.fromCharCode;

        let {object, property, computed} = node;

        if (computed && !isNodePure(property, scope)) {
            return false;
        }
        if (isNodePure(object, scope)) {
            return true;
        }

        if (types.isIdentifier(object)) {

            let name = object.name;

            if (typeof this[name] != 'undefined' && name != 'window') {//注意object为window时，可能会还原出错
                return true;
            }

            return false;
        }
        if (types.isMemberExpression(object)) {
            return isNodePure(object, scope);
        }

        return false;
    }

    if (types.isBinary(node) && scope) {
        return isNodePure(node.left, scope) && isNodePure(node.right, scope);
    }

    return false;
}

traverse(ast, {
    VariableDeclaration(path) {
        let {node} = path;
        let {declarations} = node;
        let res = [];
        // console.log(path.parentPath.type)
        if (types.isForStatement(path.parentPath)) return
        if (!declarations || declarations.length == 1) return
        for (let i = 0; i < declarations.length; i++) {
            let declaration = declarations[i];
            res.push(types.VariableDeclaration('var', [declaration]))
        }
        path.replaceWithMultiple(res)
        path.skip()

    }
})


const restoreVarDeclarator = {

    VariableDeclarator(path) {
        let scope = path.scope;
        let {id, init} = path.node;
        if (!init && (!types.isNumericLiteral(init) || !types.isStringLiteral(init))) return
        if (id.name === 'c' && init.value === 0) return
        if (!types.isIdentifier(id) || !isNodePure(init, scope)) {
            return;
        }

        const binding = scope.getBinding(id.name);
        try {
            var {
                constant, referencePaths, constantViolations
            } = binding;  //变量的定义一定会有binding.

        } catch (e) {
            return;
        }


        if (constantViolations.length > 1) {
            return;
        }

        if (constant || constantViolations[0] == path) {
            for (let referPath of referencePaths) {
                // console.log(init.value)
                // console.log(id.name, referPath.type, generator(referPath.container).code,  generator(init).code)
                referPath.replaceWith(init);
            }
            // console.log(path.toString())
            path.remove();//没有被引用，或者替换完成，可直接删除
        }
    },
}
traverse(ast, restoreVarDeclarator)

//还原object
traverse(ast, {
    VariableDeclaration(path) {
        let {scope, node} = path;
        let {declarations} = node;
        if (!declarations) return

        if (declarations.length !== 1) return;

        let {id, init} = declarations[0]
        if (!types.isObjectExpression(init)) return
        let {properties} = init;

        if (properties.length === 0) return;
        for (let i = 0; i < properties.length; i++) {
            let {key, value} = properties[i];
            if (!types.isNumericLiteral(value)) return
        }


        let binding = scope.getBinding(id.name);
        if (!binding) return
        let {constant, referencePaths, constantViolations} = binding;
        if (!constant) {//新版本的babel库，在循环里面的变量定义，默认非常量
            if (constantViolations.length != 1 || constantViolations[0] != path) //旧版本屏蔽该行即可
            {
                return;
            }
        }

        let newMap = new Map();
        for (const property of properties) {
            let {key, value} = property;
            newMap.set(key.name, value);
        }

        let canBeRemoved = true;
        for (const referPath of referencePaths) {
            let {parentPath} = referPath;
            if (!parentPath.isMemberExpression()) {
                canBeRemoved = false;
                return;
            }

            let AncestorPath = parentPath.parentPath;

            if (AncestorPath.isAssignmentExpression({"left": parentPath.node})) {
                canBeRemoved = false;
                return;
            }
            if (AncestorPath.isUpdateExpression() && ['++', '--'].includes(AncestorPath.node.operator)) {
                canBeRemoved = false;
                return;
            }

            let curKey = parentPath.node.property.name;
            if (!newMap.has(curKey)) {
                canBeRemoved = false;
                break;
            }
            parentPath.replaceWith(newMap.get(curKey));
        }
        canBeRemoved && path.remove();
        newMap.clear();
    }
})

let funcs = {};

traverse(ast, {
    FunctionDeclaration(path) {
        let {id, body} = path.node;
        let scope = path.scope;
        if (body.body.length !== 2) return
        if (!types.isVariableDeclaration(body.body[0]) || !types.isReturnStatement(body.body[1])) return
        let {declarations} = body.body[0];
        if (declarations.length !== 1) return;
        let declaration = declarations[0];
        if (types.isArrayExpression(declaration.init)) {
            let elements = declaration.init.elements;
            if (elements.length === 0) return

            for (let i = 0; i < elements.length; i++) {
                if (!types.isStringLiteral(elements[i])) {
                    return;
                }
            }
            // console.log(generator(path.node).code)
            let binding = scope.getBinding(id.name);
            console.log(generator(path.node).code)
            //  let {id} = parentPath.parentPath.node
            funcs[id.name] = {
                array: generator(path.node).code
            }
        }


    }
})

traverse(ast, {
    FunctionDeclaration(path) {
        let {id, body} = path.node;
        if (body.body.length !== 2) return
        if (!types.isVariableDeclaration(body.body[0]) || !types.isReturnStatement(body.body[1])) return
        let {declarations} = body.body[0];
        if (declarations.length !== 1) return;
        let declaration = declarations[0];
        let {node, scope} = path;
        if (!types.isCallExpression(declaration.init)) {
            return
        }
        if (Object.keys(funcs).includes(declaration.init.callee.name)) {
            funcs[declaration.init.callee.name]['call'] = generator(node).code
            funcs[declaration.init.callee.name]['name'] = id.name
        }
    }
})
traverse(ast, {
    UnaryExpression(path) {
        let {id, body} = path.node;
        let {node, scope} = path;
        let {operator, argument} = node;
        if (operator !== '!') return
        let {callee, arguments} = argument;
        if (!arguments) return
        if (arguments.length !== 1) return
        let arg = arguments[0]
        if (!types.isIdentifier(arg)) return
        if (Object.keys(funcs).includes(arg.name)) {
            funcs[arg.name]['rever'] = generator(node).code
        }
    }
})
// console.log(funcs)


var newMap = {}
let keys = Object.keys(funcs);
let keys2 = [];
for (let i = 0; i < keys.length; i++) {
    newMap[funcs[keys[i]].name] = funcs[keys[i]]
    keys2.push(funcs[keys[i]].name)
}
var map2 = {};
// 开始还原
let funcTemp = {}
for (let j = 0; j < 20; j++) {
    traverse(ast, {
        VariableDeclaration(path) {
            let {declarations} = path.node;
            let scope = path.scope;
            if (declarations.length !== 1) return;
            let declaration = declarations[0]
            let {id, init} = declaration;
            if (!init) return
            if (!types.isIdentifier(init)) return;
            if (init.name === 'Bo') {
                debugger;
            }

            if (keys2.includes(init.name)) {
                let t = newMap[init.name];
                let jsCode = t.array + ';' + t.rever + ';' + t.call;
                // console.log(jsCode)
                jsCode = `funcTemp.${t.name} = function(t,e){
                ${jsCode}
                return ${t.name}(t,e)
                }`
                eval(jsCode)
                // console.log('eval', t.name)
                keys2.push(id.name)
                newMap[id.name] = t;
                map2[id.name] = t.name
                funcTemp[id.name] = funcTemp[t.name]
                map2[init.name] = t.name
                let binding = scope.getBinding(id.name);

                if (!binding) return;
                // console.log(id.name, init.name, keys2.includes(init.name), binding.references)


                if (id.name === 'Bo') {
                    // console.log(id.name, init.name, keys2.includes(init.name), binding.references)
                    debugger
                }

                for (const referencePath of binding.referencePaths) {
                    // console.log(referencePath.parentPath.type);
                    if (types.isCallExpression(referencePath.parent)) {
                        let {callee, arguments} = referencePath.parent;
                        if (id.name === 'zo') {

                            // console.log(generator(referencePath.parentPath.node).code)
                        }
                        if (arguments.length === 1 && types.isNumericLiteral(arguments[0])) {
                            let res = funcTemp[t.name](arguments[0].value)
                            if (init.name === 'Bo') {

                                console.log(init.name, id.name, arguments[0].value, res)
                            }
                            referencePath.parentPath.replaceWith(types.StringLiteral(res))
                        }
                    }
                }

            }
        }
    })
}
// traverse(ast, {
//     CallExpression(path) {
//         let {callee, arguments} = path.node;
//         if (map2[callee.name]){
//             if (arguments.length === 1 && types.isNumericLiteral(arguments[0])) {
//             let res = funcTemp[callee.name](arguments[0].value)
//                 console.log(callee.name, arguments[0].value, res)
//                 if (!res)return
//             // if (id.name === 'zo') {
//
//                 // console.log(arguments[0].value, res)
//             // }
//             path.replaceWith(types.StringLiteral(res))
//         }
//         }
//
//     }
// })
console.log(keys2 + '')

function t() {
    return Date.now();
}

var i = function () {
    try {
        if (atob && "test" === atob("dGVzdA==")) return atob;
    } catch (t) {
    }

    function t(t) {
        this.message = t;
    }

    t.prototype = new Error(), t.prototype.name = "InvalidCharacterError";
    return function (e) {
        var n = String(e).replace(/[=]+$/, "");
        if (n.length % 4 == 1) throw new t("'atob' failed: The string to be decoded is not correctly encoded.");
        for (var r, i, a = 0, o = 0, c = ""; i = n.charAt(o++); ~i && (r = a % 4 ? 64 * r + i : i, a++ % 4) ? c += String.fromCharCode(255 & r >> (-2 * a & 6)) : 0) i = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(i);
        return c;
    };
}();

function o(n) {
    var r = t();
    for (var c = i(n), u = "", l = 0; l < c.length; ++l) {
        var s = "flEpkiz".charCodeAt(l % 7);
        u += String.fromCharCode(s ^ c.charCodeAt(l));
    }
    return u;
}

traverse(ast, {
    VariableDeclaration(path) {
        let {declarations} = path.node;
        let scope = path.scope;
        if (declarations.length !== 1) return;
        let declaration = declarations[0]
        let {id, init} = declaration;
        if (!init) return
        if (!types.isIdentifier(init)) return;
        if (init.name === 'i') {
            let binding = scope.getBinding(id.name);
            if (id.name === 'xs') {
                debugger
            }
            if (!binding) return;
            console.log(id.name, init.name, binding.references)

            for (const referencePath of binding.referencePaths) {
                // console.log(referencePath);
                // console.log(generator(referencePath.parent).code)
                // console.log(referencePath.parentPath.type)

                if (types.isCallExpression(referencePath.parent)) {
                    let {callee, arguments} = referencePath.parent;

                    if (arguments.length === 1 && types.isStringLiteral(arguments[0])) {
                        let res = o(arguments[0].value)
                        // if (id.name === 'xs') {
                        //
                        // console.log(generator(referencePath.parent).code, res)
                        // }
                        referencePath.parentPath.replaceWith(types.StringLiteral(res))
                    }
                } else {

                }
            }
        }

    }
})


//end
console.timeEnd("处理完毕，耗时");


let {code} = generator(ast, opts = {jsescOption: {"minimal": true}});

fs.writeFile(decodeFile, code, (err) => {
});