Disassembler.js

var Chalk = require("chalk");
var Op = require("./Opcode.js");
const CONST = require("./CoreConst.js");
const CLASS = require("./CoreClass.js");

var OPCODE = Op.OPCODE;


function Patch(instr){
    this.patch = instr
}
Patch.prototype.alwaysTrue = function(){

}
Patch.prototype.alwaysFalse = function(){
    
}
Patch.prototype.replace = function(){
    
}
Patch.prototype.move = function(){
    
}
Patch.prototype.deploy = function(){

}

function MethodEditor(method){


    /**
     * @param {*} bbNo Basic Block index
     * @param {*} instrNo Instr index
     */
    this.taint = function(bbNo,instrNo,reg){

    };

    /**
     * 
     * @param {*} bbNo Basic Block index
     * @param {*} instrNo Instr index
     */
    this.patch = function(bbNo,instrNo){

    };

    // do return after the given instr
    this.cut = function(instrNo){

    }
}

function Disassembler(){

}

Disassembler.prototype.method = function(method){

        let bb=null, txt="", prefix="";
        
        console.log("\n  "+method.signature());
        for(let i in method.instr){


            bb=method.instr[i];

            if(bb.tag !== null){
                prefix = Chalk.bold.blue(bb.tag)+"   ";
            }else{
                prefix = "";
            }
            

            if(bb.cond_name != null)
                console.log("\n :cond_"+bb.cond_name+"\n");
            if(bb.goto_name != null)
                console.log("\n :goto_"+bb.goto_name+"\n");

            for(let j in bb.stack){
                if(bb.stack[j] == null || bb.stack[j].opcode === undefined) continue;

                txt = prefix+"\t<"+i+","+j+">\t";

                if(bb.stack[j].opcode.instr.indexOf("if-")>-1){
                    txt += Chalk.bold.white(bb.stack[j]._raw);
                }
                else if(bb.stack[j].opcode.instr.indexOf("goto")>-1){
                    txt += Chalk.bold.blue(bb.stack[j]._raw);
                }
                else if(bb.stack[j].opcode.instr.indexOf("invoke")>-1){
                    if(bb.stack[j].right.alias != null){
                        txt += Chalk.bold.yellow(
                            bb.stack[j].opcode.instr+" {...} "+bb.stack[j].right.prettySignature());
                    }else{
                        txt += Chalk.bold.yellow(bb.stack[j]._raw);   
                    }
                }
                else if(bb.stack[j].opcode.type == CONST.INSTR_TYPE.NOP){
                    txt += "nop";
                }
                else if(bb.stack[j].opcode.instr.indexOf("const-string")>-1){
                    txt += bb.stack[j].opcode.instr;
                    txt += " "+bb.stack[j].left.t+bb.stack[j].left.i+", ";
                    txt += Chalk.bold.red('"'+bb.stack[j].right._value+'"');
                }
                else{
                    txt += bb.stack[j]._raw;
                }
                console.log(txt);
            }
        }

        return this;
    };

    /*
            switch(bb.stack[j].opcode.byte){
                case OPCODE.INVOKE_STATIC.byte:
                    if(bb.stack[j+1].opcode.byte == OPCODE.MOVE_RESULT){
                        txt = bb.stack[j+1].left.t;
                        txt += bb.stack[j+1].left.i+" = ";
                    }
                    txt += bb.stack[j].right.signature()+';';
                    break;
                case OPCODE.NEW_INSTANCE.byte:
                    break;
                case OPCODE.CONST_STRING.byte:
                case OPCODE.CONST_STRING_JUMBO.byte:
                    txt = bb.stack[j+1].left.t+bb.stack[j+1].left.t+" = ";
                    txt += '"'+bb.stack[j+1].right.value+'";';
                break;
            }
            */

Disassembler.prototype.block = function(method,bb,nested,tagged){
    let before = " ".repeat((nested*2)+1);
    let ignore = [];
    //before += "╚═";

    // for each instruction
    for(let j=0; j<bb.stack.length; j++){
        txt = "\t<"+bb.offset+","+j+">\t";
        
        if(nested > 0){
            if(j==0 && nested>0 && tagged) 
                txt += (" ".repeat((nested*2)-1))+"╚═╗";
            else
                txt += before+"║";
        }
        
        if(bb.stack[j].opcode.instr.indexOf("if-")>-1){
            txt += Chalk.bold.white(bb.stack[j]._raw);
            console.log(txt);

            taggedBlock = method.getTaggedBlock(":cond_"+bb.stack[j].right.name);
            tagged = true;
            this.block(method,taggedBlock,nested+1,true);

            do{
                taggedBlock = method.getBlock(taggedBlock.offset+1);
                if(taggedBlock !==null){
                    ignore.push(taggedBlock.offset);
                    this.block(method,taggedBlock,nested,false);
                }
            }while(taggedBlock !== null && 
                (!taggedBlock.hasInstr(CONST.INSTR_TYPE.GOTO)
                ||!taggedBlock.hasInstr(CONST.INSTR_TYPE.RET)));

                
        }
        else{
            if(bb.stack[j].opcode.type===CONST.INSTR_TYPE.GOTO){
                txt += Chalk.bold.blue(bb.stack[j]._raw);
            }
            else if(bb.stack[j].opcode.instr.indexOf("invoke")>-1){
                txt += Chalk.bold.yellow(bb.stack[j]._raw);
            }
            else if(bb.stack[j].opcode.instr.indexOf("const-string")>-1){
                txt += bb.stack[j].opcode.instr;
                txt += " "+bb.stack[j].left.t+bb.stack[j].left.i+", ";
                txt += Chalk.bold.red('"'+bb.stack[j].right._value+'"');
            }
            else{
                txt += bb.stack[j]._raw;
            }
            console.log(txt);
        }
        return { offset: bb.offset, ignore:ignore };
    }
};

Disassembler.prototype.methodPretty = function(method){

    let bb=null, txt="", ignore=[], state={};
    console.log("\n  "+method.signature());
    for(let i in method.instr){
        bb=method.instr[i];
        if(bb.tag !== null && bb.tag.startsWith(":cond_")) continue;
        if(ignore.indexOf(i)>-1);

        state = this.block(method,bb,0);
        ignore.concat(state.ignore);
    }

    return this;
};

Disassembler.prototype.methodFull = function(method){
    let bb=null, txt="", prefix="", bbe={}, line={}, result="";
    
    for(let i in method.instr){
        bb=method.instr[i];
        //result += 
    }
};

Disassembler.prototype.methodRaw = function(method){

    let bb=null, txt="", prefix="", bbe={}, line={}, result=[], c={};
    
    for(let i in method.instr){
        bb=method.instr[i];
        bbe={
            tag: null,
            instr: []
        };

        /*if(bb.isGotoBlock()){
            bbe.instr.push(bb.goto_name);
        }*/
        /*if(bb.line>-1){
            bbe.instr.push({ value:".line "+bb.line });
        }*/
        if(bb.isGotoBlock()){
            bbe.instr.push({ value:":goto_"+bb.goto_name });
        }
        if(bb.isConditionalBlock()){
            bbe.instr.push({ value:":cond_"+bb.cond_name });
        }
        if(bb.isTryBlock()){
            bbe.instr.push({ value:bb.try_name });
        }
        if(bb.isCatchBlock()){
            bbe.instr.push({ value:bb.catch_name });
        }
        if(bb.isSwitchCase()){
            bbe.instr.push({ value:bb.getSwitchCaseName() });
        }
 
        if(bb.isSwitchStatement()){
            
            bbe.instr.push({ value:bb.getSwitchStatementName() });
            
            if(bb.switch != null){

                if(bb.switch instanceof CLASS.PackedSwitchStatement){
                    bbe.instr.push({ value:".packed-switch 0x"+bb.switch.getStartValue().toString(16) });
                }else{
                    bbe.instr.push({ value:".sparse-switch" });
                }

                //bbe.instr.push({ value:bb.switch.name+" 0x"+bb.switch.getStartValue() });

                for(let j in bb.switch.cases){
                    c = bb.switch.cases[j];
                    if(c instanceof CLASS.SwitchCase){
                        if(c.type == CONST.CASE_TYPE.PACKED)
                            bbe.instr.push({ value:"    :pswitch_"+c.value.toString(16) });
                        else
                            bbe.instr.push({ value:"    "+c.value+" -> "+c.target });

                    }
    
                }

                if(bb.switch instanceof CLASS.PackedSwitchStatement){
                    bbe.instr.push({ value:".end packed-switch " });
                }else{
                    bbe.instr.push({ value:".end sparse-switch" });
                }
            }
        }
        else if(bb.switch_statement != null || bb.switch != null){
            //console.log(bb);
        }

        if(bb.tag !== null){
            bbe.tag = bb.tag;
        }
        
        for(let j in bb.stack){
            if(bb.stack[j].opcode === undefined){
                //console.log(bb.stack[j]._raw);
                continue;
            }

            // if instruction has "line number" metadata
            if(bb.stack[j].iline != null){
                bbe.instr.push({ value:".line "+bb.stack[j].iline });
            }

            line = {
                if: false,
                goto: false,
                invoke: false,
                const: false
            };
            line.tag = bb.tag;
            line.bb_offset = i;
            line.bb_roffset = j;



            
            if(bb.stack[j].opcode.instr.indexOf("if-")>-1){
                line.if = true;
                line.value = bb.stack[j]._raw;
                //line.value = '<i class="code-pink">'+bb.stack[j]._raw+'</i>';
            }
            else if(bb.stack[j].opcode.type==CONST.INSTR_TYPE.GOTO){
                line.goto = true;
                line.value = bb.stack[j]._raw;
                //line.value = '<i class="code-blue">'+bb.stack[j]._raw+'</i>';
            }
            else if(bb.stack[j].opcode.instr.indexOf("invoke")>-1){
                    line.value = bb.stack[j]._raw;   

            }
            else if(bb.stack[j].opcode.instr.indexOf("const-string")>-1){
                line.string = true;
                line.value = bb.stack[j]._raw;
            }
            else{
                line.value = bb.stack[j]._raw;
            }    
 

            bbe.instr.push(line);
        }
        if(bb.getTryEndName() != null){
            bbe.instr.push({ value:bb.getTryEndName() });
        }
        if(bb.hasCatchStatement()){
            prefix = bb.getCatchStatements();
            for(let k=0; k<prefix.length; k++){
                if(prefix[k].getException()==null)
                    txt = `.catchall {${prefix[k].getTryStart().getTryStartLabel()} .. ${prefix[k].getTryEnd().getTryEndName()}} ${prefix[k].getTarget().getCatchLabel()}`;
                else
                    txt = `.catch ${prefix[k].getException().name} {${prefix[k].getTryStart().getTryStartLabel()} .. ${prefix[k].getTryEnd().getTryEndName()}} ${prefix[k].getTarget().getCatchLabel()}`;

                bbe.instr.push({ value:txt });
            }

        }

        result.push(bbe);
    }

    let d=null;

    for(let j in method.datas){
        d = method.datas[j];
        bbe={
            tag: null,
            instr: []
        };

        bbe.instr.push({ value: d.name });
        bbe.instr.push({ value:CONST.LEX.STRUCT.ARRAY+"  "+d.getByteWidth() });
        for(let k=0; k<d.count(); k++){
             c = (d.read(k) < 0x7f && d.read(k) > 0x10)? "// '"+String.fromCharCode(d.read(k))+"'" : '';

             if(d.read(k)>=0)
                 bbe.instr.push({ value:`    0x${d.read(k).toString(16)}  ${c}` });  
             else
                 bbe.instr.push({ value:`    ${d.read(k).toString(16).replace('-','-0x')}  ${c}` });  
        }
        bbe.instr.push({ value:CONST.LEX.STRUCT.END+"  "+CONST.LEX.STRUCT.ARRAY_NAME});
        result.push(bbe);
    }
    return result;
};

module.exports = new Disassembler();