import {DUMP_FILE_PATH, soName} from "./dumpconfig";
import {il2cppApi} from "./il2cpp/il2cppApi";
import {log} from "./logger";

let classAllCount = 0;
let file = new File(DUMP_FILE_PATH, "wb");
let il2cpp_got = false;
let once =false;
export var dumper = {
    waitInject: function () {
        log("waitInject");

        let open = Module.findExportByName(null, "open");
        //fopen替换
        log("等待Il2cpp:" + open);
        if (open != null) {
            Interceptor.attach(open, {
                onEnter: function (args) {
                    let path = args[0].readCString();
                    // log("path:" + path);
                    if (path.indexOf(soName) !== -1) {
                        this.hook = true;
                    }

                },
                onLeave: function (retval) {
                    // log("this.hook:" + this.hook);
                    if (this.hook) {
                        il2cpp_got = true;
                        // Interceptor.detachAll();
                        dumper.start();
                    }
                }
            })
        }
    },
    start: function () {
        let module = Process.findModuleByName(soName);
        log("module:"+module);
        if (module == null) {
            if (il2cpp_got) {
                setTimeout(function () {
                    //执行
                    dumper.start();
                }, 5000);
                return;
            }
            this.waitInject();
            return
        }
        //延迟一下
        log("module "+module.path +" addr "+module.base);
        setTimeout(function (){
            if (once){
                return
            }
            once=true;
            module = Process.findModuleByName(soName);
            let baseAddress = module.base;

            log("base address:" + baseAddress);


            let domain = il2cppApi.il2cpp_domain_get();
            let size_t = Memory.alloc(Process.pointerSize);
            log("domain:" + domain + " baseAddress:" + baseAddress);
            //可能还没加载

            let assemblies = il2cppApi.il2cpp_domain_get_assemblies(domain, size_t);
            let assemblies_count = size_t.readInt();
            log("assemblies_count:" + assemblies_count + " pointerSize:" + Process.pointerSize
                + " assemblies:" + assemblies);
            if (assemblies_count===0){
                setTimeout(function (){
                    this.start();
                },2000);
                return;
            }
            let il2CppImageArray = new Array();

            for (let i = 0; i < assemblies_count; i++) {
                let assembly = assemblies.add(Process.pointerSize * i).readPointer();

                let Il2CppImage = il2cppApi.il2cpp_assembly_get_image(assembly);
                let typeStart = Il2CppImage.typeStart();
                log("typeStart:" + typeStart + " name:" + Il2CppImage.nameNoExt()+" typeCount:"+Il2CppImage.typeCount());
                dumper.out(" // Image :" + i + " " + Il2CppImage.nameNoExt() + " - " + Il2CppImage.typeStart() + "\n")
                il2CppImageArray.push(Il2CppImage);
            }
            for (let i = 0; i < il2CppImageArray.length; i++) {
                log("process: " + (i + 1) + "/" + assemblies_count);
                let Il2CppImage = il2CppImageArray[i];
                let nameNoExt = Il2CppImage.nameNoExt();
                let start = Il2CppImage.typeStart();
                let class_count = Il2CppImage.typeCount();
                // log("name:"+nameNoExt +" start:"+start +" count:"+class_count)
                // if (nameNoExt === "Assembly-CSharp") {
                // // dll
                // this.out("\n//assembly Image -->:" + nameNoExt + "    startIndex:" + start + "   typeCount:" + class_count);
                dumper.findAllClass(Il2CppImage);
                // }
            }
            log("dump end")
            log("classAllCount:" + classAllCount);
            // log("nativeFunNotExistMap:" + il2cppApi.nativeFunNotExistMap.size);
            if (il2cppApi.nativeFunNotExistMap.size > 0) {
                log("some NativeFun is un exist ,parser will be not accurate :");
                il2cppApi.nativeFunNotExistMap.forEach(function (value, key) {
                    log(key + "");
                })
            }
            log("module "+module.path +" addr "+module.base);

        },20000);



    },
    findAllClass: function (il2cppImage) {
        let class_count = il2cppImage.typeCount();
        classAllCount = classAllCount + class_count;
        log("findAllClass "+il2cppImage.name()+"  class_count:" + class_count)
        for (let i = 0; i < class_count; i++) {
            log("class process:"+i +"/"+class_count)
            let il2CppClass = il2cppImage.getClass(i);
            let il2CppType = il2CppClass.getType();
            let declaringType = il2CppClass.getDeclaringType();
            if (!declaringType.isNull()) {
                // log("declaringType:" + declaringType.name() + " class:" + il2CppClass.name());
            }
            this.dumpType(il2CppType);
        }
    },
    dumpType: function (il2CppType) {
        let klass = il2cppApi.il2cpp_class_from_type(il2CppType);
        let il2CppImage = il2cppApi.il2cpp_class_get_image(klass);
        this.out("\n//Namespace:" + klass.namespaze() + "  Image->" + il2CppImage.name() + "\n")
        let flags = klass.flags();
        let Serializable = flags & tabledefs.TYPE_ATTRIBUTE_SERIALIZABLE;
        if (Serializable) {
            this.out('[Serializable]\n')
        }
        let visibility = flags & tabledefs.TYPE_ATTRIBUTE_VISIBILITY_MASK;
        switch (visibility) {
            case tabledefs.TYPE_ATTRIBUTE_PUBLIC:
            case tabledefs.TYPE_ATTRIBUTE_NESTED_PUBLIC:
                this.out("public ")
                break;
            case tabledefs.TYPE_ATTRIBUTE_NOT_PUBLIC:
            case tabledefs.TYPE_ATTRIBUTE_NESTED_FAM_AND_ASSEM:
            case tabledefs.TYPE_ATTRIBUTE_NESTED_ASSEMBLY:
                this.out("internal ")
                break;
            case tabledefs.TYPE_ATTRIBUTE_NESTED_PRIVATE:
                this.out("private ")
                break;
            case tabledefs.TYPE_ATTRIBUTE_NESTED_FAMILY:
                this.out("protected ")
                break;
            case tabledefs.TYPE_ATTRIBUTE_NESTED_FAM_OR_ASSEM:
                this.out("protected internal ")
                break;
        }
        let isValuetype = klass.valueType();
        let IsEnum = klass.enumType();
        if (flags & tabledefs.TYPE_ATTRIBUTE_ABSTRACT && flags & tabledefs.TYPE_ATTRIBUTE_SEALED) {
            this.out("static ")
        } else if (!(flags & tabledefs.TYPE_ATTRIBUTE_INTERFACE) && flags & tabledefs.TYPE_ATTRIBUTE_ABSTRACT) {
            this.out("abstract ")
        } else if (!isValuetype && !IsEnum && flags & tabledefs.TYPE_ATTRIBUTE_SEALED) {
            this.out("sealed ")
        }
        if (flags & tabledefs.TYPE_ATTRIBUTE_INTERFACE) {
            this.out("interface ")
        } else if (IsEnum) {
            this.out("enum ")
        } else if (isValuetype) {
            this.out("struct ");
        } else {
            this.out("class ");
        }
        let name = klass.name();
        //获取泛型
        if (name.indexOf("`") !== -1) {
            let split = name.split("`");
            name = split[0];
            name = name + klass.getGenericName();
        }
        this.out(name + " ");
        let klass_parent = klass.parent();
        //父类
        let hasParent = false;
        if (!isValuetype && !IsEnum && !klass_parent.isNull()) {
            let parent_cls_type = klass_parent.getType();
            let typeEnum = parent_cls_type.getTypeEnum();
            if (typeEnum === Il2CppTypeEnum.IL2CPP_TYPE_OBJECT) {
                //not out
            } else {
                hasParent = true;
                this.out(": " + klass_parent.name());
            }
        }
        //实现接口类
        let iter = Memory.alloc(Process.pointerSize);
        let interfaces;
        while (!(interfaces = klass.getInterfaces(iter)).isNull()) {
            let interfaces_name = interfaces.name();
            if (interfaces_name.indexOf("`") !== -1) {
                let split = interfaces_name.split("`");
                interfaces_name = split[0];
                interfaces_name = interfaces_name + interfaces.getGenericName();
            }
            if (!hasParent) {
                this.out(": " + interfaces_name)
                hasParent = true;
            } else {
                this.out(", " + interfaces_name);
            }
        }
        this.out("\n{\n")
        this.dumpFiled(klass);
        this.dumpPropertyInfo(klass);
        this.dumpMethod(klass);
        this.out("\n}");

    },
    dumpMethod: function (klass) {
        let iter = Memory.alloc(Process.pointerSize);
        let methodInfo;
        let isFirst = true;
        let baseAddr = Module.findBaseAddress(soName);
        while (!(methodInfo = klass.getMethods(iter)).isNull()) {
            if (isFirst) {
                this.out("\n\t//methods\n");
                isFirst = false;
            }

            let methodPointer = methodInfo.getMethodPointer();
            let generic = methodInfo.is_generic();
            let inflated = methodInfo.is_inflated();
            // log("generic:"+generic +" inflated:"+inflated +"name:"+methodInfo.name());
            if (!methodPointer.isNull()) {
                let number = methodPointer - baseAddr;
                if (number===0x4CC8B94){
                    let nativePointer = klass.add(16).readPointer();
                    logHHex(nativePointer);
                    log("class :"+klass.name()+ "length:"+klass.name().length);
                }
                this.out("\t// RVA: 0x" + number.toString(16).toUpperCase());
                this.out("  VA: 0x");
                this.out(methodPointer.toString(16).toUpperCase());

            } else {
                this.out("\t// RVA: 0x  VA: 0x0");
            }
            //非必须
            // log("slot:" + methodInfo.getSlot());
            // if (methodInfo.getSlot() !== 65535) {
            //     this.out(" Slot: " + methodInfo.getSlot());
            // }
            this.out("\n\t");
            let methodModifier = utils.get_method_modifier(methodInfo.getFlags());
            this.out(methodModifier);

            let returnType = methodInfo.getReturnType();
            let return_cls = il2cppApi.il2cpp_class_from_type(returnType);
            this.out(return_cls.name() + " " + methodInfo.name() + "(");
            let paramCount = methodInfo.getParamCount();
            // log("paramCount:" + paramCount);
            if (paramCount > 0) {
                for (let i = 0; i < paramCount; i++) {
                    let paramType = methodInfo.getParam(i);
                    let paramCls = il2cppApi.il2cpp_class_from_type(paramType);
                    let name = paramCls.name();
                    //获取泛型
                    if (name.indexOf("`") !== -1) {
                        let split = name.split("`");
                        name = split[0];
                        name = name + paramCls.getGenericName();
                    }
                    this.out(name + " " + methodInfo.getParamName(i));
                    if (i + 1 !== paramCount) {
                        this.out(", ");
                    } else {
                        this.out(") { }\n");
                    }
                }
            } else {
                this.out("){ }\n");
            }

        }
    },

    dumpPropertyInfo: function (klass) {
        let iter = Memory.alloc(Process.pointerSize);
        let propertyInfo;
        let isFirst = true;
        while (!(propertyInfo = klass.getProperties(iter)).isNull()) {
            if (isFirst) {
                this.out("\n\t// Properties\n");
                isFirst = false;
            }
            this.out("\t");
            //获取getSet
            // log(" dumpPropertyInfo get:" + propertyInfo.getMethod().isNull());
            let pro_class;
            let method = propertyInfo.getMethod();
            let setMethod = propertyInfo.setMethod();
            if (method.isNull() && setMethod.isNull()) {

                continue;
            }
            if (!method.isNull()) {
                let methodModifier = utils.get_method_modifier(method.getFlags());
                // let methodPointer = method.getMethodPointer()
                // log("methodModifier:" + methodModifier + " methodPointer:" + methodPointer);
                this.out(methodModifier);
                pro_class = il2cppApi.il2cpp_class_from_type(method.getReturnType());
            } else if (!setMethod.isNull()) {
                let setModifier = utils.get_method_modifier(setMethod.getFlags());
                this.out(setModifier);
                pro_class = il2cppApi.il2cpp_class_from_type(setMethod.getReturnType());
            }
            // log("pro_class:"+pro_class +"propertyInfo:"+propertyInfo.getName() +" method:"+method +" setMethod:"+setMethod)
            this.out(pro_class.name() + " " + propertyInfo.getName() + " { ");

            if (!method.isNull()) {
                this.out("get; ");
            }
            if (!setMethod.isNull()) {
                this.out("set; ");
            }
            this.out("}\n");
        }
    },
    dumpFiled: function (klass) {
        // log("dumpFiled class :" + klass.name())
        let filedCount = klass.filedCount();
        // log("fieldCount:" + filedCount);
        if (filedCount > 0) {
            let iter = Memory.alloc(Process.pointerSize);
            let filedInfo;
            this.out("\t//Fileds\n");
            while (!(filedInfo = klass.getFieldsInfo(iter)).isNull()) {
                let flags = filedInfo.getFlags();
                this.out("\t")
                let access = flags & tabledefs.FIELD_ATTRIBUTE_FIELD_ACCESS_MASK;
                switch (access) {
                    case tabledefs.FIELD_ATTRIBUTE_PRIVATE:
                        this.out("private ")
                        break;
                    case tabledefs.FIELD_ATTRIBUTE_PUBLIC:
                        this.out("public ")
                        break;
                    case tabledefs.FIELD_ATTRIBUTE_FAMILY:
                        this.out("protected ")
                        break;
                    case tabledefs.FIELD_ATTRIBUTE_ASSEMBLY:
                    case tabledefs.FIELD_ATTRIBUTE_FAM_AND_ASSEM:
                        this.out("internal ")
                        break;
                    case tabledefs.FIELD_ATTRIBUTE_FAM_OR_ASSEM:
                        this.out("protected internal ")
                        break;
                }
                if (flags & tabledefs.FIELD_ATTRIBUTE_LITERAL) {
                    this.out("const ")
                } else {
                    if (flags & tabledefs.FIELD_ATTRIBUTE_STATIC) {
                        this.out("static ")
                    }
                    if (flags & tabledefs.FIELD_ATTRIBUTE_INIT_ONLY) {
                        this.out("readonly ")
                    }
                }
                let fieldClass = filedInfo.getFiledClass();
                let name = fieldClass.name(); //参数名
                let offset = filedInfo.getOffset();//偏移
                // //如果是泛型变量则进行补充
                if (name.indexOf("`") !== -1) { //`1 `2 `3 说明是泛型类型 解析泛型变量
                    let genericName = fieldClass.getGenericName();
                    let split = name.split("`");
                    name = split[0];
                    name = name + genericName;
                }
                this.out(name + " " + filedInfo.getFiledName());
                //获取常量的初始值
                // let filed_info_cpp_type = filedInfo.getType(); //获取变量参数类型
                // log("filed_info_cpp_type:" + filed_info_cpp_type.getTypeEnum() + name + " " + filedInfo.getFiledName());

                if (flags & tabledefs.FIELD_ATTRIBUTE_LITERAL) {
                    // let staticValue = filedInfo.getStaticValue();
                    // if (staticValue !== null) {
                    //     this.out(" = " + staticValue + ";\n");
                    // }
                    this.out(";\n");
                } else {
                    this.out(" ;// 0x" + offset.toString(16).toUpperCase() + "\n");
                }

            }
        }
    },

    out: function (string) {
        file.write(string);
        file.flush();
    }
}