/*
 *      Interactive disassembler (IDA).
 *      Copyright (c) 1990-98 by Ilfak Guilfanov.
 *      ALL RIGHTS RESERVED.
 *                              E-mail: ig@estar.msk.su, ig@datarescue.com
 *                              FIDO:   2:5020/209
 *
 */

#include "xa.hpp"
#include <frame.hpp>
#include <segment.hpp>
#include <auto.hpp>
#include <funcs.hpp>
#include <struct.hpp>


static bool flow;               // does the current instruction pass
                                // execution to the next instruction?

static void create_ext_ram_seg(ea_t &v)
{
  if ((v & 0xFF0000L) >= 0x80000L) // these are references to code
  {
    v = v & 0x7FFFFL;
    return;
  }

  if (v && getseg(v) == NULL)
  {
    ea_t start = v & 0xFFFF0000L;
    segment_t *sreg;

    add_segm(start>>4, start, start+0x10000L, NULL, "DATA");
    sreg = getseg(start);
    set_segm_name(sreg, "RAM%02x", int((start&0xFF0000L)>>16));
  }
}

static int check_insn(int prev, int itype, optype_t op1type,
ea_t op1value, optype_t op2type, ea_t op2value)
{
  if (prev && decode_prev_insn(cmd.ea) == BADADDR)
    return 0;

  switch (itype)
  {
    case XA_mov:
      if (cmd.itype != XA_mov && cmd.itype != XA_movs)
        return 0;
      break;
    case XA_add:
    case XA_sub:
      if (cmd.itype != itype && cmd.itype != XA_adds)
        return 0;
      break;
    default:
      if (cmd.itype != itype)
        return 0;
      break;
  }

  if (op1type != o_last)
  {
    if (cmd.Op1.type != op1type)
      return 0;

    if (op1value != BADADDR)
    {
      switch(op1type)
      {
        case o_imm:
          if (cmd.Op1.value != op1value)
            return 0;
          break;
        case o_reg:
        case o_phrase:
          if (cmd.Op1.reg != op1value)
            return 0;
          break;
        default:
          if (cmd.Op1.addr != op1value)
            return 0;
          break;
      }
    }
  }

  if (op2type != o_last)
  {
    if (cmd.Op2.type != op2type)
      return 0;

    if (op2value != BADADDR)
    {
      switch(op2type)
      {
        case o_imm:
          if (cmd.Op2.value != op2value)
            return 0;
          break;
        case o_reg:
        case o_phrase:
          if (cmd.Op2.reg != op2value)
            return 0;
          break;
        default:
          if (cmd.Op2.addr != op2value)
            return 0;
          break;
      }
    }
  }

  return 1;
}

//------------------------------------------------------------------------
// Handle an operand with an immediate value:
//      - mark it with FF_IMMD flag
//      - for bit logical instructions specify the operand type as a number
//        because such an operand is likely a plain number rather than
//        an offset or of another type.

static void doImmdValue(op_t &x)
{
  doImmd(cmd.ea);
  switch ( cmd.itype )
  {
    case XA_and:
    case XA_or:
    case XA_xor:
      op_num(cmd.ea,x.n);
      break;
  }
  uFlag = getFlags(cmd.ea);             // refresh uFlag with new flags
}

//----------------------------------------------------------------------
static void attach_bit_comment(int addr, int bit)
{
  predefined_t *predef = GetPredefined(ibits, addr, bit);
  if ( predef != NULL && get_cmt(cmd.ea,0) == NULL )
    set_cmt(cmd.ea,predef->cmt,0);
}

//----------------------------------------------------------------------
// Handle an operand. What this function usually does:
//      - creates cross-references from the operand
//        (the kernel deletes all xrefs before calling emu())
//      - creates permanent comments
//      - if possible, specifies the operand type (for example, it may
//        create stack variables)
//      - anything else you might need to emulate or trace

static void handle_operand(op_t &x,int loading)
{
  switch ( x.type )
  {
    case o_reg:              // no special hanlding for these types
      break;

    case o_imm:                         // an immediate number as an operand
      if ( !loading ) goto BAD_LOGIC;   // this can't happen!
      doImmdValue(x);                   // handle immediate number

      // if the value was converted to an offset, then create a data xref:
      if ( isOff(uFlag, x.n) ) ua_add_off_drefs(x, dr_O);

      break;

    case o_displ:
      if (x.phrase != fRi)
        doImmdValue(x);                   // handle immediate number

      // if the value was converted to an offset, then create a data xref:
      if ( isOff(uFlag, x.n) ) ua_add_off_drefs(x, loading?dr_R:dr_W);

      // Handle stack variables in a form [R7] and [R7+xx]
      // There is no frame pointer and all references are SP (R7) based
      if ( may_create_stkvars()
        && !isDefArg(uFlag,x.n)
        && x.indreg == rR7 &&
        !(x.n == 1 && check_insn(0, XA_lea, o_reg, rR7, o_last, BADADDR)))
      {
        func_t *pfn = get_func(cmd.ea);
        if ( pfn != NULL )
        {
          insn_t saved = cmd;
          int n = x.n;
          op_t fake = x;

          if (ua_code(cmd.ea+cmd.size))
          {
            if (fake.dtyp == dt_word) {
              if (saved.itype == cmd.itype &&
                  saved.Operands[n].type == cmd.Operands[n].type &&
                  saved.Operands[n].phrase == cmd.Operands[n].phrase &&
                  saved.Operands[n].indreg == cmd.Operands[n].indreg &&
                  saved.Operands[n].addr + 2 == cmd.Operands[n].addr &&
                  saved.Operands[1-n].type == cmd.Operands[1-n].type &&
                  saved.Operands[1-n].reg + 1 == cmd.Operands[1-n].reg)
              {
                fake.dtyp = dt_dword;
              }
            } else { // dt_byte
              if (saved.itype == XA_mov && cmd.itype == XA_mov && n == 1 &&
                  cmd.Op2.type == o_reg &&
                  saved.Op1.reg == cmd.Op2.reg &&
                  cmd.Op1.type == o_mem && cmd.Op1.addr == ES &&
                  ua_code(cmd.ea+cmd.size) &&
                  cmd.itype == XA_mov &&
                  cmd.Op1.type == o_reg && cmd.Op1.dtyp == dt_word &&
                  cmd.Op2.type == o_displ &&
                  cmd.Op2.addr + 2 == saved.Op2.addr)
              {
                fake.dtyp  = dt_dword;
                fake.addr -= 2;
              }
            }
          }

          cmd = saved; uFlag = getFlags(cmd.ea);

          if ( ua_stkvar(fake, fake.addr) )
            op_stkvar(cmd.ea, x.n);
          else
          {
            if (fake.dtyp == dt_dword)
            {
              fake.dtyp = dt_word;
              if (ua_stkvar(fake, fake.addr))
              {
                fake.dtyp = dt_dword;
                ua_stkvar(fake, fake.addr);
                op_stkvar(cmd.ea, x.n);
              }
            }
          }
        }
      }
      // fallthru

    case o_phrase:
      if (x.indreg != rR7 && (x.phrase == fRi || x.phrase == fRip)) // catch ES:offset references
      {
        int reg = x.indreg - rR0;
        insn_t saved = cmd;

	if (check_insn(1, XA_mov, o_reg, reg+rR0, o_imm, BADADDR))
        {
          ea_t v = EXTRAMBASE + cmd.Op2.value;
          int dtyp;

          create_ext_ram_seg(v);
          if (!isDefArg(uFlag, 1))
            op_offset(cmd.ea, 1, REF_OFF16, v, v & 0xFFFF0000);

          cmd = saved;
          dtyp = x.dtyp;
          if (dtyp == dt_word)
          {
            int n = x.n;
            if (ua_code(cmd.ea+cmd.size) &&
              cmd.Operands[n].type == o_displ &&
              cmd.Operands[n].indreg == reg+rR0)
            {
              dtyp = dt_dword;
            }
            cmd = saved;
          }
          ua_dodata(v, dtyp);
          if ( !loading ) doVar(v);     // write access
          ua_add_dref(x.offb, v, loading ? dr_R : dr_W);

          set_cmt(cmd.ea, get_name_expr(cmd.ea, x.n, v, v&0xFFFF), 0);
        } else if (check_insn(0, XA_setb, o_bit, 0x218+reg, o_last, BADADDR))
        {
          if (check_insn(1, XA_mov, o_mem, ES, o_imm, BADADDR))
          {
            ea_t v = EXTRAMBASE + (cmd.Op2.value << 16);
            if (check_insn(1, XA_mov, o_reg, reg+rR0, o_imm, BADADDR) ||
                check_insn(0, XA_lea, o_reg, reg+rR0, o_displ, BADADDR))
            {
              int dtyp;
              v += (cmd.Op2.type == o_imm)?cmd.Op2.value:cmd.Op2.addr;
              create_ext_ram_seg(v);
              if (!isDefArg(uFlag, 1))
                op_offset(cmd.ea, 1, REF_OFF16, v, v & 0xFFFF0000);
              cmd = saved;

              dtyp = x.dtyp;
              if (dtyp == dt_word)
              {
                int n = x.n;
                if (ua_code(cmd.ea+cmd.size) &&
                  cmd.Operands[n].type == o_displ &&
                  cmd.Operands[n].indreg == reg+rR0)
                {
                  dtyp = dt_dword;
                }
                cmd = saved;
              }

              ua_dodata(v, dtyp);
              if ( !loading ) doVar(v);     // write access
              ua_add_dref(x.offb, v, loading ? dr_R : dr_W);

              set_cmt(cmd.ea, get_name_expr(cmd.ea, x.n, v, v&0xFFFF), 0);
            }
          } else if (check_insn(0, XA_mov, o_mem, ES, o_reg, 2*reg+rR1L) ||
                     check_insn(0, XA_mov, o_mem, CS, o_reg, 2*reg+rR1L))
          { // MOV.B ES/CS,R1L
            int prev = 0;
            ea_t v = EXTRAMBASE;
            int ok = 0;
            if (check_insn(1, XA_jb, o_bit, BADADDR, o_last, BADADDR) &&
              (cmd.Op1.addr & 0xf) == 0xf && (cmd.Op1.addr & 0xFFF0) == ((reg+1)<<4))
            {
              prev = 1;
            }

            if (check_insn(prev, XA_add, o_reg, reg+rR0, o_reg, BADADDR))
              prev = 1;
            else
              prev = 0;

            if (check_insn(prev, XA_mov, o_reg, 2*reg+rR1H, o_imm, 0))
            {
              if (check_insn(1, XA_mov, o_reg, 2*reg+rR1L, o_mem, DS))
              {
                ok = 1;
              }
            } else if (check_insn(0, XA_mov, o_reg, reg+rR1, o_imm, BADADDR) ||
                       check_insn(0, XA_addc, o_reg, reg+rR1, o_imm, BADADDR))
            {
              v += (cmd.Op2.value << 16);
              ok = 1;
            }
            if (ok && (check_insn(1, XA_mov, o_reg, reg+rR0, o_imm, BADADDR) ||
                      check_insn(0, XA_add, o_reg, reg+rR0, o_imm, BADADDR)))
            {
              int dtyp;
              v += cmd.Op2.value;
              create_ext_ram_seg(v);
              if (!isDefArg(uFlag, 1))
                op_offset(cmd.ea, 1, REF_OFF16, v, v & 0xFFFF0000);
              cmd = saved;

              dtyp = x.dtyp;
              if (dtyp == dt_word)
              {
                int n = x.n;
                if (ua_code(cmd.ea+cmd.size) &&
                  cmd.Operands[n].type == o_displ &&
                  cmd.Operands[n].indreg == reg+rR0)
                {
                  dtyp = dt_dword;
                }
                cmd = saved;
              }

              ua_dodata(v, dtyp);
              if ( !loading ) doVar(v);
              ua_add_dref(x.offb, v, loading ? dr_R : dr_W);
              set_cmt(cmd.ea, get_name_expr(cmd.ea, x.n, v, v&0xFFFF), 0);
            }
          } else if (check_insn(0, XA_mov, o_reg, reg+rR0, o_imm, BADADDR))
          { // mov.w Rx,#xxxx
            ea_t v = cmd.Op2.value;
            if (check_insn(1, XA_mov, o_mem, ES, o_reg, BADADDR) && cmd.Op1.dtyp == dt_byte)
            {
              int reg2 = cmd.Op2.reg;
              if (check_insn(1, XA_mov, o_reg, reg2, o_imm, BADADDR))
              {
                int dtyp;
                v += EXTRAMBASE + (cmd.Op2.value << 16);
                create_ext_ram_seg(v);
                cmd = saved;

                dtyp = x.dtyp;
                if (dtyp == dt_word)
                {
                  int n = x.n;
                  if (ua_code(cmd.ea+cmd.size) &&
                    cmd.Operands[n].type == o_displ &&
                    cmd.Operands[n].indreg == reg+rR0)
                  {
                    dtyp = dt_dword;
                  }
                  cmd = saved;
                }

                ua_dodata(v, dtyp);
                if ( !loading ) doVar(v);	// write access
                ua_add_dref(x.offb, v, loading ? dr_R : dr_W);
                set_cmt(cmd.ea, get_name_expr(cmd.ea, x.n, v, v&0xFFFF), 0);
              }
            }
          }
        } else if ( // MOV.B ES,RxL
          check_insn(0, XA_mov, o_mem, ES, o_reg, BADADDR) && cmd.Op2.dtyp == dt_byte)
        {
          int reg2 = (cmd.Op2.reg - rR0L) >> 1;
          if (check_insn(1, XA_jb, o_bit, BADADDR, o_last, BADADDR) &&
            (cmd.Op1.addr & 0xf) == 0xf && (cmd.Op1.addr & 0xFFF0) == (reg2<<4) &&
            check_insn(1, XA_setb, o_bit, 0x218+reg, o_last, BADADDR))
          {
            int prev = 0;
            if (check_insn(1, XA_add, o_reg, reg+rR0, o_last, BADADDR))
              prev = 1;
            if (check_insn(prev, XA_mov, o_reg, reg2+rR0, o_imm, BADADDR) ||
                check_insn(0, XA_addc, o_reg, reg2+rR0, o_imm, BADADDR))
            {
              ea_t v = (cmd.Op2.value & 0x8000) ? 0 : EXTRAMBASE;
              v += (cmd.Op2.value & 0xff) << 16;
              if (check_insn(1, XA_mov, o_reg, reg+rR0, o_imm, BADADDR) ||
                  check_insn(0, XA_add, o_reg, reg+rR0, o_imm, BADADDR))
              {
                int dtyp;
                v += cmd.Op2.value;
                create_ext_ram_seg(v);
                op_offset(cmd.ea, 1, REF_OFF16, v, v & 0xFFFF0000);
                cmd = saved;

                dtyp = x.dtyp;
                if (dtyp == dt_word)
                {
                  int n = x.n;
                  if (ua_code(cmd.ea+cmd.size) &&
                    cmd.Operands[n].type == o_displ &&
                    cmd.Operands[n].indreg == reg+rR0)
                  {
                    dtyp = dt_dword;
                  }
                  cmd = saved;
                }

                ua_dodata(v, dtyp);
                if ( !loading ) doVar(v);	// write access
                ua_add_dref(x.offb, v, loading ? dr_R : dr_W);
                set_cmt(cmd.ea, get_name_expr(cmd.ea, x.n, v, v&0xFFFF), 0);
              }
            }
          }
        }
        cmd = saved;
      }
      uFlag = getFlags(cmd.ea);
      break;

    case o_bit:                         // 8051 specific operand types - bits
    case o_bitnot:
      {
        int addr = x.addr >> 3;
        int bit = x.addr & 7;
        ea_t dea;

        if (addr & 0x40) // SFR
        {
          addr += 0x3c0;
        } else if ((x.addr & 0x20) == 0) // Register file
          break;

        attach_bit_comment(addr, bit);  // attach a comment if necessary
        dea = map_addr(addr);
        ua_dodata(dea, dt_byte);
        if ( !loading ) doVar(dea);     // write access
        ua_add_dref(x.offb, dea, loading ? dr_R : dr_W);
      }
      break;

    case o_mem:                         // an ordinary memory data reference
      {
        ea_t dea = map_addr(x.addr);
        ua_dodata(dea, x.dtyp);
        if ( !loading ) doVar(dea);     // write access
        ua_add_dref(x.offb, dea, loading ? dr_R : dr_W);
      }
      break;

    case o_near:                        // a code reference
      {
        ea_t ea = toEA(cmd.cs, x.addr);
        int iscall = InstrIsSet(cmd.itype, CF_CALL);
        ua_add_cref(x.offb, ea, iscall ? fl_CN : fl_JN);

	if (auto_state == AU_USED &&
          isEnabled(ea-1) && isUnknown(getFlags(ea-1)) &&
          get_byte(ea-1) == 0 && cmd.ea != ea-2)
        {
          auto_make_code(ea-1);
        }

        if ( flow && iscall )
        {
          func_t *pfn = get_func(ea);
          if ( pfn != NULL && (pfn->flags & FUNC_NORET) ) flow = false;
        }
      }
      break;

    case o_far:                        // a code reference
      {
        ea_t ea = x.addr + (x.specval << 16);
        int iscall = InstrIsSet(cmd.itype, CF_CALL);
        ua_add_cref(x.offb, ea, iscall ? fl_CF : fl_JF);
        if ( flow && iscall )
        {
          func_t *pfn = get_func(ea);
          if ( pfn != NULL && (pfn->flags & FUNC_NORET) ) flow = false;
        }
      }
      break;

    default:
BAD_LOGIC:
      warning("%a (%s): bad optype", cmd.ea, Instructions[cmd.itype].name);
      break;
  }
}

//----------------------------------------------------------------------
// Emulate an instruction
// This function should:
//      - create all xrefs from the instruction
//      - perform any additional analysis of the instruction/program
//        and convert the instruction operands, create comments, etc.
//      - create stack variables
//      - analyze the delayed branches and similar constructs
// The kernel calls ana() before calling emu(), so you may be sure that
// the 'cmd' structure contains a valid and up-to-date information.
// You are not allowed to modify the 'cmd' structure.
// Upon entering this function, the 'uFlag' variable contains the flags of
// cmd.ea. If you change the characteristics of the current instruction, you
// are required to refresh 'uFlag'.
// Usually the kernel calls emu() with consecutive addresses in cmd.ea but
// you can't rely on this - for example, if the user asks to analyze an
// instruction at arbirary address, his request will be handled immediately,
// thus breaking the normal sequence of emulation.
// If you need to analyze the surroundings of the current instruction, you
// are allowed to save the contents of the 'cmd' structure and call ana().
// For example, this is a very common pattern:
//  {
//    insn_t saved = cmd;
//    if ( decode_prev_insn(cmd.ea) != BADADDR )
//    {
//      ....
//    }
//    cmd = saved;
//  }
//
// This sample emu() function is a very simple emulation engine.

int emu(void)
{
  int Feature = Instructions[cmd.itype].feature;
  flow = ((Feature & CF_STOP) == 0);

  int report = 0;

  // you may emulate selected instructions with a greater care:
  switch ( cmd.itype )
  {
    case XA_mov:
    case XA_movs:
// mov R7,#xxx
      if ( cmd.Op1.type == o_reg && cmd.Op1.reg == rR7)
      {
        if ( cmd.Op2.type == o_imm && !isDefArg(uFlag,1) )
          op_offset(cmd.ea, 1, REF_OFF16, INTMEMBASE + cmd.Op2.value, INTMEMBASE);
      }

// mov DS,#xx
      if (check_insn(0, XA_mov, o_mem, DS, o_imm, BADADDR))
      {
        ea_t v = EXTRAMBASE + (cmd.Op2.value << 16);
        create_ext_ram_seg(v);
      }

// mov ES,#xx
      if (check_insn(0, XA_mov, o_mem, ES, o_last, BADADDR)) // MOV ES,xx
      {
        insn_t saved = cmd;
        ea_t v = 0;

        if (cmd.Op2.type == o_imm)
        {
          v = EXTRAMBASE + (cmd.Op2.value << 16);
        } else if (cmd.Op2.type == o_reg && cmd.Op1.dtyp == dt_byte)
        {
          int reg = cmd.Op2.reg;
          if (check_insn(1, XA_mov, o_reg, reg, o_imm, BADADDR))
          {
            v = EXTRAMBASE + (cmd.Op2.value << 16);
          }
        }

        create_ext_ram_seg(v);

        if (cmd.Op2.type == o_imm && check_insn(1, XA_mov, o_reg, BADADDR, o_imm, BADADDR) && cmd.Op1.dtyp == dt_word)
        {
          v += cmd.Op2.value;
          if (!isDefArg(uFlag, 1))
            op_offset(cmd.ea, 1, REF_OFF16, v, v & 0xFFFF0000);
        }
        cmd = saved;
        uFlag = getFlags(cmd.ea);
      }

// mov CS,#xx
      if (check_insn(0, XA_mov, o_mem, CS, o_imm, BADADDR)) // MOV CS,#xx
      {
        insn_t saved = cmd;
        ea_t v = (cmd.Op2.value << 16);
        create_ext_ram_seg(v);

        if (check_insn(1, XA_mov, o_reg, BADADDR, o_imm, BADADDR) && cmd.Op1.dtyp == dt_word)
        {
          v += cmd.Op2.value;
          if (!isDefArg(uFlag, 1))
            op_offset(cmd.ea, 1, REF_OFF16, v, v & 0xFFFF0000);
        }
        cmd = saved;
        uFlag = getFlags(cmd.ea);
      }

// mov Rx,#xxxx
      if (check_insn(0, XA_mov, o_reg, BADADDR, o_imm, BADADDR) && cmd.Op1.dtyp == dt_word)
      {
        insn_t saved =  cmd;

        if (check_insn(1, XA_mov, o_mem, ES, o_reg, BADADDR) && cmd.Op1.dtyp == dt_byte)
        {
          int regL = cmd.Op2.reg - rR0L;
          if (check_insn(1, XA_mov, o_reg, regL, o_imm, BADADDR))
          {
            ea_t v = EXTRAMBASE + (cmd.Op2.value << 16) + saved.Op2.value;
            create_ext_ram_seg(v);
            cmd = saved; uFlag = getFlags(cmd.ea);
            if (!isDefArg(uFlag,1))
              op_offset(saved.ea, 1, REF_OFF16, v, v & 0xFFFF0000L);
          }
        }
        cmd = saved;
        uFlag = getFlags(cmd.ea);
      }

// mov.b R1H, #0
      if (check_insn(0, XA_mov, o_reg, BADADDR, o_imm, 0))
      {
        int reg = (cmd.Op1.reg - rR1H) >> 1;
        insn_t saved = cmd;
        if (check_insn(1, XA_mov, o_reg, 2*reg+rR1L, o_mem, DS)) // mov rx,DS
        {
          if (check_insn(1, XA_mov, o_reg, reg+rR0, o_imm, BADADDR))
          {
            ea_t v = EXTRAMBASE + cmd.Op2.value;
            uFlag = getFlags(cmd.ea);
            if (!isDefArg(uFlag,1))
              op_offset(cmd.ea, 1, REF_OFF16, v, EXTRAMBASE);
          }
        }
        cmd = saved;
        uFlag = getFlags(cmd.ea);
      }

      break;

    case XA_push:
    case XA_pop:
      if (cmd.Op1.type == o_phrase &&
        (cmd.Op1.phrase == fRlistL || cmd.Op1.phrase == fRlistH))
      {
        func_t *pfn = get_func(cmd.ea);
        int bits = 0, firstreg;

        for (int bit = 7; bit >= 0; bit--)
        {
          if (cmd.Op1.indreg & (1<<bit))
          {
            bits++;
            firstreg = bit;
          }
        }
        if (bits && should_trace_sp() && pfn && !get_sp_delta(pfn, cmd.ea))
          add_auto_stkpnt(cmd.ea, (cmd.itype==XA_push)?-2*bits:2*bits);

        if (cmd.itype == XA_push &&
            bits == 2 && (cmd.Op1.indreg & (1<<(firstreg+1)))) // dword push
        {
          insn_t save = cmd;
          if (check_insn(1, XA_mov, o_reg, firstreg+rR1, o_imm, BADADDR))
          {
            ea_t v = EXTRAMBASE + (cmd.Op2.value << 16);
            if (check_insn(1, XA_mov, o_reg, firstreg+rR0, o_imm, BADADDR))
            {
              v += cmd.Op2.value;
              create_ext_ram_seg(v);
              if (!isDefArg(uFlag, 1))
                op_offset(cmd.ea, 1, REF_OFF16, v, v & 0xFFFF0000);
            }
          }
          cmd = save; uFlag = getFlags(cmd.ea);
        }
      } else if (cmd.Op1.type == o_mem) {
        func_t *pfn = get_func(cmd.ea);
        if (should_trace_sp() && pfn && !get_sp_delta(pfn, cmd.ea))
          add_auto_stkpnt(cmd.ea, (cmd.itype==XA_push)?-2:2);
      } else {
        warning("emu: strange push/pop instruction operand at %a", cmd.ea);
      }
      break;

   case XA_add:
   case XA_sub:
   case XA_adds:
     if ( !may_trace_sp() )
       break;
     if (cmd.Op1.type == o_reg && cmd.Op1.reg == rR7)
     {
       if (cmd.Op2.type == o_imm)
       {
         func_t *pfn = get_func(cmd.ea);

         sval_t offset = (cmd.Op2.value < 0x8000L || cmd.Op2.value > 0x80000000L) ? cmd.Op2.value : cmd.Op2.value-0x10000L;

         if (should_trace_sp() && pfn && !get_sp_delta(pfn, cmd.ea))
           add_auto_stkpnt(cmd.ea, (cmd.itype==XA_sub)?-offset:offset);
       } else {
         warning("emu: add/adds/sub with R7 and non-imm operand at %a", cmd.ea);
       }
     }
     break;

   case XA_lea:
     if ( !may_trace_sp() )
       break;
     if (cmd.Op1.type == o_reg && cmd.Op1.reg == rR7)
     {
       if (cmd.Op2.type == o_displ &&
         cmd.Op2.indreg == rR7)
       {
         func_t *pfn = get_func(cmd.ea);
         if (pfn && !get_sp_delta(pfn, cmd.ea))
           add_auto_stkpnt(cmd.ea, cmd.Op2.addr);
       } else {
         warning("emu: lea with R7 and unknown 2nd operand at %a", cmd.ea);
       }
    }
    break;

  }

  if ( Feature & CF_USE1 ) handle_operand(cmd.Op1, 1);
  if ( Feature & CF_USE2 ) handle_operand(cmd.Op2, 1);
  if ( Feature & CF_USE3 ) handle_operand(cmd.Op3, 1);
  if ( Feature & CF_JUMP ) QueueMark(Q_jumps,cmd.ea);

  if ( Feature & CF_CHG1 ) handle_operand(cmd.Op1, 0);
  if ( Feature & CF_CHG2 ) handle_operand(cmd.Op2, 0);
  if ( Feature & CF_CHG3 ) handle_operand(cmd.Op3, 0);

  // if the execution flow is not stopped here, then create
  // a xref to the next instruction.
  // Thus we plan to analyze the next instruction.

  if ( flow ) ua_add_cref(0,cmd.ea+cmd.size,fl_F);

  if (report) warning("emu: leaving");
  return 1;    // actually the return value is unimportant, but let's it be so
}

// Special functions for Hisoft XA C compiler

bool xa_create_func(func_t *pfn)
{
   ea_t prologue = pfn->startEA;
   int more;
   int prologue_at_end = 0;

   if (pfn->frame && pfn->frsize)
   {
     add_frame(pfn, pfn->frsize, pfn->frregs, 0);
     return 1;
   }
   if (ua_code(prologue))
   {
     if (cmd.itype == XA_jmp || cmd.itype == XA_br)
     {
       prologue = toEA(cmd.cs, cmd.Op1.addr);
       prologue_at_end = 1;
     }
     do
     {
       more = 0;
       if (ua_code(prologue) == 0)
         break;
       if (cmd.itype == XA_push &&
         cmd.Op1.type == o_phrase &&
         (cmd.Op1.phrase == fRlistL || cmd.Op1.phrase == fRlistH))
       {
         for (int bit = 0; bit < 8; bit++)
         {
           if (cmd.Op1.indreg & (1<<bit))
             pfn->frregs += 2;
         }
         more = 1;
       }
       if (cmd.itype == XA_lea &&
         cmd.Op1.type == o_reg && cmd.Op1.reg == rR7 &&
         cmd.Op2.type == o_displ &&
         (cmd.Op2.phrase == fRid8 || cmd.Op2.phrase == fRid16) &&
         cmd.Op2.indreg == rR7)
       {
         sval_t offset = cmd.Op2.addr;
         if (offset >= 0)
         {
           warning("%a: positive offset %a", cmd.ea, offset);
           offset -= 0x10000L;
         }
         pfn->frsize -= offset;
         more = 1;
       }
       if (cmd.itype == XA_sub &&
         cmd.Op1.type == o_reg && cmd.Op1.reg == rR7 &&
         cmd.Op2.type == o_imm)
       {
         pfn->frsize += (int)cmd.Op2.addr;
         more = 1;
       }
       if (cmd.itype == XA_adds &&
         cmd.Op1.type == o_reg && cmd.Op1.reg == rR7 &&
         cmd.Op2.type == o_imm)
       {
         pfn->frsize -= (int)cmd.Op2.value;
         more = 1;
       }
       prologue += cmd.size;
     } while (more);
   }
   add_frame(pfn, pfn->frsize, pfn->frregs, 0);
   if (prologue_at_end)
     add_auto_stkpnt(pfn->startEA, -pfn->frsize-pfn->frregs);

   return 1;
}

bool xa_is_switch(switch_info_t *si)
{
  int value = 0;
  int prev;

  if (cmd.Op1.type == o_phrase && cmd.Op1.phrase == fRi)
  {
    insn_t saved = cmd;
    int jumpreg, datareg;

    jumpreg = cmd.Op1.indreg;
    if (check_insn(1, XA_movc, o_reg, jumpreg, o_phrase, fRip) &&
      cmd.Op2.indreg == jumpreg)
    {
      if (check_insn(1, XA_add, o_reg, jumpreg, o_imm, BADADDR))
      {
        si->jumps = (cmd.ea & 0xFFFF0000) + cmd.Op2.value;
        op_offset(cmd.ea, 1, REF_OFF16, si->jumps, cmd.ea & 0xFFFF0000);
        if (check_insn(1, XA_asl, o_reg, jumpreg, o_imm, 1))
        {
          if (check_insn(1, XA_mov, o_reg, rR0H + 2*(jumpreg-rR0), o_imm, 0))
          {
            datareg = cmd.Op1.reg - 1;
            prev = 0;
            if (check_insn(1, XA_nop, o_last, BADADDR, o_last, BADADDR))
              prev = 1;
            if (check_insn(prev, XA_bg, o_last, BADADDR, o_last, BADADDR) ||
                (check_insn(0, XA_jmp, o_last, BADADDR, o_last, BADADDR) &&
                 check_insn(1, XA_bl, o_last, BADADDR, o_last, BADADDR)))
            {
              if (check_insn(1, XA_cmp, o_reg, datareg, o_imm, BADADDR))
              {
                si->ncases = cmd.Op2.value+1;
                if (check_insn(1, XA_bcs, o_last, BADADDR, o_last, BADADDR))
                {
                  si->defjump = cmd.Op1.addr;
                  if (check_insn(1, XA_sub, o_reg, datareg, o_imm, BADADDR) ||
                      check_insn(0, XA_adds, o_reg, datareg, o_imm, BADADDR))
                  {
                    if (cmd.itype == XA_sub)
                    {
                      si->lowcase = cmd.Op2.value;
                    } else {
                      si->lowcase = -cmd.Op2.value;
                    }
                    si->flags = SWI_DEFAULT;
                    value = 1;
                    si->startea = cmd.ea;
                  } else {
                    warning("%a: no sub/add, may start with 0", cmd.ea);
                  }
                } else {
                  si->lowcase = 0;
                  si->flags = SWI_DEFAULT;
                  value = 1;
                  si->startea = cmd.ea + cmd.size;
                }
              } else {
                warning("%a: no cmp", cmd.ea);
              }
            } else {
              warning("%a: no bg, may be signed", cmd.ea);
            }
          } else {
            prev = 0;
            if (check_insn(0, XA_nop, o_last, BADADDR, o_last, BADADDR))
              prev = 1;
            if (check_insn(prev, XA_bg, o_last, BADADDR, o_last, BADADDR) ||
                (check_insn(0, XA_jmp, o_last, BADADDR, o_last, BADADDR) &&
                 check_insn(1, XA_bl, o_last, BADADDR, o_last, BADADDR)))
            {
              if (check_insn(1, XA_cmp, o_reg, BADADDR, o_imm, BADADDR))
              {
                datareg = cmd.Op1.reg;
                si->ncases = cmd.Op2.value+1;
                if (check_insn(1, XA_bcs, o_last, BADADDR, o_last, BADADDR))
                {
                  si->defjump = cmd.Op1.addr;
                  if (check_insn(1, XA_sub, o_reg, datareg, o_imm, BADADDR) ||
                      check_insn(0, XA_adds, o_reg, datareg, o_imm, BADADDR))
                  {
                    if (cmd.itype == XA_sub)
                    {
                      si->lowcase = cmd.Op2.value;
                    } else {
                      si->lowcase = -cmd.Op2.value;
                    }
                    si->flags = SWI_DEFAULT;
                    value = 1;
                    si->startea = cmd.ea;
                  } else {
                    warning("no sub/add, may start with 0");
                  }
                } else {
                  si->lowcase = 0;
                  si->flags = SWI_DEFAULT;
                  value = 1;
                  si->startea = cmd.ea + cmd.size;
                }
              } else {
                warning("%a: no cmp", cmd.ea);
              }
            } else {
              warning("%a: no bg, may be signed", cmd.ea);
            }
          }
        }
      }
    }

    if (value)
    {
      if (get_byte(si->jumps + 2*si->ncases) == 0xfe)
        ua_add_cref(saved.ea, si->jumps + 2*si->ncases, fl_F);
    }
    cmd = saved;
  }
  return value;
}

int xa_frame_retsize(func_t *pfn)
{
  return pfn->is_far() ? 2 : 4;
}

void xa_stkvar_def(char *buf, size_t bufsize, const member_t *mptr, sval_t v)
{
  char sign = '+';
  if ( v < 0 )
  {
    v = -v; sign = '-';
  }
  qsnprintf(buf, bufsize,
            COLSTR("%-*s ", SCOLOR_LOCNAME)
            COLSTR("set     %c", SCOLOR_SYMBOL)
            COLSTR("%s",SCOLOR_DNUM),
            inf.indent-1, get_member_name(mptr->id), sign, btoa(v));
}

int xa_align_insn(ea_t ea)
{
  if (get_byte(ea) == 0) return 1;
  return 0;
}
