#include "disasm.h"
#include "globals.h"
#include "instruction.h"

disasm::disasm(void)
{
	x86_init(opt_none, NULL);
	force_quit=false;
}

disasm::~disasm(void)
{
	x86_cleanup();
}

int disasm::follow_insn_dest( x86_insn_t *insn ) {
        if ( insn->type == insn_jmp || insn->type == insn_jcc ||
             insn->type == insn_call || insn->type == insn_callcc ) {
                return(1);
        }
        return(0);
}

void disasm::do_instruction(x86_insn_t *insn)
{
	char line[4096];
	int i;
	
	/*if ( x86_format_insn(insn, line, 4096, intel_syntax) <= 0 ) {
		return;
	}
	printf("%X %X | %s\n",insn->offset,insn->addr,line);*/
	if(globals::instance()._map[insn->offset]==0)
		globals::instance()._map[insn->offset]=new instruction(insn);
}

int disasm::insn_doesnt_return( x86_insn_t *insn ) {
        return( (insn->type == insn_jmp || insn->type == insn_return) ? 1: 0 );
}

long disasm::resolver( x86_op_t *op, x86_insn_t *insn )
{
		
		if (globals::instance()._map[insn->offset] !=0 )
		{
			if (globals::instance()._map[insn->offset]->done())
			{
				/* we have seen this one already; return -1 */
				//printf("tum tum tum!!already seen %X!!!\n",insn->offset);
				return(-1);
			}
			else
			{
				globals::instance()._map[insn->offset]->done(true);
			}
		}

		if(force_quit)
		{
			return -1;
		}

        return resolve(op,insn) ;
}

long disasm::resolve( x86_op_t *op, x86_insn_t *insn ){
        long next_addr = -1;
		
        if ( x86_optype_is_address(op->type) ) {
                next_addr = op->data.sdword;
        } else if ( op->type == op_relative_near ) {
		next_addr = insn->addr + insn->size + op->data.relative_near;
        } else if ( op->type == op_relative_far ) {
		next_addr = insn->addr + insn->size + op->data.relative_far;
        }
        return( next_addr );
}

int disasm::do_disasm( unsigned int offset)
{

	int result=0;

	result=disasm_func(offset);

	long next;
	std::list<long>::iterator iter;
	std::list<long> tocall = globals::instance()._call_list;

	for(iter=tocall.begin();iter!=tocall.end();++iter)
	{
		next=(*iter);
		//printf("doing %X\n",next);
		globals::instance()._call_list.remove(next);
		do_disasm(next);
	}

	return result;
}

int disasm::disasm_func( unsigned int offset)
{
        x86_insn_t insn;
        x86_op_t *op;
        long next_addr;
        unsigned long next_offset;

        int size, count = 0, bytes = 0, cont = 1;
		globals::instance()._done.push_back(offset);

        while ( cont && bytes < buf_len ) {
                size = x86_disasm( buf, buf_len, buf_rva, offset + bytes,
                           &insn );

                if ( size ) {
                        /* invoke callback if it exists */
                        do_instruction(&insn);
                        bytes += size;
                        count ++;
                } else {
                        /* error */
                        /* bytes++;        /* try next byte */
						/* on s'arrete apres une erreur!*/
						globals::instance()._map[offset + bytes]=new instruction();
						globals::instance()._map[offset + bytes]->done(true);
						break;
                }

                if ( this->follow_insn_dest(&insn) ) {
                        op = x86_get_dest_operand( &insn );
                        next_addr = -1;

                        /* if caller supplied a resolver, use it to determine
                         * the address to disassemble */
                         next_addr = this->resolver(op, &insn);

                        if (next_addr != -1 ) {
                                next_offset = next_addr - buf_rva;
                                /* if offset is in this buffer... */
                                if ( next_offset >= 0 &&
                                     next_offset < buf_len ) {
											/* go ahead and disassemble */
										
										globals::instance()._map[insn.offset]->next_flow=next_offset;
										if(insn.type == insn_jmp)
											globals::instance()._map[insn.offset]->next=0;

										if(insn.type == insn_call)
										 {
											 //printf("insn_call=true | ");
											 globals::instance()._call_list.push_back(next_offset);
										 }
										 else
											count += this->disasm_func(next_offset);
                                } else  {
                                        /* report unresolved address */
                                        x86_report_error( report_disasm_bounds,
                                                     (void *) next_addr );
                                }
                        }
                } /* end follow_insn */
				if ( size )
					globals::instance()._map[insn.offset]->done(true);

                if ( this->insn_doesnt_return(&insn) ) {
                        /* stop disassembling */
                        cont = 0;
						globals::instance()._map[insn.offset]->next=0;
						/*
						if(insn.type == insn_return)
							globals::instance()._map[insn.offset]->next_flow=0;
						*/

                }
        }
        return( count );
}

void disasm::init(unsigned char *lbuf, unsigned int lbuf_len,unsigned long lbuf_rva)
{
	buf=lbuf;
	buf_len=lbuf_len;
	buf_rva=lbuf_rva;
}
