/*  $Id$
 *
 *  DSH4 version 0.1.2 (Jan 06, 2001)
 *
 *  This is a disassembler for Hitachi SH-4 binaries.
 *
 *
 *  Jan 06, 2001: Fixed bug in (disp,pc) decoding for MOVA
 *  Feb 04, 2000: Fixed bug in (disp,pc) decoding
 *
 */

#include <assert.h>
#include <errno.h>
#include <malloc.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

/*
 *  TRUE and FALSE already seem to be defined on some systems.
 */
#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE 1
#endif

enum { FORMAT_LISTING = 'l', FORMAT_SOURCE = 's', FORMAT_COMBINED = 'c' };

enum { ENDIAN_LITTLE = 'l', ENDIAN_BIG = 'b' };

enum { OPER_NONE, OPER_IMM, OPER_IIMM, OPER_RN, OPER_SPECREG, OPER_IND_RN,
       OPER_IND_RN_INC, OPER_IND_DEC_RN, OPER_IND_R0_RN, OPER_IND_R0_GBR,
       OPER_IND_DISP_RN, OPER_IND_DISP_PC, OPER_IND_DISP_GBR, OPER_RN_BANK,
       OPER_FRN, OPER_DRN, OPER_XDN, OPER_FVN, OPER_DISP_RELPC };

enum { SPECREG_PC, SPECREG_GBR, SPECREG_SR, SPECREG_VBR, SPECREG_SSR,
       SPECREG_SPC, SPECREG_DBR, SPECREG_MACH, SPECREG_MACL, SPECREG_PR,
       SPECREG_SGR, SPECREG_FPUL, SPECREG_FPSCR, SPECREG_XMTRX };

enum { SIZE_NONE, SIZE_B, SIZE_W, SIZE_L, SIZE_S, SIZE_D };

enum { IMM_NONE, IMM_ANY = 1, IMM_ABS = 2, IMM_REL = 4, IMM_RELPC = 0xc };

enum { SCALE_NONE, SCALE_1 = 0, SCALE_2, SCALE_4, SCALE_8 };

enum { MNEM_DC, MNEM_MOV, MNEM_MOVA, MNEM_MOVT, MNEM_SWAP, MNEM_XTRCT,
       MNEM_ADD, MNEM_ADDC, MNEM_ADDV, 
       MNEM_CMPEQ, MNEM_CMPHS, MNEM_CMPGE, MNEM_CMPHI, MNEM_CMPGT,
       MNEM_CMPPZ, MNEM_CMPPL, MNEM_CMPSTR, MNEM_DIV1, MNEM_DIV0S,
       MNEM_DIV0U, MNEM_DMULU, MNEM_DMULS, MNEM_DT, MNEM_EXTU,
       MNEM_EXTS, MNEM_MAC, MNEM_MUL, MNEM_MULS, MNEM_MULU, MNEM_NEG,
       MNEM_NEGC, MNEM_SUB, MNEM_SUBC, MNEM_SUBV,
       MNEM_AND, MNEM_NOT, MNEM_OR, MNEM_TAS, MNEM_TST, MNEM_XOR,
       MNEM_ROTL, MNEM_ROTR, MNEM_ROTCL, MNEM_ROTCR, MNEM_SHAD,
       MNEM_SHAL, MNEM_SHAR, MNEM_SHLD, MNEM_SHLL, MNEM_SHLR,
       MNEM_SHLL2, MNEM_SHLR2, MNEM_SHLL8, MNEM_SHLR8,
       MNEM_SHLL16, MNEM_SHLR16,
       MNEM_BF, MNEM_BFS, MNEM_BT, MNEM_BTS, MNEM_BRA, MNEM_BRAF,
       MNEM_BSR, MNEM_BSRF, MNEM_JMP, MNEM_JSR, MNEM_RTS,
       MNEM_CLRMAC, MNEM_CLRS, MNEM_CLRT, MNEM_SETS, MNEM_SETT,
       MNEM_LDC, MNEM_LDS, MNEM_STC, MNEM_STS, MNEM_LDTLB, MNEM_MOVCA,
       MNEM_NOP, MNEM_OCBI, MNEM_OCBP, MNEM_OCBWB, MNEM_PREF,
       MNEM_RTE, MNEM_SLEEP, MNEM_TRAPA,
       MNEM_FLDI0, MNEM_FLDI1, MNEM_FMOV, MNEM_FLDS, MNEM_FSTS,
       MNEM_FABS, MNEM_FADD, MNEM_FCMPEQ, MNEM_FCMPGT, MNEM_FDIV,
       MNEM_FLOAT, MNEM_FMAC, MNEM_FMUL, MNEM_FNEG, MNEM_FSQRT,
       MNEM_FSUB, MNEM_FTRC,
       MNEM_FCNVSD, MNEM_FCNVDS,
       MNEM_FIPR, MNEM_FTRV, MNEM_FRCHG, MNEM_FSCHG
};


enum { DECODE_16BIT_REL = 1 };


typedef unsigned char uint8;
typedef unsigned short uint16;
typedef unsigned int uint32;
typedef signed char int8;
typedef signed short int16;
typedef signed int int32;

typedef struct
{
  int32 imm, immlabeladdr;
  uint8 type;
  uint8 immsize, immtype, immscale;
  uint8 reg;
} oper;

typedef struct
{
  uint32 address;
  uint8 opcodelen;
  uint8 label;			/* Unused */
  uint8 mnemonic;
  uint8 opersize;
  oper oper1, oper2, oper3;
} instr;

/*
 *  Used for creating a table with rows consisting
 *  of <address><instruction-length><label-flag> to
 *  to resolve labels.
 */
typedef struct
{
  uint32 address;		/* instruction address */
  uint8 opcodelen;		/* length of instruction in bytes */
  uint8 label;			/* (flag) label at this instruction */
} instrentry;

/*
 *  Used for a table to identify what particular
 *  instruction a given opcode belongs to. Example:
 *
 *  if ((opcode & mask) == match)
 *      decode(opcode, &ins);
 *  else
 *      try_next_row_instead;
 */
typedef struct {
  int mask;			/* mask for zeroing out uninteresting bits */
  int match;			/* bit pattern it should match to get a hit */
  int (*decode)(int, instr *); /* function for decoding instruction */
} opcodeentry;

const char *programname = "dsh4";

const char *sizenames = " bwlsd";

const char *scalenames = "1248";

const char *specregnames[] = {
  "pc", "gbr", "sr", "vbr", "ssr", "spc", "dbr", "mach", "macl",
  "pr", "sgr", "fpul", "fpscr", "xmtrx"
};

const char *mnemnames[] = {
  "dc", "mov", "mova", "movt", "swap", "xtrct",
  "add", "addc", "addv", 
  "cmp/eq", "cmp/hs", "cmp/ge", "cmp/hi", "cmp/gt", "cmp/pz", "cmp/pl",
  "cmp/str", "div1", "div0s", "div0u", "dmulu", "dmuls",
  "dt", "extu", "exts", "mac", "mul", "muls", "mulu", "neg", "negc",
  "sub", "subc", "subv",
  "and", "not", "or", "tas", "tst", "xor",
  "rotl", "rotr", "rotcl", "rotcr", "shad", "shal", "shar", "shld",
  "shll", "shlr", "shll2", "shlr2", "shll8", "shlr8",
  "shll16", "shlr16",
  "bf", "bf/s", "bt", "bt/s", "bra", "braf", "bsr", "bsrf",
  "jmp", "jsr", "rts",
  "clrmac", "clrs", "clrt", "sets", "sett", "ldc", "lds", "stc", "sts",
  "ldtlb", "movca", "nop", "ocbi", "ocbp", "ocbwb", "pref", "rte",
  "sleep", "trapa",
  "fldi0", "fldi1", "fmov", "flds", "fsts", "fabs", "fadd",
  "fcmp/eq", "fcmp/gt", "fdiv", "float", "fmac", "fmul", "fneg",
  "fsqrt", "fsub", "ftrc",
  "fcnvsd", "fcnvds",
  "fipr", "ftrv", "frchg", "fschg"
};

int decodeflags = 0;

/*
 *  User options modified by parseopts() during program startup.
 */
struct {
  char format;		/* output format */
  char endian;			/* endian */
  char *fname;                /* source filename */
  unsigned org;		/* base address */
  unsigned start;		/* starting offset of chunk */
  unsigned length;		/* length of chunk */
  int debug;			/* (flag) extra debugging output */
  int labels;			/* (flag) output labels */
  int tabsize;                   /* tab-stop delta */
} options = { FORMAT_LISTING, ENDIAN_LITTLE, 0, 0, 0, 0, FALSE, FALSE, 8 };

/*
 *  Data compiled from user options and input file. Heavily used
 *  during disassembly to check boundaries and access input binary.
 */
struct {
  unsigned start;		/* low address */
  unsigned end;		/* high address */
  unsigned char *bin;		/* binary data */
} prog = { 0, 0, 0 };

/*
 *  Print error message and exit.
 */
void error(char *fmt, ...)
{
  va_list ap;

  fprintf(stderr, "%s: ", programname);
  va_start(ap, fmt);
  vfprintf(stderr, fmt, ap);
  va_end(ap);
  fflush(stderr);
  exit(1);
}

/*
 *  Print debug message if debug flag is set.
 */
void debug(char *fmt, ...)
{
  va_list ap;

  if (options.debug)
    {
      va_start(ap, fmt);
      vprintf(fmt, ap);
      va_end(ap);
      fflush(stdout);
    }
}

int accessbyte(unsigned addr)
{
  return addr - prog.start < prog.end - prog.start;
}

int accessword(unsigned addr)
{
  return accessbyte(addr) && accessbyte(addr + 1);
}

int accesslong(unsigned addr)
{
  return accessword(addr) && accessword(addr + 2);
}

int getsbyte(int *w, unsigned addr)
{
  if (!accessbyte(addr))
    return FALSE;
  *w = (char) prog.bin[addr - prog.start];
  return TRUE;
}

int getubyte(int *w, unsigned addr)
{
  if (!accessbyte(addr))
    return FALSE;
  *w = prog.bin[addr - prog.start];
  return TRUE;
}

int getsword(int *w, unsigned addr)
{
  unsigned offset = addr - prog.start;
    
  if (!accessword(addr))
    return FALSE;
  if (options.endian == ENDIAN_BIG)
    *w = (short) (prog.bin[offset] << 8) | prog.bin[offset + 1];
  else
    *w = prog.bin[offset] | (short) (prog.bin[offset + 1] << 8);
  return TRUE;
}

int getuword(int *w, unsigned addr)
{
  unsigned offset = addr - prog.start;
    
  if (!accessword(addr))
    return FALSE;
  if (options.endian == ENDIAN_BIG)
    *w = (prog.bin[offset] << 8) | prog.bin[offset + 1];
  else
    *w = prog.bin[offset] | (prog.bin[offset + 1] << 8);
  return TRUE;
}

int getlong(int *w, unsigned addr)
{
  int i, j;
  if (!getuword(&i, addr) || !getuword(&j, addr + 2))
    return FALSE;
  if (options.endian == ENDIAN_BIG)
    *w = (i << 16) + j;
  else
    *w = i + (j << 16);
  return TRUE;
}

void decode_ea_imm(int imm, int size, instr *i, oper *o)
{
  o->type = OPER_IMM;
  o->imm = imm;
  o->immsize = size;
  o->immtype = IMM_ABS;
}

void decode_ea_iimm(int imm, int size, instr *i, oper *o)
{
  o->type = OPER_IIMM;
  o->imm = imm;
  o->immsize = size;
  o->immtype = IMM_ABS;
}

void decode_ea_rn(int r, instr *i, oper *o)
{
  o->type = OPER_RN;
  o->reg = r;
}

void decode_ea_rn_bank(int r, instr *i, oper *o)
{
  o->type = OPER_RN_BANK;
  o->reg = r;
}

void decode_ea_specreg(int r, instr *i, oper *o)
{
  o->type = OPER_SPECREG;
  o->reg = r;
}

void decode_ea_ind_rn(int r, instr *i, oper *o)
{
  o->type = OPER_IND_RN;
  o->reg = r;
}

void decode_ea_ind_rn_inc(int r, instr *i, oper *o)
{
  o->type = OPER_IND_RN_INC;
  o->reg = r;
}

void decode_ea_ind_dec_rn(int r, instr *i, oper *o)
{
  o->type = OPER_IND_DEC_RN;
  o->reg = r;
}

void decode_ea_ind_r0_rn(int r, instr *i, oper *o)
{
  o->type = OPER_IND_R0_RN;
  o->reg = r;
}

void decode_ea_ind_r0_gbr(instr *i, oper *o)
{
  o->type = OPER_IND_R0_GBR;
}

void decode_ea_ind_disp_pc(int disp, instr *i, oper *o)
{
  o->type = OPER_IND_DISP_PC;

  if (i->opersize == SIZE_L)
    o->imm = disp + 4 + (i->address & 0xfffffffc);
  else
    o->imm = disp + 4 + i->address;

  o->immsize = SIZE_NONE;
  o->immtype = IMM_RELPC;
}

void decode_ea_ind_disp_rn(int disp, int r, instr *i, oper *o)
{
  o->type = OPER_IND_DISP_RN;
  o->reg = r;
  o->imm = disp;
  o->immsize = SIZE_NONE;
  o->immtype = IMM_ABS;
}

void decode_ea_ind_disp_gbr(int disp, instr *i, oper *o)
{
  o->type = OPER_IND_DISP_GBR;
  o->imm = disp;
  o->immsize = SIZE_NONE;
  o->immtype = IMM_ABS;
}

void decode_ea_frn(int r, instr *i, oper *o)
{
  o->type = OPER_FRN;
  o->reg = r;
}

void decode_ea_drn(int r, instr *i, oper *o)
{
  o->type = OPER_DRN;
  o->reg = r;
}

void decode_ea_xdn(int r, instr *i, oper *o)
{
  o->type = OPER_XDN;
  o->reg = r;
}

void decode_ea_fvn(int r, instr *i, oper *o)
{
  o->type = OPER_FVN;
  o->reg = r;
}

void decode_ea_disp_relpc(int disp, instr *i, oper *o)
{
  o->type = OPER_DISP_RELPC;
  o->imm = disp + 4 + i->address;
  o->immsize = SIZE_NONE;
  o->immtype = IMM_RELPC;
}

int decode_mov(int opcode, instr *i)
{
  int sizetab[3] = { SIZE_B, SIZE_W, SIZE_L };

  i->mnemonic = MNEM_MOV;

  if ((opcode & 0xf000) == 0xe000)
    {
      decode_ea_imm((char) (opcode & 0xff), SIZE_B, i, &i->oper1);
      decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper2);
    }
  else if ((opcode & 0xf000) == 0xd000)
    {
      i->opersize = SIZE_L;
      decode_ea_ind_disp_pc((opcode & 0xff) * 4, i, &i->oper1);
      decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper2);
    }
  else if ((opcode & 0xf000) == 0x9000)
    {
      i->opersize = SIZE_W;
      decode_ea_ind_disp_pc((opcode & 0xff) * 2, i, &i->oper1);
      decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper2);
    }
  else if ((opcode & 0xf004) == 0x2000)
    {
      i->opersize = sizetab[opcode & 3];
      decode_ea_rn((opcode >> 4) & 0xf, i, &i->oper1);
      decode_ea_ind_rn((opcode >> 8) & 0xf, i, &i->oper2);
    }
  else if ((opcode & 0xf004) == 0x2004)
    {
      i->opersize = sizetab[opcode & 3];
      decode_ea_rn((opcode >> 4) & 0xf, i, &i->oper1);
      decode_ea_ind_dec_rn((opcode >> 8) & 0xf, i, &i->oper2);
    }
  else if ((opcode & 0xf00f) == 0x6003)
    {
      decode_ea_rn((opcode >> 4) & 0xf, i, &i->oper1);
      decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper2);
    }
  else if ((opcode & 0xf004) == 0x6000)
    {
      i->opersize = sizetab[opcode & 3];
      decode_ea_ind_rn((opcode >> 4) & 0xf, i, &i->oper1);
      decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper2);
    }
  else if ((opcode & 0xf004) == 0x6004)
    {
      i->opersize = sizetab[opcode & 3];
      decode_ea_ind_rn_inc((opcode >> 4) & 0xf, i, &i->oper1);
      decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper2);
    }
  else if ((opcode & 0xfe00) == 0x8000)
    {
      i->opersize = sizetab[(opcode >> 8) & 1];
      decode_ea_rn(0, i, &i->oper1);
      decode_ea_ind_disp_rn((opcode & 0xf) * ((opcode & 0x100) ? 2 : 1), (opcode >> 4) & 0xf, i, &i->oper2);
    }
  else if ((opcode & 0xf000) == 0x1000)
    {
      i->opersize = SIZE_L;
      decode_ea_rn((opcode >> 4) & 0xf, i, &i->oper1);
      decode_ea_ind_disp_rn((opcode & 0xf) * 4, (opcode >> 8) & 0xf, i, &i->oper2);
    }
  else if ((opcode & 0xfe00) == 0x8400)
    {
      i->opersize = sizetab[(opcode >> 8) & 1];
      decode_ea_ind_disp_rn((opcode & 0xf) * ((opcode & 0x100) ? 2 : 1), (opcode >> 4) & 0xf, i, &i->oper1);
      decode_ea_rn(0, i, &i->oper2);
    }
  else if ((opcode & 0xf000) == 0x5000)
    {
      i->opersize = SIZE_L;
      decode_ea_ind_disp_rn((opcode & 0xf) * 4, (opcode >> 4) & 0xf, i, &i->oper1);
      decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper2);
    }
  else if ((opcode & 0xf00c) == 0x0004)
    {
      i->opersize = sizetab[opcode & 3];
      decode_ea_rn((opcode >> 4) & 0xf, i, &i->oper1);
      decode_ea_ind_r0_rn((opcode >> 8) & 0xf, i, &i->oper2);
    }
  else if ((opcode & 0xf00c) == 0x000c)
    {
      i->opersize = sizetab[opcode & 3];
      decode_ea_ind_r0_rn((opcode >> 4) & 0xf, i, &i->oper1);
      decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper2);
    }
  else if ((opcode & 0xfc00) == 0xc000)
    {
      i->opersize = sizetab[(opcode >> 8) & 3];
      decode_ea_rn(0, i, &i->oper1);
      decode_ea_ind_disp_gbr((opcode & 0xff) << ((opcode >> 8) & 3), i, &i->oper2);
    }
  else if ((opcode & 0xfc00) == 0xc400)
    {
      i->opersize = sizetab[(opcode >> 8) & 3];
      decode_ea_ind_disp_gbr((opcode & 0xff) << ((opcode >> 8) & 3), i, &i->oper1);
      decode_ea_rn(0, i, &i->oper2);
    }
  else
    {
      printf("%x %x\n", opcode, i->address);
      assert(0);
    }

  return TRUE;
}

int decode_movt(int opcode, instr *i)
{
  i->mnemonic = MNEM_MOVT;
  decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper1);

  return TRUE;
}

int decode_mova(int opcode, instr *i)
{
  i->mnemonic = MNEM_MOVA;
  i->opersize = SIZE_L; /* Hack, to make decode_ind_disp_pc() round down pc to longword alignment */
  decode_ea_ind_disp_pc((opcode & 0xff) * 4, i, &i->oper1);
  i->opersize = SIZE_NONE;
  decode_ea_rn(0, i, &i->oper2);

  return TRUE;
}

int decode_swap(int opcode, instr *i)
{
  i->mnemonic = MNEM_SWAP;
  i->opersize = (opcode & 1) ? SIZE_W : SIZE_B;
  decode_ea_rn((opcode >> 4) & 0xf, i, &i->oper1);
  decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper2);

  return TRUE;
}

int decode_xtrct(int opcode, instr *i)
{
  i->mnemonic = MNEM_XTRCT;
  decode_ea_rn((opcode >> 4) & 0xf, i, &i->oper1);
  decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper2);

  return TRUE;
}

int decode_add(int opcode, instr *i)
{
  if ((opcode & 0xf000) == 0x3000)
    {
      if (opcode & 2)
	{
	  if (opcode & 1)
	    i->mnemonic = MNEM_ADDV;
	  else
	    i->mnemonic = MNEM_ADDC;
	}
      else
	i->mnemonic = MNEM_ADD;

      decode_ea_rn((opcode >> 4) & 0xf, i, &i->oper1);
      decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper2);
    }
  else
    {
      i->mnemonic = MNEM_ADD;
      decode_ea_imm((char) (opcode & 0xff), SIZE_B, i, &i->oper1);
      decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper2);
    }

  return TRUE;
}

int decode_cmpeqimm(int opcode, instr *i)
{
  i->mnemonic = MNEM_CMPEQ;
  decode_ea_imm(opcode & 0xff, SIZE_B, i, &i->oper1);
  decode_ea_rn(0, i, &i->oper2);
  return TRUE;
}

int decode_cmp(int opcode, instr *i)
{
  int mnemonics[8] = { MNEM_CMPEQ, 0, MNEM_CMPHS, MNEM_CMPGE,
		       MNEM_CMPSTR, 0, MNEM_CMPHI, MNEM_CMPGT };

  i->mnemonic = mnemonics[opcode & 7];
  decode_ea_rn((opcode >> 4) & 0xf, i, &i->oper1);
  decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper2);

  return TRUE;
}

int decode_cmpsingleoperand(int opcode, instr *i)
{
  if (opcode & 4)
    i->mnemonic = MNEM_CMPPL;
  else
    i->mnemonic = MNEM_CMPPZ;
  decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper1);

  return TRUE;
}

int decode_div1(int opcode, instr *i)
{
  i->mnemonic = MNEM_DIV1;
  decode_ea_rn((opcode >> 4) & 0xf, i, &i->oper1);
  decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper2);

  return TRUE;
}

int decode_div0s(int opcode, instr *i)
{
  i->mnemonic = MNEM_DIV0S;
  decode_ea_rn((opcode >> 4) & 0xf, i, &i->oper1);
  decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper2);

  return TRUE;
}

int decode_div0u(int opcode, instr *i)
{
  i->mnemonic = MNEM_DIV0U;
  return TRUE;
}

int decode_dmul(int opcode, instr *i)
{
  if (opcode & 8)
    i->mnemonic = MNEM_DMULS;
  else
    i->mnemonic = MNEM_DMULU;
  i->opersize = SIZE_L;
  decode_ea_rn((opcode >> 4) & 0xf, i, &i->oper1);
  decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper2);

  return TRUE;
}

int decode_dt(int opcode, instr *i)
{
  i->mnemonic = MNEM_DT;
  decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper1);

  return TRUE;
}

int decode_ext(int opcode, instr *i)
{
  if (opcode & 2)
    i->mnemonic = MNEM_EXTS;
  else
    i->mnemonic = MNEM_EXTU;
  if (opcode & 1)
    i->opersize = SIZE_W;
  else
    i->opersize = SIZE_B;

  decode_ea_rn((opcode >> 4) & 0xf, i, &i->oper1);
  decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper2);

  return TRUE;
}

int decode_mac(int opcode, instr *i)
{
  i->mnemonic = MNEM_MAC;
  if (opcode & 0x4000)
    i->opersize = SIZE_L;
  else
    i->opersize = SIZE_W;

  decode_ea_ind_rn_inc((opcode >> 4) & 0xf, i, &i->oper1);
  decode_ea_ind_rn_inc((opcode >> 8) & 0xf, i, &i->oper2);

  return TRUE;
}

int decode_mul(int opcode, instr *i)
{
  if (opcode & 0x2000)
    {
      if (opcode & 1)
	i->mnemonic = MNEM_MULS;
      else
	i->mnemonic = MNEM_MULU;
      i->opersize = SIZE_W;
    }
  else
    {
      i->mnemonic = MNEM_MUL;
      i->opersize = SIZE_L;
    }

  decode_ea_rn((opcode >> 4) & 0xf, i, &i->oper1);
  decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper2);

  return TRUE;
}

int decode_neg(int opcode, instr *i)
{
  if (opcode & 1)
    i->mnemonic = MNEM_NEG;
  else
    i->mnemonic = MNEM_NEGC;

  decode_ea_rn((opcode >> 4) & 0xf, i, &i->oper1);
  decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper2);

  return TRUE;
}

int decode_sub(int opcode, instr *i)
{
  if (opcode & 2)
    {
      if (opcode & 1)
	i->mnemonic = MNEM_SUBV;
      else
	i->mnemonic = MNEM_SUBC;
    }
  else
    i->mnemonic = MNEM_SUB;

  decode_ea_rn((opcode >> 4) & 0xf, i, &i->oper1);
  decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper2);

  return TRUE;
}


int decode_stdbitwisealu(int opcode, instr *i)
{
  if ((opcode & 0xf000) == 0xc000)
    {
      decode_ea_imm(opcode & 0xff, SIZE_B, i, &i->oper1);
      if (opcode & 0x400)
	{
	  i->opersize = SIZE_B;
	  decode_ea_ind_r0_gbr(i, &i->oper2);
	}
      else
	decode_ea_rn(0, i, &i->oper2);
    }
  else
    {
      decode_ea_rn((opcode >> 4) & 0xf, i, &i->oper1);
      decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper2);
    }

  return TRUE;
}

int decode_and(int opcode, instr *i)
{
  i->mnemonic = MNEM_AND;
  return decode_stdbitwisealu(opcode, i);
}

int decode_not(int opcode, instr *i)
{
  i->mnemonic = MNEM_NOT;
  decode_ea_rn((opcode >> 4) & 0xf, i, &i->oper1);
  decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper2);

  return TRUE;
}

int decode_or(int opcode, instr *i)
{
  i->mnemonic = MNEM_OR;
  return decode_stdbitwisealu(opcode, i);
}

int decode_tas(int opcode, instr *i)
{
  i->mnemonic = MNEM_TAS;
  i->opersize = SIZE_B;
  decode_ea_ind_rn((opcode >> 8) & 0xf, i, &i->oper1);

  return TRUE;
}

int decode_tst(int opcode, instr *i)
{
  i->mnemonic = MNEM_TST;
  return decode_stdbitwisealu(opcode, i);
}

int decode_xor(int opcode, instr *i)
{
  i->mnemonic = MNEM_XOR;
  return decode_stdbitwisealu(opcode, i);
}

int decode_rot(int opcode, instr *i)
{
  int mnemonics[4] = { MNEM_ROTL, MNEM_ROTR, MNEM_ROTCL, MNEM_ROTCR };
  i->mnemonic = mnemonics[((opcode & 0x20) >> 4) + (opcode & 1)];
  decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper1);

  return TRUE;
}

int decode_shiftd(int opcode, instr *i)
{
  if (opcode & 1)
    i->mnemonic = MNEM_SHLD;
  else
    i->mnemonic = MNEM_SHAD;

  decode_ea_rn((opcode >> 4) & 0xf, i, &i->oper1);
  decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper2);

  return TRUE;
}

int decode_shift(int opcode, instr *i)
{
  int mnemonics[4] = { MNEM_SHLL, MNEM_SHLR, MNEM_SHAL, MNEM_SHAR };
  i->mnemonic = mnemonics[((opcode & 0x20) >> 4) + (opcode & 1)];
  decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper1);

  return TRUE;

}

int decode_multishift(int opcode, instr *i)
{
  int mnemonics[6] = { MNEM_SHLL2, MNEM_SHLR2, MNEM_SHLL8, MNEM_SHLR8,
		       MNEM_SHLL16, MNEM_SHLR16 };
  i->mnemonic = mnemonics[((opcode & 0x30) >> 3) + (opcode & 1)];
  decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper1);

  return TRUE;

}

int decode_bcc(int opcode, instr *i)
{
  int mnemonics[4] = { MNEM_BT, MNEM_BF, MNEM_BTS, MNEM_BFS };

  i->mnemonic = mnemonics[(opcode & 0x600) >> 9];
  decode_ea_disp_relpc((char) (opcode & 0xff) * 2, i, &i->oper1);

  return TRUE;
}

int decode_branch(int opcode, instr *i)
{
  if (opcode & 0x1000)
    i->mnemonic = MNEM_BSR;
  else
    i->mnemonic = MNEM_BRA;

  decode_ea_disp_relpc(((opcode & 0x7ff) - (opcode & 0x800)) * 2, i, &i->oper1);

  return TRUE;
}

int decode_branchf(int opcode, instr *i)
{
  if (opcode & 0x0020)
    i->mnemonic = MNEM_BSRF;
  else
    i->mnemonic = MNEM_BRAF;

  decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper1);

  return TRUE;
}

int decode_jump(int opcode, instr *i)
{
  if (opcode & 0x0020)
    i->mnemonic = MNEM_JMP;
  else
    i->mnemonic = MNEM_JSR;

  decode_ea_ind_rn((opcode >> 8) & 0xf, i, &i->oper1);

  return TRUE;
}

int decode_rts(int opcode, instr *i)
{
  i->mnemonic = MNEM_RTS;
  return TRUE;
}

int decode_clrmac(int opcode, instr *i)
{
  i->mnemonic = MNEM_CLRMAC;
  return TRUE;
}

int decode_setclrst(int opcode, instr *i)
{
  int mnemonics[4] = { MNEM_CLRT, MNEM_CLRS, MNEM_SETT, MNEM_SETS };
  i->mnemonic = mnemonics[((opcode & 0x10) >> 3) + ((opcode & 0x40) >> 6)];
  return TRUE;
}

int decode_ldc(int opcode, instr *i)
{
  int specregs[5] = { SPECREG_SR, SPECREG_GBR, SPECREG_VBR, SPECREG_SSR,
		      SPECREG_SPC };

  i->mnemonic = MNEM_LDC;
  if ((opcode & 0xf0ff) == 0x40fa || (opcode & 0xf0ff) == 0x40f6)
    {
      if (opcode & 8)
	decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper1);
      else
	{
	  i->opersize = SIZE_L;
	  decode_ea_ind_rn_inc((opcode >> 8) & 0xf, i, &i->oper1);
	}

      decode_ea_specreg(SPECREG_DBR, i, &i->oper2);
    }
  else
    {
      if (opcode & 1)
	{
	  i->opersize = SIZE_L;
	  decode_ea_ind_rn_inc((opcode >> 8) & 0xf, i, &i->oper1);
	}
      else
	decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper1);

      if (opcode & 0x80)
	decode_ea_rn_bank((opcode >> 4) & 7, i, &i->oper2);
      else
	decode_ea_specreg(specregs[(opcode >> 4) & 7], i, &i->oper2);
    }

  return TRUE;
}

int decode_lds(int opcode, instr *i)
{
  int specregs[7] = { SPECREG_MACH, SPECREG_MACL, SPECREG_PR, 0, 0,
		      SPECREG_FPUL, SPECREG_FPSCR };

  i->mnemonic = MNEM_LDS;
  if (opcode & 8)
    decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper1);
  else
    {
      i->opersize = SIZE_L;
      decode_ea_ind_rn_inc((opcode >> 8) & 0xf, i, &i->oper1);
    }

  decode_ea_specreg(specregs[(opcode >> 4) & 0xf], i, &i->oper2);

  return TRUE;
}

int decode_stc(int opcode, instr *i)
{
  int specregs[5] = { SPECREG_SR, SPECREG_GBR, SPECREG_VBR, SPECREG_SSR,
		      SPECREG_SPC };

  i->mnemonic = MNEM_STC;
  if ((opcode & 0xf03f) == 0x003a || (opcode & 0xf03f) == 0x4032)
    {
      decode_ea_specreg((opcode & 0x40) ? SPECREG_DBR : SPECREG_SGR, i, &i->oper1);
      if (opcode & 8)
	decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper2);
      else
	{
	  i->opersize = SIZE_L;
	  decode_ea_ind_dec_rn((opcode >> 8) & 0xf, i, &i->oper2);
	}
    }
  else
    {
      if (opcode & 0x80)
	decode_ea_rn_bank((opcode >> 4) & 7, i, &i->oper1);
      else
	decode_ea_specreg(specregs[(opcode >> 4) & 7], i, &i->oper1);

      if (opcode & 1)
	{
	  i->opersize = SIZE_L;
	  decode_ea_ind_dec_rn((opcode >> 8) & 0xf, i, &i->oper2);
	}
      else
	decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper2);
    }

  return TRUE;
}

int decode_sts(int opcode, instr *i)
{
  int specregs[7] = { SPECREG_MACH, SPECREG_MACL, SPECREG_PR, 0, 0,
		      SPECREG_FPUL, SPECREG_FPSCR };

  i->mnemonic = MNEM_STS;
  decode_ea_specreg(specregs[(opcode >> 4) & 0xf], i, &i->oper1);
  if (opcode & 8)
    decode_ea_rn((opcode >> 8) & 0xf, i, &i->oper2);
  else
    {
      i->opersize = SIZE_L;
      decode_ea_ind_dec_rn((opcode >> 8) & 0xf, i, &i->oper2);
    }

  return TRUE;
}

int decode_sleep(int opcode, instr *i)
{
  i->mnemonic = MNEM_SLEEP;
  return TRUE;
}
int decode_trapa(int opcode, instr *i)
{
  i->mnemonic = MNEM_TRAPA;
  decode_ea_imm(opcode & 0xff, SIZE_B, i, &i->oper1);
  return TRUE;
}

int decode_ldtlb(int opcode, instr *i)
{
  i->mnemonic = MNEM_LDTLB;
  return TRUE;
}

int decode_nop(int opcode, instr *i)
{
  i->mnemonic = MNEM_NOP;
  return TRUE;
}

int decode_movca(int opcode, instr *i)
{
  i->mnemonic = MNEM_MOVCA;
  i->opersize = SIZE_L;
  decode_ea_rn(0, i, &i->oper1);
  decode_ea_ind_rn((opcode >> 8) & 0xf, i, &i->oper2);
  return TRUE;
}

int decode_ocxxx(int opcode, instr *i)
{
  int mnemonics[4] = { MNEM_PREF, MNEM_OCBI, MNEM_OCBP, MNEM_OCBWB };

  i->mnemonic = mnemonics[(opcode & 0x30) >> 4];
  decode_ea_ind_rn((opcode >> 8) & 0xf, i, &i->oper1);

  return TRUE;
}

int decode_rte(int opcode, instr *i)
{
  i->mnemonic = MNEM_RTE;
  return TRUE;
}

int decode_fldi0(int opcode, instr *i)
{
  i->mnemonic = MNEM_FLDI0;
  decode_ea_frn((opcode >> 8) & 0xf, i, &i->oper1);
  return TRUE;
}

int decode_fldi1(int opcode, instr *i)
{
  i->mnemonic = MNEM_FLDI1;
  decode_ea_frn((opcode >> 8) & 0xf, i, &i->oper1);
  return TRUE;
}

int decode_fmovf(int opcode, instr *i)
{
  i->mnemonic = MNEM_FMOV;
  if ((opcode & 0xf) != 0xc)
    i->opersize = SIZE_S;

  switch (opcode & 0xf)
    {
    case 0xc:
      decode_ea_frn((opcode >> 4) & 0xf, i, &i->oper1);
      decode_ea_frn((opcode >> 8) & 0xf, i, &i->oper2);
      break;
    case 0x8:
      decode_ea_ind_rn((opcode >> 4) & 0xf, i, &i->oper1);
      decode_ea_frn((opcode >> 8) & 0xf, i, &i->oper2);
      break;
    case 0x6:
      decode_ea_ind_r0_rn((opcode >> 4) & 0xf, i, &i->oper1);
      decode_ea_frn((opcode >> 8) & 0xf, i, &i->oper2);
      break;
    case 0x9:
      decode_ea_ind_rn_inc((opcode >> 4) & 0xf, i, &i->oper1);
      decode_ea_frn((opcode >> 8) & 0xf, i, &i->oper2);
      break;
    case 0xa:
      decode_ea_frn((opcode >> 4) & 0xf, i, &i->oper1);
      decode_ea_ind_rn((opcode >> 8) & 0xf, i, &i->oper2);
      break;
    case 0xb:
      decode_ea_frn((opcode >> 4) & 0xf, i, &i->oper1);
      decode_ea_ind_dec_rn((opcode >> 8) & 0xf, i, &i->oper2);
      break;
    case 0x7:
      decode_ea_frn((opcode >> 4) & 0xf, i, &i->oper1);
      decode_ea_ind_r0_rn((opcode >> 8) & 0xf, i, &i->oper2);
      break;
    }

  return TRUE;
}

int decode_fldsts(int opcode, instr *i)
{
  if (opcode & 0x10)
    {
      i->mnemonic = MNEM_FLDS;
      decode_ea_frn((opcode >> 8) & 0xf, i, &i->oper1);
      decode_ea_specreg(SPECREG_FPUL, i, &i->oper2);
    }
  else
    {
      i->mnemonic = MNEM_FSTS;
      decode_ea_specreg(SPECREG_FPUL, i, &i->oper1);
      decode_ea_frn((opcode >> 8) & 0xf, i, &i->oper2);
    }

  return TRUE;
}

int decode_fabsf(int opcode, instr *i)
{
  i->mnemonic = MNEM_FABS;
  decode_ea_frn((opcode >> 8) & 0xf, i, &i->oper1);
  return TRUE;
}

int decode_faddf(int opcode, instr *i)
{
  i->mnemonic = MNEM_FADD;
  decode_ea_frn((opcode >> 4) & 0xf, i, &i->oper1);
  decode_ea_frn((opcode >> 8) & 0xf, i, &i->oper2);
  return TRUE;
}

int decode_fcmpf(int opcode, instr *i)
{
  if (opcode & 1)
    i->mnemonic = MNEM_FCMPGT;
  else
    i->mnemonic = MNEM_FCMPEQ;

  decode_ea_frn((opcode >> 4) & 0xf, i, &i->oper1);
  decode_ea_frn((opcode >> 8) & 0xf, i, &i->oper2);
  return TRUE;
}

int decode_fdivf(int opcode, instr *i)
{
  i->mnemonic = MNEM_FDIV;
  decode_ea_frn((opcode >> 4) & 0xf, i, &i->oper1);
  decode_ea_frn((opcode >> 8) & 0xf, i, &i->oper2);
  return TRUE;
}

int decode_floatf(int opcode, instr *i)
{
  i->mnemonic = MNEM_FLOAT;
  decode_ea_specreg(SPECREG_FPUL, i, &i->oper1);
  decode_ea_frn((opcode >> 8) & 0xf, i, &i->oper2);
  return TRUE;
}

int decode_fmac(int opcode, instr *i)
{
  i->mnemonic = MNEM_FMAC;
  decode_ea_frn(0, i, &i->oper1);
  decode_ea_frn((opcode >> 4) & 0xf, i, &i->oper2);
  decode_ea_frn((opcode >> 8) & 0xf, i, &i->oper3);
  return TRUE;
}

int decode_fmulf(int opcode, instr *i)
{
  i->mnemonic = MNEM_FMUL;
  decode_ea_frn((opcode >> 4) & 0xf, i, &i->oper1);
  decode_ea_frn((opcode >> 8) & 0xf, i, &i->oper2);
  return TRUE;
}

int decode_fnegf(int opcode, instr *i)
{
  i->mnemonic = MNEM_FNEG;
  decode_ea_frn((opcode >> 8) & 0xf, i, &i->oper1);
  return TRUE;
}

int decode_fsqrtf(int opcode, instr *i)
{
  i->mnemonic = MNEM_FSQRT;
  decode_ea_frn((opcode >> 8) & 0xf, i, &i->oper1);
  return TRUE;
}

int decode_fsubf(int opcode, instr *i)
{
  i->mnemonic = MNEM_FSUB;
  decode_ea_frn((opcode >> 4) & 0xf, i, &i->oper1);
  decode_ea_frn((opcode >> 8) & 0xf, i, &i->oper2);
  return TRUE;
}

int decode_ftrcf(int opcode, instr *i)
{
  i->mnemonic = MNEM_FTRC;
  decode_ea_frn((opcode >> 8) & 0xf, i, &i->oper1);
  decode_ea_specreg(SPECREG_FPUL, i, &i->oper2);
  return TRUE;
}

int decode_fcnvsd(int opcode, instr *i)
{
  i->mnemonic = MNEM_FCNVSD;
  decode_ea_specreg(SPECREG_FPUL, i, &i->oper1);
  decode_ea_drn((opcode >> 8) & 0xe, i, &i->oper2);
  return TRUE;
}

int decode_fcnvds(int opcode, instr *i)
{
  i->mnemonic = MNEM_FCNVDS;
  decode_ea_drn((opcode >> 8) & 0xe, i, &i->oper1);
  decode_ea_specreg(SPECREG_FPUL, i, &i->oper2);
  return TRUE;
}

int decode_fipr(int opcode, instr *i)
{
  i->mnemonic = MNEM_FIPR;
  decode_ea_fvn((opcode >> 6) & 0xc, i, &i->oper1);
  decode_ea_fvn((opcode >> 8) & 0xc, i, &i->oper2);
  return TRUE;
}

int decode_ftrv(int opcode, instr *i)
{
  i->mnemonic = MNEM_FTRV;
  decode_ea_specreg(SPECREG_XMTRX, i, &i->oper1);
  decode_ea_fvn((opcode >> 8) & 0xc, i, &i->oper2);
  return TRUE;
}

int decode_frchg(int opcode, instr *i)
{
  i->mnemonic = MNEM_FRCHG;
  return TRUE;
}

int decode_fschg(int opcode, instr *i)
{
  i->mnemonic = MNEM_FSCHG;
  return TRUE;
}

int decode_unknown(int opcode, instr *i)
{
  i->mnemonic = MNEM_DC;
  i->opersize = SIZE_W;
  i->oper1.type = OPER_IIMM;
  i->oper1.imm = opcode;
  i->oper1.immtype = (decodeflags & DECODE_16BIT_REL) ? IMM_ANY : IMM_ABS;
  i->oper1.immsize = SIZE_W;
  i->opcodelen = 2;

  return TRUE;
}

opcodeentry opcodetab0[] =
{
  { 0xf00f, 0x0007,   decode_mul }, /* mov */
  { 0xf00f, 0x000f,   decode_mac }, /* mov */
  { 0xf004, 0x0004, decode_mov },
  { 0xf0ff, 0x0029, decode_movt },
  { 0xf0df, 0x0003, decode_branchf },
  { 0xf0ff, 0x00c3, decode_movca },
  { 0xf0cf, 0x0083, decode_ocxxx },
  { 0xf0cf, 0x0002, decode_stc },
  { 0xf0ff, 0x0042, decode_stc },
  { 0xf08f, 0x0082, decode_stc },
  { 0xf0ff, 0x003a,   decode_stc }, /* sts */
  { 0xf0ff, 0x00fa, decode_stc },
  { 0xf0cf, 0x000a, decode_sts },
  { 0xf0ff, 0x405a, decode_sts },
  { 0xf0ff, 0x406a, decode_sts },
  { 0xffaf, 0x0008, decode_setclrst },
  { 0xffff, 0x0009, decode_nop },
  { 0xffff, 0x000b, decode_rts },
  { 0xffff, 0x0019, decode_div0u },
  { 0xffff, 0x001b, decode_sleep },
  { 0xffff, 0x0028, decode_clrmac },
  { 0xffff, 0x002b, decode_rte },
  { 0xffff, 0x0038, decode_ldtlb },
  { 0, 0, 0 }
};

opcodeentry opcodetab1[] =
{
  { 0xf000, 0x1000, decode_mov },
  { 0, 0, 0 }
};

opcodeentry opcodetab2[] =
{
  { 0xf00b, 0x2003,   0 }, /* mov */
  { 0xf008, 0x2000, decode_mov },
  { 0xf00f, 0x2007, decode_div0s },
  { 0xf00f, 0x2008, decode_tst },
  { 0xf00f, 0x2009, decode_and },
  { 0xf00f, 0x200a, decode_xor },
  { 0xf00f, 0x200b, decode_or },
  { 0xf00f, 0x200c, decode_cmp },
  { 0xf00f, 0x200d, decode_xtrct },
  { 0xf00e, 0x200e, decode_mul },
  { 0, 0, 0 }
};

opcodeentry opcodetab3[] =
{
  { 0xf00f, 0x3000, decode_cmp },
  { 0xf00f, 0x3004, decode_div1 },
  { 0xf00f, 0x3008, decode_sub },
  { 0xf00e, 0x300a, decode_sub },
  { 0xf00f, 0x300c, decode_add },
  { 0xf00e, 0x300e, decode_add },
  { 0xf00a, 0x3002, decode_cmp },
  { 0xf007, 0x3005, decode_dmul },
  { 0, 0, 0 }
};

opcodeentry opcodetab4[] =
{
  { 0xf0fb, 0x4011, decode_cmpsingleoperand },
  { 0xf0ff, 0x4010, decode_dt },
  { 0xf00f, 0x400f, decode_mac },
  { 0xf0ff, 0x401b, decode_tas },
  { 0xf0de, 0x4004, decode_rot },
  { 0xf00e, 0x400c, decode_shiftd },
  { 0xf0de, 0x4000, decode_shift },
  { 0xf0ee, 0x4008, decode_multishift },
  { 0xf0fe, 0x4028, decode_multishift },
  { 0xf0df, 0x400b, decode_jump },
  { 0xf0cf, 0x400e, decode_ldc },
  { 0xf0ff, 0x404e, decode_ldc },
  { 0xf0ff, 0x40fa,   decode_ldc }, /* lds */
  { 0xf08f, 0x408e, decode_ldc },

  { 0xf0cf, 0x4007, decode_ldc },
  { 0xf0ff, 0x4047, decode_ldc },
  { 0xf0ff, 0x40f6, decode_ldc }, /* lds */
  { 0xf0ff, 0x4047, decode_ldc },

  { 0xf0cf, 0x400a, decode_lds },
  { 0xf0cf, 0x4006, decode_lds },

  { 0xf0ff, 0x405a, decode_lds },
  { 0xf0ff, 0x406a, decode_lds },
  { 0xf0ff, 0x4056, decode_lds },
  { 0xf0ff, 0x4066, decode_lds },

  { 0xf0cf, 0x4003, decode_stc },
  { 0xf0ff, 0x4043, decode_stc },
  { 0xf08f, 0x4083, decode_stc },
  { 0xf0ff, 0x4032,   decode_stc }, /* sts */
  { 0xf0ff, 0x40f2, decode_stc },
  { 0xf0cf, 0x4002, decode_sts },
  { 0xf0ff, 0x4052, decode_sts },
  { 0xf0ff, 0x4062, decode_sts },

  { 0, 0, 0 }
};

opcodeentry opcodetab5[] =
{
  { 0xf000, 0x5000, decode_mov },
  { 0, 0, 0 }
};

opcodeentry opcodetab6[] =
{
  { 0xf00f, 0x6007,   decode_not }, /* mov */
  { 0xf008, 0x6000, decode_mov },
  { 0xf00c, 0x600c, decode_ext },
  { 0xf00e, 0x6008, decode_swap },
  { 0xf00e, 0x600a, decode_neg },
  { 0, 0, 0 }
};

opcodeentry opcodetab7[] =
{
  { 0xf000, 0x7000, decode_add },
  { 0, 0, 0 }
};

opcodeentry opcodetab8[] =
{
  { 0xfa00, 0x8000, decode_mov },
  { 0xff00, 0x8800, decode_cmpeqimm },
  { 0xf900, 0x8900, decode_bcc },
  { 0, 0, 0 }
};

opcodeentry opcodetab9[] =
{
  { 0xf000, 0x9000, decode_mov },
  { 0, 0, 0 }
};

opcodeentry opcodetaba[] =
{
  { 0xf000, 0xa000, decode_branch },
  { 0, 0, 0 }
};

opcodeentry opcodetabb[] =
{
  { 0xf000, 0xb000, decode_branch }
};

opcodeentry opcodetabc[] =
{
  { 0xff00, 0xc300,   decode_trapa }, /* mov */
  { 0xff00, 0xc700,   decode_mova }, /* mov */
  { 0xf800, 0xc000, decode_mov },
  { 0xfb00, 0xc800, decode_tst },
  { 0xfb00, 0xc900, decode_and },
  { 0xfb00, 0xca00, decode_xor },
  { 0xfb00, 0xcb00, decode_or },
  { 0, 0, 0 }
};

opcodeentry opcodetabd[] =
{
  { 0xf000, 0xd000, decode_mov },
  { 0, 0, 0 }
};

opcodeentry opcodetabe[] =
{
  { 0xf000, 0xe000, decode_mov },
  { 0, 0, 0 }
};

opcodeentry opcodetabf[] =
{
  { 0xf0ff, 0xf08d, decode_fldi0 },
  { 0xf0ff, 0xf09d, decode_fldi1 },
  { 0xf00f, 0xf00c, decode_fmovf },
  { 0xf00e, 0xf006, decode_fmovf },
  { 0xf00c, 0xf008, decode_fmovf },
  { 0xf0ef, 0xf00d, decode_fldsts },
  { 0xf0ff, 0xf05d, decode_fabsf },
  { 0xf00f, 0xf000, decode_faddf },
  { 0xf00e, 0xf004, decode_fcmpf },
  { 0xf00f, 0xf003, decode_fdivf },
  { 0xf0ff, 0xf02d, decode_floatf },
  { 0xf00f, 0xf00e, decode_fmac },
  { 0xf00f, 0xf002, decode_fmulf },
  { 0xf0ff, 0xf04d, decode_fnegf },
  { 0xf0ff, 0xf06d, decode_fsqrtf },
  { 0xf00f, 0xf001, decode_fsubf },
  { 0xf0ff, 0xf03d, decode_ftrcf },
  { 0xf1ff, 0xf0ad, decode_fcnvsd },
  { 0xf1ff, 0xf0bd, decode_fcnvds },
  { 0xf0ff, 0xf0ed, decode_fipr },
  { 0xf3ff, 0xf1fd, decode_ftrv },
  { 0xffff, 0xfbfd, decode_frchg },
  { 0xffff, 0xf3fd, decode_fschg },
  { 0, 0, 0 }
};

opcodeentry *opcodetab[16] =
{
  opcodetab0, opcodetab1, opcodetab2, opcodetab3,
  opcodetab4, opcodetab5, opcodetab6, opcodetab7,
  opcodetab8, opcodetab9, opcodetaba, opcodetabb,
  opcodetabc, opcodetabd, opcodetabe, opcodetabf
};

int print_pad(int current, int end)
{
  int new;
  while (current < end)
    {
      new = current + (options.tabsize - (current % options.tabsize));
      if (new > end)
	  for (; current < end; current++)
	    printf(" ");
      else
	{
	  printf("\t");
	  current = new;
	}
    }
  return current;
}

int print_oper(oper *o)
{
  int j;
  char buf[256];

  buf[0] = 0;

  switch (o->type)
    {
    case OPER_RN:
      sprintf(buf, "r%d", o->reg);
      break;
    case OPER_SPECREG:
      sprintf(buf, "%s", specregnames[o->reg]);
      break;
    case OPER_IND_RN:
      sprintf(buf, "@r%d", o->reg);
      break;
    case OPER_IND_RN_INC: 
      sprintf(buf, "@r%d+", o->reg);
      break;
    case OPER_IND_DEC_RN: 
      sprintf(buf, "@-r%d", o->reg);
      break;
    case OPER_IND_R0_RN: 
      sprintf(buf, "@(r0,r%d)", o->reg);
      break;
    case OPER_IND_R0_GBR: 
      sprintf(buf, "@(r0,gbr)");
      break;
    case OPER_RN_BANK:
      sprintf(buf, "r%d_bank", o->reg);
      break;
    case OPER_FRN:
      sprintf(buf, "fr%d", o->reg);
      break;
    case OPER_DRN:
      sprintf(buf, "dr%d", o->reg);
      break;
    case OPER_XDN:
      sprintf(buf, "xd%d", o->reg);
      break;
    case OPER_FVN:
      sprintf(buf, "fv%d", o->reg);
      break;
    case OPER_IND_DISP_RN:
      sprintf(buf, "@(");
      if (o->immtype & IMM_REL)
	{
	  sprintf(buf + strlen(buf), "l%x", o->immlabeladdr);
	  j = (unsigned) o->imm - (unsigned) o->immlabeladdr;
	  if (o->immlabeladdr != o->imm)
	    sprintf(buf + strlen(buf), "%c%s%x", (j >= 0 ? '+' : '-'), (abs(j) <= 9 ? "" : "$"), abs(j));
	}
      else
	sprintf(buf + strlen(buf), "%s%x", (o->imm >= 0 && o->imm <= 9) ? "" : "$", o->imm);
      sprintf(buf + strlen(buf), ",r%d)", o->reg);
      break;
    case OPER_IND_DISP_PC:
      sprintf(buf, "@(");
      if (o->immtype & IMM_REL)
	{
	  sprintf(buf + strlen(buf), "l%x", o->immlabeladdr);
	  j = (unsigned) o->imm - (unsigned) o->immlabeladdr;
	  if (o->immlabeladdr != o->imm)
	    sprintf(buf + strlen(buf), "%c%s%x", (j >= 0 ? '+' : '-'), (abs(j) <= 9 ? "" : "$"), abs(j));
	}
      else
	sprintf(buf + strlen(buf), "%s%x", (o->imm >= 0 && o->imm <= 9) ? "" : "$", o->imm);
      sprintf(buf + strlen(buf), ",pc)");
      break;
    case OPER_IND_DISP_GBR:
      sprintf(buf, "@(");
      if (o->immtype & IMM_REL)
	{
	  sprintf(buf + strlen(buf), "l%x", o->immlabeladdr);
	  j = (unsigned) o->imm - (unsigned) o->immlabeladdr;
	  if (o->immlabeladdr != o->imm)
	    sprintf(buf + strlen(buf), "%c%s%x", (j >= 0 ? '+' : '-'), (abs(j) <= 9 ? "" : "$"), abs(j));
	}
      else
	sprintf(buf + strlen(buf), "%s%x", (o->imm >= 0 && o->imm <= 9) ? "" : "$", o->imm);
      sprintf(buf + strlen(buf), ",gbr)");
      break;
    case OPER_IMM:
      sprintf(buf, "#");
    case OPER_IIMM:
      if (o->immtype & IMM_REL)
	{
	  sprintf(buf + strlen(buf), "l%x", o->immlabeladdr);
	  j = (unsigned) o->imm - (unsigned) o->immlabeladdr;
	  if (o->immlabeladdr != o->imm)
	    sprintf(buf + strlen(buf), "%c%s%x", (j >= 0 ? '+' : '-'), (abs(j) <= 9 ? "" : "$"), abs(j));
	}
      else
	{
	  if (o->imm < 0 && o->immsize != SIZE_L)
	    sprintf(buf + strlen(buf), "-%s%x", (o->imm >= 0 && o->imm <= 9) ? "" : "$", -o->imm);
	  else
	    sprintf(buf + strlen(buf), "%s%x", (o->imm >= 0 && o->imm <= 9) ? "" : "$", o->imm);
	}
      break;
    case OPER_DISP_RELPC:
      if (o->immtype & IMM_REL)
	{
	  sprintf(buf, "l%x", o->immlabeladdr);
	  j = (unsigned) o->imm - (unsigned) o->immlabeladdr;
	  if (o->immlabeladdr != o->imm)
	    sprintf(buf + strlen(buf), "%c%s%x", (j >= 0 ? '+' : '-'), (abs(j) <= 9 ? "" : "$"), abs(j));
	}
      else
	{
	  sprintf(buf, "%s%x", (o->imm >= 0 && o->imm <= 9) ? "" : "$", o->imm);
	}
      break;
    default:
      sprintf(buf, "Unknown operand");
      break;
    }

  printf("%s", buf);
  return strlen(buf);
}

int print_instr(instr *ins, int len)
{
  int len0 = len;
  
  printf("%s", mnemnames[ins->mnemonic]);
  len += strlen(mnemnames[ins->mnemonic]);
  if (ins->opersize)
    {
      printf(".%c", sizenames[ins->opersize]);
      len += 2;
    }

  if (ins->oper1.type || ins->oper2.type)
    len = print_pad(len, len0 + 8);

  if (ins->oper1.type)
    len += print_oper(&ins->oper1);
  if (ins->oper1.type && ins->oper2.type)
    {
      printf(",");
      len++;
    }
  if (ins->oper2.type)
    len += print_oper(&ins->oper2);
  if (ins->oper1.type && ins->oper2.type && ins->oper3.type)
    {
      printf(",");
      len++;
    }
  if (ins->oper3.type)
    len += print_oper(&ins->oper3);

  return len;
}

/*
 * Initialize <instr> to known defaults.
 */
void init_instr(instr *i, unsigned pc)
{
  memset(i, 0, sizeof(instr));
  i->address = pc;		/* should not be changed later */
  i->opcodelen = 2;
  i->mnemonic = 0xff;		/* magic -- should be changed later */
  i->opersize = SIZE_NONE;
}

/*
 *  Perform limited sanity checking on an <instr> initialized by init_instr().
 */
void sanitycheck_instr(instr *i, unsigned pc)
{
  assert(i->address == pc);
  assert(i->mnemonic != 0xff);
}

/*
 *  Read opcode pointed to by pc, and decode it into <instr>.
 */
void decode_instr(instr *i, unsigned pc)
{
  int opcode;
  opcodeentry *p;

  if (!getuword(&opcode, pc))
    assert(0);
  init_instr(i, pc);
    
  p = opcodetab[(opcode >> 12) & 0xf];

  while (p->match || p->mask || p->decode)
    {
      if ((opcode & p->mask) == p->match) /* matching entry found? */
	{
	  if (!p->decode || !p->decode(opcode, i)) /* no success? */
	    {
	      init_instr(i, pc);
	      if (!decode_unknown(opcode, i))
		assert(0);
	    }
	  break;		/* done -- one try only */
	}
      p++;
    }

  if (!(p->match || p->mask || p->decode))  /* matching entry not found? */
    if (!decode_unknown(opcode, i))
      assert(0);

  sanitycheck_instr(i, pc);
}

/*
 *  Pass 1 -- Build instruction length table.
 */
void pass1(instrentry **instab, int *inscount)
{
  int bufcount = 1024;
  int bufsize = bufcount * sizeof(instrentry);
  unsigned pc;
  instr ins;
  instrentry *instab2;
  instrentry *ip;

  debug("Pass 1...\n");
  debug("Allocate initial table of %d kB\n", bufsize / 1024);
  if (!(*instab = (instrentry *) malloc(bufsize))) /* initial table */
    error("Out of memory\n");

  for (pc = prog.start, ip = *instab, *inscount = 0;
       pc < prog.end;
       pc += ins.opcodelen, ip++, (*inscount)++)
    {
      if (!accessword(pc))	/* make sure we're not getting out of bounds */
	break;
      decode_instr(&ins, pc);

      if (*inscount == bufcount) /* expand table if is too small */
	{
	  debug("Expand table from %d to %d kB\n",
		bufsize / 1024, bufsize * 2 / 1024);
	  instab2 = (instrentry *) realloc(*instab, bufsize * 2);
	  if (!instab2)
	    {
	      free(*instab);
	      error("Out of memory.\n");
	    }
	  *instab = instab2;
	  ip = *instab + *inscount;
	  bufcount *= 2;
	  bufsize *= 2;
	}

      ip->address = ins.address;
      ip->opcodelen = ins.opcodelen;
      ip->label = FALSE;	/* no labels during this pass */
    }
    
  debug("Table usage is %d kB\n", *inscount * sizeof(instrentry) / 1024);
  debug("Instruction count is %d\n", *inscount);
}

/*
 *  Check if operand is referring to data that could be represented
 *  by a label instead of an address. If so, set the corresponding
 *  instruction's label flag.
 */
void set_label(instrentry *instab, int inscount, oper *o, unsigned address)
{
  instrentry *ip = 0;
  instrentry *iprel;
  int min = 0, max = inscount - 1, pivot;
  
  if (o->immtype != IMM_NONE)
    {
      if (accessbyte(o->imm))	/* within reach at all? */
	{
	  do			/* binary search for instruction */
	    {
	      pivot = (min + max) / 2;
	      ip = instab + pivot;

	      if (ip->address > (unsigned) o->imm)
		max = pivot - 1;
	      else if (ip->address + ip->opcodelen <= (unsigned) o->imm)
		min = pivot + 1;
	      else
		break;

	    } while (min <= max);
	
	  iprel = ip;
	}
      else
	{
	  if (instab->address >= (unsigned) o->imm)
	    iprel = instab;
	  else
	    iprel = instab + inscount - 1;
	}

      if (((o->immtype & IMM_ANY)
	   && ((o->immsize == SIZE_L)
	       || (o->immsize == SIZE_W && (decodeflags & DECODE_16BIT_REL))))
	  || (o->immtype & IMM_REL))
	{
	  if (ip) /* within bounds? */
	    {
	      debug("Label set from %x at %x\n", address, ip->address);
	      ip->label = TRUE; /* yep */
	      o->immlabeladdr = ip->address;
	      o->immtype &= ~(IMM_ANY | IMM_ABS);
	      o->immtype |= IMM_REL;
	      return; /* time is of the essence... */
	    }
	  else if (o->immtype & IMM_REL)
	    {
	      debug("Label set from %x at %x\n", address, iprel->address);
	      iprel->label = TRUE; /* yep */
	      o->immlabeladdr = iprel->address;
	      o->immtype &= ~(IMM_ANY | IMM_ABS);
	      o->immtype |= IMM_REL;
	    }
	  else
	    o->immtype = IMM_ABS;
	}
      else
	o->immtype = IMM_ABS;
    }
}

/* Set labels for any relative operands of the instruction */
void set_labels(instrentry *instab, int inscount, instr *ins)
{
  set_label(instab, inscount, &ins->oper1, ins->address);
  set_label(instab, inscount, &ins->oper2, ins->address);
  set_label(instab, inscount, &ins->oper3, ins->address);
}

/* Remove any relativity flags */
void kill_labels(instr *ins)
{
  if (ins->oper1.immtype != IMM_NONE)
    ins->oper1.immtype = IMM_ABS;
  if (ins->oper2.immtype != IMM_NONE)
    ins->oper2.immtype = IMM_ABS;
  if (ins->oper3.immtype != IMM_NONE)
    ins->oper3.immtype = IMM_ABS;
}

/*
 *  Pass 2 -- Set labels in instruction length table.
 */
void pass2(instrentry *instab, int inscount)
{
  unsigned pc;
  instr ins;
    
  debug("Pass 2...\n");
  if (options.labels)		/* only necessary if labels wanted in output */
    for (pc = prog.start; pc < prog.end; pc += ins.opcodelen)
      {
	if (!accessword(pc))
	  break;
	decode_instr(&ins, pc);
	    
	set_labels(instab, inscount, &ins);
      }
}

/*
 *  Pass 3 -- Print result.
 */
void pass3(instrentry *instab, int inscount)
{
  instr ins;
  instrentry *ip;
  int i, j;
  int len, len0;
  time_t zatime;
  unsigned oldiaddress;

  debug("Pass 3...\n");

  time(&zatime);
  printf("; Disassembly of %s [offset %s%x, length %s%x]\n", options.fname, (options.start >= 0 && options.start <= 9) ? "" : "$", options.start, (options.length >= 0 && options.length <= 9) ? "" : "$", options.length);
  printf("; %s", ctime(&zatime));
  printf("; Address range %s%x to %s%x\n\n", (prog.start >= 0 && prog.start <= 9) ? "" : "$", prog.start, (prog.end >= 0 && prog.end <= 9) ? "" : "$", prog.end);
    
  oldiaddress = prog.start;

  if (options.format != FORMAT_LISTING)
    {
      print_pad(0, 16);
      printf("org\t%s%x\n\n", (prog.start >= 0 && prog.start <= 9) ? "" : "$", prog.start);
    }

  for (ip = instab; ip < instab + inscount; ip++)
    {
      if (!accessword(ip->address))
	break;
      decode_instr(&ins, ip->address);
      if (options.labels)
	set_labels(instab, inscount, &ins);
      else
	kill_labels(&ins);

      if (options.format == FORMAT_COMBINED &&
	  (oldiaddress & ~0x1f) != (ip->address & ~0x1f))
	printf("; ------------------------ %s%x ------------------------\n", (ip->address >= 0 && ip->address <= 9) ? "" : "$", ip->address);

      if (options.labels && ip->label) /* label wanted here? */
	printf("l%x:\n", ip->address);

      len = 0;
	
      switch (options.format)
	{
	case FORMAT_LISTING:
	  printf("%08x", ins.address);
	  len += 8;

	  len = print_pad(len, 16);

	  len0 = len;

	  for (i = 0; i < ins.opcodelen; i++)
	    {
	      if (!getubyte(&j, ins.address + i))
		assert(0);
	      printf("%02x", j);
	      len += 2;
	    }

	  len = print_pad(len, len0 + 16);

	  break;
	case FORMAT_SOURCE:
	case FORMAT_COMBINED:
	  len = print_pad(len, 16);
	  break;
	default:
	  assert(0);
	}

      len = print_instr(&ins, len);

      if (options.format == FORMAT_COMBINED)
	{
	  len = print_pad(len, 48);
	  printf("; ");
	  len += 2;
	  for (i = 0; i < ins.opcodelen; i++)
	    {
	      if (!getubyte(&j, ins.address + i))
		assert(0);
	      printf("%02x", j);
	      len += 2;
	    }
	}

      oldiaddress = ip->address;

      printf("\n");
    }
  if (options.format != FORMAT_LISTING)
    printf("\tend\n");
}

void disasm(void)
{
  instrentry *instab;
  int inscount;
    
  pass1(&instab, &inscount);
  pass2(instab, inscount);
  pass3(instab, inscount);
  free(instab);
}

/*
 *  Globals for mygetopt().
 */
char *optarg;			/* argument associated with option */
int optind = 1;			/* index into parent argv vector */
int optopt;			/* character checked for validity */

/*
 *  getopt() clone -- extract command line options.
 */
int mygetopt(int argc, char **argv, char *optstr)
{
  static char *pos = "";
  char *substr;
    
  if (!*pos)			/* update scanning pointer */
    {
      if (optind >= argc)	/* no more command line arguments */
	return -1;
      pos = argv[optind];	/* pick new argument */
      if (*pos++ != '-')	/* end of options */
	return -1;
      if (*pos == '-')	/* premature end of options */
	{
	  optind++;
	  return -1;
	}
    }
    
  optopt = *pos++;		/* get option letter */
	
  if (optopt == ':'
      || (substr = strchr(optstr, optopt)) == 0) /* bad option letter */
    {
      if (!*pos)
	optind++;
      fprintf(stderr, "%s: illegal option -- %c\n", argv[0], optopt);
      return (optopt = '?');
    }

  if (*++substr != ':')	/* do not need an argument */
    {
      optarg = 0;
      if (!*pos)
	optind++;
    }
  else			/* need an argument */
    {
      if (*pos)		/* no white space */
	optarg = pos;
      else if (argc <= ++optind) /* no argument supplied */
	{
	  fprintf(stderr, "%s: option requires an argument -- %c\n",
		  argv[0], optopt);
	  pos = "";
	  return (optopt = '?');
	}
      else			/* white space */
	optarg = argv[optind];
      pos = "";
      optind++;
    }
	
  return optopt;		/* dump back option letter */
}

void version(void)
{
  printf("%s version 0.1.2 (Feb 06, 2001)\n", programname);
  printf("Disassembler for Hitachi SH-4 binaries\n");
}

void usage(void)
{
  version();
  printf("\n");
  printf("Usage: %s [options] <file>\n", programname);
  printf("\n");
  printf("  -f <l,s,c>\tOutput format (l=listing, s=source, c=combined)"
	 " (default %c)\n", options.format);
  printf("  -t <delta>\tTab-stop delta (default %d)\n", options.tabsize);
  printf("  -e <l,b>\tEndian (l=little, b=big)"
	 " (default %c)\n", options.endian);
  printf("  -o <address>\tOrg -- base address of disassembled code\n");
  printf("  -s <offset>\tStarting offset of chunk in bytes\n");
  printf("  -l <length>\tLength of chunk in bytes\n");
  printf("\n");
  printf("  -h\t\tPrint this message and exit\n");
  printf("  -v\t\tPrint version number of %s and exit\n", programname);
  printf("  -d\t\tPrint additional debugging output\n");
}

/*
 *  Parse command line options and return filename given on command line.
 */
char *parseopts(int argc, char **argv)
{
  char *end;
    
  while (mygetopt(argc, argv, "c:de:t:f:hl:o:s:v") != -1)
    {
      switch (optopt)
	{
	case 'd':		/* set debug flag */
	  options.debug = TRUE;
	  break;
	case 'e':		/* select endianness */
	  if (*optarg == ENDIAN_LITTLE && strlen(optarg) == 1)
	    {
	      options.endian = ENDIAN_LITTLE;
	    }
	  else if (*optarg == ENDIAN_BIG && strlen(optarg) == 1)
	    {
	      options.endian = ENDIAN_BIG;
	    }
	  else
	    error("Unknown endian: %s\n", optarg);
	  break;
	case 't':               /* Select tab-stop delta */
	  options.tabsize = strtoul(optarg, &end, 0);
	  if (options.tabsize <= 0)
	    error("Invalid tab-stop delta: %s\n", optarg);
	  break;
	case 'f':		/* select output format */
	  if (*optarg == FORMAT_LISTING && strlen(optarg) == 1)
	    {
	      options.format = FORMAT_LISTING;
	      options.labels = FALSE;
	    }
	  else if (*optarg == FORMAT_SOURCE && strlen(optarg) == 1)
	    {
	      options.format = FORMAT_SOURCE;
	      options.labels = TRUE;
	    }
	  else if (*optarg == FORMAT_COMBINED && strlen(optarg) == 1)
	    {
	      options.format = FORMAT_COMBINED;
	      options.labels = TRUE;
	    }
	  else
	    error("Unknown output format: %s\n", optarg);
	  break;
	case 'h':		/* display help information and exit */
	  usage();
	  exit(1);
	case 'l':		/* set length of chunk */
	  options.length = strtoul(optarg, &end, 0);
	  if (end != optarg + strlen(optarg))
	    error("Invalid length of chunk: %s\n", optarg);
	  break;
	case 'o':		/* set base address */
	  options.org = strtoul(optarg, &end, 0);
	  if (end != optarg + strlen(optarg))
	    error("Invalid base address: %s\n", optarg);
	  break;
	case 's':		/* set starting offset of chunk */
	  options.start = strtoul(optarg, &end, 0);
	  if (end != optarg + strlen(optarg))
	    error("Invalid starting offset of chunk: %s\n", optarg);
	  break;
	case 'v':		/* display version information and exit */
	  version();
	  exit(1);
	case '?':		/* error message by mygetopt() */
	  error("Try '%s -h' for more information\n", programname);
	default:		/* mygetopt() brain damage */
	  error("mygetopt() failure\n");
	}
    }
    
  debug("format\t%c\n", options.format);
  debug("org\t0x%x\n", options.org);
  debug("start\t0x%x\n", options.start);
  debug("length\t0x%x\n", options.length);

  if (optind != argc - 1)	/* need exactly one filename */
    {
      usage();
      exit(1);
    }

  return argv[optind];	/* return filename */
}

/*
 *  Read chunk of file into memory.
 */
void readfile(char *fname)
{
  FILE *f;
  unsigned flength;
  unsigned char *data;
    
  if (!(f = fopen(fname, "rb"))) /* open file */
    error("%s: %s\n", fname, strerror(errno));
  if (fseek(f, 0, SEEK_END))	/* go to end of file */
    {
      fclose(f);
      error("%s: %s\n", fname, strerror(errno));
    }
  if ((flength = ftell(f)) == (unsigned) -1) /* get file size */
    {
      fclose(f);
      error("%s: %s\n", fname, strerror(errno));
    }

  debug("fname\t%s\n", fname);
  debug("flength\t%u\n", flength);

  if (options.start >= flength) /* sanity check */
    {
      fclose(f);
      error("Given starting offset is larger than size of file: %u >= %u\n",
	    options.start, flength);
    }
  if (options.start + options.length >= flength) /* sanity check */
    {
      fclose(f);
      error("Given starting offset and length are larger than or equal"
	    " to size of file: %u + %u >= %u\n",
	    options.start, options.length, flength);
    }
  if (options.length == 0)	/* adjust length if none given */
    options.length = flength - options.start;

  if (!(data = (unsigned char *) malloc(options.length)))
    {
      fclose(f);
      error("Out of memory\n");
    }
  if (fseek(f, options.start, SEEK_SET) /* read from beginning of chunk */
      || fread(data, 1, options.length, f) != options.length)
    {
      fclose(f);
      error("%s: %s\n", fname, strerror(errno));
    }
  fclose(f);

  prog.start = options.org;	/* update globals accordingly */
  prog.end = prog.start + options.length;
  prog.bin = data;
}

int main(int argc, char **argv)
{
  char *fname;
    
  fname = parseopts(argc, argv);
  options.fname = fname;
  readfile(fname);
  disasm();
  free(prog.bin);
  return 0;
}
