#include <stdio.h>
#include "insertor.h"
#include "utils.h"

u_char buffer[20];		/* buffer for the current instruction */
int len;			/* number of byte in this instruction */
u_int offset;
u_int func_align = 4;		/* 4 for i386, 16 for i486 */
int dis_res;

#define READ_BYTE (buffer[len++] = text_seg[offset++]) 

struct addr 
{
  u_int type;
#define TUnused 0x00
#define TJRel8 0x01
#define TJRel32 0x02
#define TJump32 0x03
#define TDisp32 0x04 
  union
  {
    struct
      {
        int type;
#define TDISP8 0x01
#define TDISP32 0x02
#define TSIB 0x04
        unsigned char modrm;
        unsigned char sib;
        unsigned int disp;
      } modrm;
    unsigned char data8;
    unsigned short data16;
    unsigned int data32;
  } info;
  int n_reloc;
  struct relocation_info *reloc;
};

/* read an immediat in the instruction and set the 'type' field 
 * If you want to read a disp, set 'type' after calling this function.
 */
void
read_data(struct addr *ad, int size)
{
  u_int old_offset = offset;
  switch(size)
    {
      case 1:
        ad->info.data8 = READ_BYTE;
        ad->type = Imm8;
        break;
      case 2:
        ad->info.data16 = (READ_BYTE) + (READ_BYTE << 8);
        ad->type = Imm16;
        break;
      case 4:
        ad->info.data32 = (READ_BYTE) + (READ_BYTE << 8)
                        + (READ_BYTE << 16) + (READ_BYTE << 24);
        ad->type = Imm32;
        break;
      default:
        FATAL(1, "Unknown size in read_data\n");
    }
    ad->reloc = search_reloc(old_offset);
    if (ad->reloc && (1 << ad->reloc->r_length) != size)
      FATAL(0, "Bad relocation entry...\n");
}

void
disassemble(struct dis_info *info)
{
 int nbr_prefixes = 0;
 unsigned int instr_byte;
 template const *temp;
 int extended = 0;
 struct discrib const *table;
 char group_byte;
 int addr16 = 0;
 int size = 0;
 int sens = 0;
 unsigned int opcode_modifier;
 int off_operand = 0;
 struct addr Addr[2];
 
 /* initialization */
 offset = info->offset;
 Addr[0].type = TUnused;
 Addr[1].type = TUnused;
 len = 0;
 info->length = 0;
 info->type = T_ERROR;
 
 printf("%4x: ", info->offset);
 /* read a byte and loop for prefixes */
 while (instr_byte = READ_BYTE, instr[instr_byte].type == Prefix)
 {
   nbr_prefixes++;
   printf("%s/ ",instr[instr_byte].info.onebyte->name);
   switch (instr_byte)
     {
       case 0x67:	/* addr16 */
         FATAL(0, "found an addr16 prefix\n");
         return ;
         break;
       case 0x66:
         addr16 = 1;
         break;
       case 0x2e:
       case 0x3e:
       case 0x26:
       case 0x64:
       case 0x65:
       case 0x36:
         FATAL(0, "found a segment overrides prefix\n");
         return ;
         break;
     }
 }

 if (instr_byte == 0x0f)	/* extended byte */
   {
     extended = 1;
     instr_byte = READ_BYTE;
     table = extend_instr;
   }
 else
   table = instr;

 switch(table[instr_byte].type)
  {
    case TUnknown:
      fatal(0,"Unknown instruction: 0x%x\n", instr_byte);
      return ;
      break;
    case Prefix:
      FATAL(1,"Found a prefix as an instruction\n");
      break;
    case OneByte:
      temp = table[instr_byte].info.onebyte;
      break;
    case TwoByte:
      READ_BYTE;
      temp = table[instr_byte].info.onebyte;
      break;
    case Extend:
      FATAL(1, "Found an extended opcode in extended table\n");
      break;
    case FPint:
      group_byte = READ_BYTE;
      if ((group_byte & 0xc0) == 0xc0)
        temp = instr[instr_byte].info.fgroup[(group_byte & 0x30) + 8];
      else
        temp = instr[instr_byte].info.fgroup[(group_byte & 0x38) >> 3];
/*      FATAL(1, "Found an FP instruction\n"); */
      break;
    case TGroup:
      group_byte = READ_BYTE;
      temp = table[instr_byte].info.group[(group_byte >> 3) & 7];
      break;
  }
  printf("%s ", temp->name);
  
  opcode_modifier = temp->opcode_modifier;
  if (opcode_modifier & W)
    {
      if ((instr_byte & 1) == 0)
        size = 1;	/* byte */
      else
        if (addr16 == 1)
          size = 2;	/* word */
        else
          size = 4;	/* long word */
      opcode_modifier &= ~0x01;
    }
  if (opcode_modifier & D)
    {
      if ((instr_byte & 2) == 0)
        sens = 1;
      else
        sens = 2;
      opcode_modifier &= ~0x02;
    }
  if (opcode_modifier & FloatD)
    {
      if((instr_byte & 0x04) == 0x04)
        sens = 2;
      else
        sens = 1;
    }
  switch (opcode_modifier)
    {
      case NoModrm:
        for (; off_operand < temp->operands; off_operand++)
          switch (temp->operand_types[off_operand])
            {
              case InOutPortReg:
                break;
              case Disp32:
                read_data(&Addr[off_operand], 4);
                Addr[off_operand].type = Disp32;                
                break;
              case Imm32:
                read_data(&Addr[off_operand], 4);
                break;
              case Imm16:
                read_data(&Addr[off_operand], 2);
                break;
              case Imm8S:
              case Imm8:
                read_data(&Addr[off_operand], 1);
                break;
              case Imm:
                if (size == 0)
                  FATAL(1,"Unknown size for an immediat\n");
                read_data(&Addr[off_operand], size);
                break;
            }
        break;
      case ReverseRegRegmem | Modrm:
      case Modrm:
        Addr[0].type = Modrm;
        Addr[0].info.modrm.type = 0;
        if (table[instr_byte].type == TGroup || table[instr_byte].type == FPint)
          Addr[0].info.modrm.modrm = group_byte;
        else
          Addr[0].info.modrm.modrm = READ_BYTE;
        if ((Addr[0].info.modrm.modrm & 0xc0) != 0xc0)
          {
            if ((Addr[0].info.modrm.modrm & 0x07) == 0x04)
              {
                Addr[0].info.modrm.sib = READ_BYTE;
                Addr[0].info.modrm.type |= TSIB;
                if ((Addr[0].info.modrm.modrm & 0xc0) == 0x00
                     && (Addr[0].info.modrm.sib & 0x07) == 0x05)
                  {
                    Addr[0].info.modrm.type |= TDISP32;
                    Addr[0].reloc = search_reloc(offset);
                    if (Addr[0].reloc && Addr[0].reloc->r_length != 2)
                      FATAL(0, "bad relocation entry\n");
                    Addr[0].info.modrm.disp = READ_BYTE
                      + (READ_BYTE << 8) + (READ_BYTE << 16) + (READ_BYTE << 24);
                  }
              }
            if ((Addr[0].info.modrm.modrm & 0xc0) == 0x40)
              {
                Addr[0].reloc = search_reloc(offset);
                if (Addr[0].reloc && Addr[0].reloc->r_length != 0)
                  FATAL(1, "bad relocation entry\n");
                Addr[0].info.modrm.disp = READ_BYTE;                  
                Addr[0].info.modrm.type |= TDISP8;
              }
            if ((Addr[0].info.modrm.modrm & 0xc0) == 0x80
                || (Addr[0].info.modrm.modrm & 0xc7) == 0x05)
              {
                Addr[0].reloc = search_reloc(offset);
                if (Addr[0].reloc && Addr[0].reloc->r_length != 2)
                  FATAL(0, "bad relocation entry\n");              
                Addr[0].info.modrm.disp = READ_BYTE
                   + (READ_BYTE << 8) + (READ_BYTE << 16) + (READ_BYTE << 24);
                Addr[0].info.modrm.type |= TDISP32;
              }
          }
        if (temp->operands == 0)
          FATAL(1, "Bad number of operands for an modrm\n");
        if (temp->operands == 1)
          switch (temp->operand_types[0])
            {
              case WordReg | WordMem:
              case WordReg | Mem:
              case WordReg:
              case WordMem:
                if (addr16)
                  size = 2;
                else
                  size = 4;
                break;
              case Reg | Mem:
                if (size == 0)
                  FATAL(1,"Size unknown...\n");
                break;
              case Reg | Mem | JumpAbsolute:
              case Reg32 | Mem | JumpAbsolute:
              case Reg32:
                size = 4;
                break;
              case Reg8 | Mem:
                size = 1;
                break;
              case Reg16 | Mem16:
              case Reg16 | Mem:
                size = 2;
                break;
              case Mem48:
                size = 6;
                break;
              case Mem:
                if (table[instr_byte].type == FPint)
                  {
                    size = 10;	/* not always good */
                    break;
                  }
              default:
                FATAL(1, "Unknwon informations for modrm\n");
            }
        else if (temp->operands == 2)
          {
            switch (temp->operand_types[0])
              {
                case WordReg|WordMem:
                case WordReg|Mem:
                  if (addr16)
                    size = 2;
                  else
                    size = 4;
                  break;
                case Reg|Mem:
                  if (size == 0)
                    FATAL(1, "Bad size\n");
                  break;
                default:
                  FATAL(1, "Operand Unknown\n");
              }
            switch (temp->operand_types[1])
              {
                case Imm8S:
                case Imm8:
                  read_data(&Addr[1], 1);
                  break;
                case Imm:
                  read_data(&Addr[1], size);
                  break;
                case Imm16|Imm32:
                  if (size != 2 || size != 4)
                    FATAL(1, "Bad size");
                  read_data(&Addr[1], size);
                  break;
                default:
                  FATAL(1,"Bad operand type\n");
              }
          }
        break;
      case ShortFormW:
        if ((instr_byte & 0x08) == 0)
          size = 1;	/* byte */
        else
          if (addr16)
            size = 2;
          else
            size = 4;
        if (temp->operands != 1 || temp->operand_types[0] != Imm)
          FATAL(1,"bad ShortFormW instruction\n");
        read_data(&Addr[0], size);
        break;
      case Seg2ShortForm:
        if (temp->operands != 1 || temp->operand_types[0] != SReg2)
          FATAL(1, "bad Seg2ShortForm instruction\n");
        break;
      case Seg3ShortForm:
        if (temp->operands != 1 || temp->operand_types[0] != SReg3)
          FATAL(1, "bad Seg2ShortForm instruction\n");
        break;
      case ShortForm:
        if (temp->operands != 1)
          FATAL(1, "Bad number of arguments\n");
        switch(temp->operand_types[0])
          {
            case WordReg:
              if (addr16)
                size = 2;
              else
                size = 4;
              break;
            case FloatReg:
              size = 10;
              break;
            default:
              FATAL(1,"Bad operand type\n");
          }
        break;
      case Jump:
      	/* specialy for jmp */
        if (len == 1 && instr_byte == 0xe9)
          {
            read_data(&Addr[0], 4);
            Addr[0].type = TJRel32;
            break;
          }
        if (extended)
          {
            read_data(&Addr[0], 4);
            Addr[0].type = TJRel32;
            if (abs((long)Addr[0].info.data32) < 127)
              FATAL(0, "Non optimized jump...\n");
          }
        else
          {
            read_data(&Addr[0], 1);
            Addr[0].type = TJRel8;
            info->address = offset + Addr[0].data8;
          }
        break;
      case JumpDword:
        if(temp->operands != 1 || temp->operand_types[0] != Disp32)
          FATAL(1, "Bad operands\n");
        read_data(&Addr[0], 4);
        Addr[0].type = TJump32;
        break;
      default:
        FATAL(1, "Unknown mode\n");
    }
  printf("[%d bytes]\n", len);
  info->length = len;
  info->type = temp->op_struct;
  return;
}
