/*  $Id: d68k.c,v 1.2 1999/01/13 13:26:09 kalms Exp $
 *
 *  D68K version 0.1 (Dec 17, 1998) by Mikael Kalms <mikael@kalms.org>
 *   and Martin Kalms <martin@kalms.org>.
 *
 *  This is a disassembler for Motorola M680x0 binaries.
 *
 * File:   d68k.c
 * Author: Mikael Kalms <mikael@kalms.org>, Martin Kalms <martin@kalms.org>
 * Date:   17 Dec 1998
 * Title: Disassembler for Motorola M680x0 binaries (C)
 */

#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 { CPU_68000, CPU_68010, CPU_68020, CPU_68030, CPU_68040, CPU_68060 = 6 };

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

enum { OPER_NONE, OPER_RN, OPER_IND_RN, OPER_RN_INC, OPER_DEC_RN, OPER_D16_RN,
       OPER_D8_RN_RN, OPER_ADDR, OPER_IMM, OPER_IIMM, OPER_REGLIST };

enum { REG_DN = 0, REG_AN = 8, REG_PC = 16, REG_SR, REG_CCR, REG_USP };

enum { SIZE_NONE, SIZE_B, SIZE_W, SIZE_L };

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_ADD,     MNEM_ADDA,    MNEM_ADDI,    MNEM_AND,
       MNEM_ANDI,    MNEM_CMP,     MNEM_CMPA,    MNEM_CMPI,    MNEM_CMPM,
       MNEM_EOR,     MNEM_EORI,    MNEM_OR,      MNEM_ORI,     MNEM_SUB,
       MNEM_SUBA,    MNEM_SUBI,    MNEM_ADDQ,    MNEM_SUBQ,    MNEM_ADDX,
       MNEM_SUBX,    MNEM_NOT,     MNEM_NEG,     MNEM_NEGX,    MNEM_ABCD,
       MNEM_SBCD,    MNEM_NBCD,    MNEM_DIVU,    MNEM_DIVS,    MNEM_MULU,
       MNEM_MULS,    MNEM_MOVE,    MNEM_MOVEA,   MNEM_MOVEP,   MNEM_MOVEQ,
       MNEM_MOVEM,   MNEM_CLR,     MNEM_LEA,     MNEM_PEA,     MNEM_EXG,
       MNEM_BTST,    MNEM_BCHG,    MNEM_BCLR,    MNEM_BSET,    MNEM_BRA,
       MNEM_BSR,     MNEM_BHI,     MNEM_BLO,     MNEM_BCC,     MNEM_BCS,
       MNEM_BNE,     MNEM_BEQ,     MNEM_BVC,     MNEM_BVS,     MNEM_BPL,
       MNEM_BMI,     MNEM_BGE,     MNEM_BLT,     MNEM_BGT,     MNEM_BLE,
       MNEM_DBT,     MNEM_DBF,     MNEM_DBHI,    MNEM_DBLO,    MNEM_DBCC,
       MNEM_DBCS,    MNEM_DBNE,    MNEM_DBEQ,    MNEM_DBVC,    MNEM_DBVS,
       MNEM_DBPL,    MNEM_DBMI,    MNEM_DBGE,    MNEM_DBLT,    MNEM_DBGT,
       MNEM_DBLE,    MNEM_ST,      MNEM_SF,      MNEM_SHI,     MNEM_SLO,
       MNEM_SCC,     MNEM_SCS,     MNEM_SNE,     MNEM_SEQ,     MNEM_SVC,
       MNEM_SVS,     MNEM_SPL,     MNEM_SMI,     MNEM_SGE,     MNEM_SLT,
       MNEM_SGT,     MNEM_SLE,     MNEM_TRAPT,   MNEM_TRAPF,   MNEM_TRAPHI,
       MNEM_TRAPLO,  MNEM_TRAPCC,  MNEM_TRAPCS,  MNEM_TRAPNE,  MNEM_TRAPEQ,
       MNEM_TRAPVC,  MNEM_TRAPVS,  MNEM_TRAPPL,  MNEM_TRAPMI,  MNEM_TRAPGE,
       MNEM_TRAPLT,  MNEM_TRAPGT,  MNEM_TRAPLE,  MNEM_ASL,     MNEM_ASR,
       MNEM_LSL,     MNEM_LSR,     MNEM_ROXL,    MNEM_ROXR,    MNEM_ROL,
       MNEM_ROR,     MNEM_EXTB,    MNEM_EXT,     MNEM_SWAP,    MNEM_TST,
       MNEM_TAS,     MNEM_ILLEGAL, MNEM_JMP,     MNEM_JSR,     MNEM_TRAP,
       MNEM_LINK,    MNEM_UNLK,    MNEM_RESET,   MNEM_NOP,     MNEM_STOP,
       MNEM_RTE,     MNEM_RTS,     MNEM_TRAPV,   MNEM_RTR };

enum { CC_T, CC_F, CC_HI, CC_LS, CC_CC, CC_CS, CC_NE, CC_EQ,
       CC_VC, CC_VS, CC_PL, CC_MI, CC_GE, CC_LT, CC_GT, CC_LE };

enum { DECODE_16BIT_REL = 1 };

/*
 *  *sniff*
 */
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;
    uint16 reglist;
    uint8 type;
    uint8 immsize, immtype;
    uint8 reg1;
    uint8 reg2, reg2size, reg2scale;
} oper;

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

/*
 *  Used for creating a table with rows consisting
 *  of <address><instruction-length><label-flag> to
 *  to resolv 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 = "d68k";

const char *sizenames = " bwl";

const char *scalenames = "1248";

const char *regnames[] = {
    "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7",
    "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7",
    "pc", "sr", "ccr", "usp"
};

const char *mnemnames[] = {
    "dc",      "add",     "adda",    "addi",    "and",
    "andi",    "cmp",     "cmpa",    "cmpi",    "cmpm",
    "eor",     "eori",    "or",      "ori",     "sub",
    "suba",    "subi",    "addq",    "subq",    "addx",
    "subx",    "not",     "neg",     "negx",    "abcd",
    "sbcd",    "nbcd",    "divu",    "divs",    "mulu",
    "muls",    "move",    "movea",   "movep",   "moveq",
    "movem",   "clr",     "lea",     "pea",     "exg",
    "btst",    "bchg",    "bclr",    "bset",    "bra",
    "bsr",     "bhi",     "blo",     "bcc",     "bcs",
    "bne",     "beq",     "bvc",     "bvs",     "bpl",
    "bmi",     "bge",     "blt",     "bgt",     "ble",
    "dbt",     "dbf",     "dbhi",    "dblo",    "dbcc",
    "dbcs",    "dbne",    "dbeq",    "dbvc",    "dbvs",
    "dbpl",    "dbmi",    "dbge",    "dblt",    "dbgt",
    "dble",    "st",      "sf",      "shi",     "slo",
    "scc",     "scs",     "sne",     "seq",     "svc",
    "svs",     "spl",     "smi",     "sge",     "slt",
    "sgt",     "sle",     "trapt",   "trapf",   "traphi",
    "traplo",  "trapcc",  "trapcs",  "trapne",  "trapeq",
    "trapvc",  "trapvs",  "trappl",  "trapmi",  "trapge",
    "traplt",  "trapgt",  "traple",  "asl",     "asr",
    "lsl",     "lsr",     "roxl",    "roxr",    "rol",
    "ror",     "extb",    "ext",     "swap",    "tst",
    "tas",     "illegal", "jmp",     "jsr",     "trap",
    "link",    "unlk",    "reset",   "nop",     "stop",
    "rte",     "rts",     "trapv",   "rtr"
};

/*
 *  Foo.
 */
int decodeflags = DECODE_16BIT_REL;

/*
 *  User options modified by parseopts() during program startup.
 */
struct {
    int cpu;			/* cpu type */
    char format;		/* output format */
    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 */
} options = { CPU_68000, FORMAT_LISTING, 0, 0, 0, 0, FALSE, FALSE };

/*
 *  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;
    *w = (short) (prog.bin[offset] << 8) | prog.bin[offset + 1];
    return TRUE;
}

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

int getswordbyte(int *w, unsigned addr)
{
    int j;
    if (!getuword(&j, addr) || j >= 0x100)
	return FALSE;
    *w = (char) j;
    return TRUE;
}

int getuwordbyte(int *w, unsigned addr)
{
    int j;
    if (!getuword(&j, addr) || j >= 0x100)
	return FALSE;
    *w = j;
    return TRUE;
}

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

int decode_ea(int rm, instr *i, oper *o)
{
    rm &= 0x3f;
    switch (rm >> 3)
    {
    case 0:
	o->type = OPER_RN;
	o->reg1 = rm & 7;
	break;
    case 1:
	o->type = OPER_RN;
	o->reg1 = (rm & 7) + 8;
	break;
    case 2:
	o->type = OPER_IND_RN;
	o->reg1 = (rm & 7) + 8;
	break;
    case 3:
	o->type = OPER_RN_INC;
	o->reg1 = (rm & 7) + 8;
	break;
    case 4:
	o->type = OPER_DEC_RN;
	o->reg1 = (rm & 7) + 8;
	break;
    case 5:
	o->type = OPER_D16_RN;
	o->immsize = SIZE_W;
	o->immtype = IMM_ABS;
	o->reg1 = (rm & 7) + 8;
	break;
    case 6:
	o->type = OPER_D8_RN_RN;
	o->immsize = SIZE_B;
	o->immtype = IMM_ABS;
	o->reg1 = (rm & 7) + 8;
	break;
    case 7:
	switch (rm & 7)
	{
	case 0:
	    o->type = OPER_ADDR;
	    o->immsize = SIZE_W;
	    if (decodeflags & DECODE_16BIT_REL)
		o->immtype = IMM_ANY;
	    else
		o->immtype = IMM_ABS;
	    break;
	case 1:
	    o->type = OPER_ADDR;
	    o->immsize = SIZE_L;
	    o->immtype = IMM_ANY;
	    break;
	case 4:
	    o->type = OPER_IMM;
	    o->immsize = i->opersize;
	    if (o->immsize == SIZE_B
		|| (o->immsize == SIZE_W && !(decodeflags & DECODE_16BIT_REL)))
		o->immtype = IMM_ABS;
	    else
		o->immtype = IMM_ANY;
	    break;
	case 2:
	    o->type = OPER_D16_RN;
	    o->immsize = SIZE_W;
	    o->immtype = IMM_RELPC;
	    o->reg1 = REG_PC;
	    break;
	case 3:
	    o->type = OPER_D8_RN_RN;
	    o->immsize = SIZE_B;
	    o->immtype = IMM_RELPC;
	    o->reg1 = REG_PC;
	    break;
	default:
	    return FALSE;
	}
    }
    return TRUE;
}

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

void decode_ea_rn_inc(int r, instr *i, oper *o)
{
    o->type = OPER_RN_INC;
    o->reg1 = r;
}

void decode_ea_dec_rn(int r, instr *i, oper *o)
{
    o->type = OPER_DEC_RN;
    o->reg1 = r;
}

void decode_ea_d16_rn(int r, instr *i, oper *o)
{
    o->type = OPER_D16_RN;
    o->reg1 = r;
    o->immsize = SIZE_W;
    if (r == REG_PC)
	o->immtype = IMM_RELPC;
    else
	o->immtype = IMM_ABS;
}

void decode_ea_imm(instr *i, oper *o)
{
    o->type = OPER_IMM;
    o->immsize = i->opersize;
    if (o->immsize == SIZE_B
	|| (o->immsize == SIZE_W && !(decodeflags & DECODE_16BIT_REL)))
	o->immtype = IMM_ABS;
    else
	o->immtype = IMM_ANY;
}

int ea_extrasize(instr *i, oper *o)
{
    int sizes[] = { 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0 };
    int sizes2[] = { 0, 2, 2, 4 };
    if (o->type <= OPER_D8_RN_RN || o->type == OPER_REGLIST)
	return sizes[o->type];
    return sizes2[o->immsize];
}

int decode_ea_extra(unsigned addr, instr *i, oper *o)
{
    int j;

    if (o->type <= OPER_DEC_RN)
	return TRUE;

    switch (o->type)
    {
    case OPER_D16_RN:
	if (!getsword(&o->imm, addr))
	    return FALSE;
	break;
    case OPER_D8_RN_RN:
	if (!getsbyte(&o->imm, addr + 1) || !getuword(&j, addr))
	    return FALSE;
	o->reg2 = j >> 12;
	o->reg2size = (j & 0x800) ? SIZE_L : SIZE_W;
	if ((o->reg2scale = ((j &0x600) >> 9)) && options.cpu < CPU_68020)
	    return FALSE;
	break;
    case OPER_ADDR:
    case OPER_IMM:
    case OPER_IIMM:
	switch (o->immsize)
	{
	case SIZE_B:
	    if (!getswordbyte(&o->imm, addr))
		return FALSE;
	    break;
	case SIZE_W:
	    if (!getsword(&o->imm, addr))
		return FALSE;
	    break;
	case SIZE_L:
	    if (!getlong(&o->imm, addr))
		return FALSE;
	    break;
	}
	break;
    default:
	return FALSE;
    }

    if (o->immtype == IMM_RELPC)
	o->imm += i->address + 2;

    return TRUE;
}

/*
int validate_ea_stdsrc(instr *i, oper *o)
{
  if (o->type == OPER_RN
  && o->reg1 >= REG_AN
  && o->reg1 < REG_PC
  && i->opersize == SIZE_B)
    return FALSE;
  return TRUE;
}

int validate_ea_stddest(instr *i, oper *o)
{
  if ((o->type == OPER_RN
       && o->reg1 >= REG_AN
       && o->reg1 < REG_PC
       && i->opersize == SIZE_B)
      || o->type == OPER_IMM
      || o->type == OPER_IIMM
      || ((o->type == OPER_D16_RN
	   || o->type == OPER_D8_RN_RN)
	  && o->reg1 == REG_PC))
      return FALSE;
  return TRUE;
}
*/

int validate_ea_srcdest(instr *i, int srcforbidregs, int destforbidregs,
			int srcforbidoper, int destforbidoper)
{
    return (!(i->oper1.type == OPER_RN
	      && i->oper1.reg1 >= REG_AN
	      && i->oper1.reg1 < REG_PC
	      && i->opersize == SIZE_B)
	    && !(i->oper2.type == OPER_RN
		 && i->oper2.reg1 >= REG_AN
		 && i->oper2.reg1 < REG_PC
		 && i->opersize == SIZE_B)
	    && !(i->oper1.type == OPER_RN
		 && ((1 << i->oper1.reg1) & srcforbidregs))
	    && !(i->oper2.type == OPER_RN
		 && ((1 << i->oper2.reg1) & destforbidregs))
	    && !((1 << i->oper1.type) & srcforbidoper)
	    && !((1 << i->oper2.type) & destforbidoper));
}

int decode_stdalu(int opcode, instr *i, int eadest,
		  int srcforbidregs, int destforbidregs,
		  int srcforbidoper, int destforbidoper)
{
    if (!decode_ea(opcode & 0x3f, i, eadest ? &i->oper2 : &i->oper1))
	return FALSE;

    if (!decode_ea_extra(i->address + 2, i, &i->oper1)
	|| !decode_ea_extra(i->address + 2 + ea_extrasize(i, &i->oper1),
			    i, &i->oper2)
	|| !validate_ea_srcdest(i, srcforbidregs, destforbidregs,
				srcforbidoper, destforbidoper))
	return FALSE;

    i->opcodelen = 2 + ea_extrasize(i, &i->oper1) + ea_extrasize(i, &i->oper2);
    return TRUE;
}

int decode_addsub(int opcode, instr *i)
{
    int mnems[] = { MNEM_SUB, MNEM_SUBA, MNEM_ADD, MNEM_ADDA };

    i->mnemonic = mnems[((opcode & 0x4000) >> 13)
		       + ((opcode & 0xc0) == 0xc0 ? 1 : 0)];

    if ((opcode & 0xc0) != 0xc0)
    {
	i->opersize = ((opcode & 0xc0) >> 6) + 1;
	decode_ea_rn(REG_DN + ((opcode & 0xe00) >> 9), i,
		     (opcode & 0x100) ? &i->oper1 : &i->oper2);

	return decode_stdalu(opcode, i, opcode & 0x100, 0,
			     (opcode & 0x100) ? 0x1ff00 : 0x1ff00,
			     0, (1 << OPER_IMM) | (1 << OPER_IIMM));
    }
    else
    {
	i->opersize = ((opcode & 0x100) >> 8) + 2;
	decode_ea_rn(REG_AN + ((opcode & 0xe00) >> 9), i, &i->oper2);

	return decode_stdalu(opcode, i, 0, 0, 0x100ff,
			     0, (1 << OPER_IMM) | (1 << OPER_IIMM));
    }
}

int decode_addsubq(int opcode, instr *i)
{
    i->mnemonic = (opcode & 0x100) ? MNEM_SUBQ : MNEM_ADDQ;
    i->opersize = ((opcode & 0xc0) >> 6) + 1;

    decode_ea_imm(i, &i->oper1);

    i->oper1.immsize = SIZE_B;
    i->oper1.immtype = IMM_ABS;
    i->oper1.imm = (opcode & 0xe00) ? ((opcode & 0xe00) >> 9) : 8;

    if (!decode_ea(opcode, i, &i->oper2)
	|| !decode_ea_extra(i->address + 2, i, &i->oper2)
	|| !validate_ea_srcdest(i, 0, 0x10000, 0, 1 << OPER_IMM))
	return FALSE;
  
    i->opcodelen = 2 + ea_extrasize(i, &i->oper2);
    return TRUE;
}

int decode_addsubx(int opcode, instr *i)
{
    i->mnemonic = (opcode & 0x4000) ? MNEM_ADDX : MNEM_SUBX;
    i->opersize = ((opcode & 0xc0) >> 6) + 1;

    if (opcode & 8)
    {
	decode_ea_dec_rn(REG_AN + (opcode & 7), i, &i->oper1);
	decode_ea_dec_rn(REG_AN + ((opcode & 0xe00) >> 9), i, &i->oper2);
    }
    else
    {
	decode_ea_rn(REG_DN + (opcode & 7), i, &i->oper1);
	decode_ea_rn(REG_DN + ((opcode & 0xe00) >> 9), i, &i->oper2);
    }

    i->opcodelen = 2;
    return TRUE;
}

int decode_cmp(int opcode, instr *i)
{
    if ((opcode & 0xc0) != 0xc0)
    {
	i->opersize = ((opcode & 0xc0) >> 6) + 1;
      
	if ((opcode & 0x38) == 8)
	{
	    i->mnemonic = MNEM_CMPM;
	    decode_ea_rn_inc(REG_AN + (opcode & 7), i, &i->oper1);
	    decode_ea_rn_inc(REG_AN + ((opcode & 0xe00) >> 9), i, &i->oper2);
	    i->opcodelen = 2;
	    return TRUE;
	}
	else
	{
	    i->mnemonic = MNEM_CMP;
	    decode_ea_rn(REG_DN + ((opcode & 0xe00) >> 9), i, &i->oper2);
	  
	    return decode_stdalu(opcode, i, 0, 0, 0, 0,
				 (1 << OPER_IMM) | (1 << OPER_IIMM));
	}
    }
    else
    {
	i->mnemonic = MNEM_CMPA;
	i->opersize = ((opcode & 0x100) >> 8) + 2; 
	decode_ea_rn(REG_AN + ((opcode & 0xe00) >> 9), i, &i->oper2);

	return decode_stdalu(opcode, i, 0, 0, 0, 0,
			     (1 << OPER_IMM) | (1 << OPER_IIMM));
    }
}

int decode_andor(int opcode, instr *i)
{
    i->mnemonic = (opcode & 0x4000) ? MNEM_AND : MNEM_OR;
    i->opersize = ((opcode & 0xc0) >> 6) + 1;

    decode_ea_rn(REG_DN + ((opcode & 0xe00) >> 9), i,
		 (opcode & 0x100) ? &i->oper1 : &i->oper2);

    return decode_stdalu(opcode, i, opcode & 0x100, 0xff00,
			 (opcode & 0x100) ? 0xff00 : 0x1ff00, 0,
			 (1 << OPER_IMM) | (1 << OPER_IIMM));
}

int decode_eor(int opcode, instr *i)
{
    i->mnemonic = MNEM_EOR;
    i->opersize = ((opcode & 0xc0) >> 6) + 1;

    decode_ea_rn(REG_DN + ((opcode & 0xe00) >> 9), i, &i->oper1);

    return decode_stdalu(opcode, i, 1, 0, 0x1ff00, 0,
			 (1 << OPER_IMM) | (1 << OPER_IIMM));
}

int decode_bxxx(int opcode, instr *i)
{
    int mnems[] = { MNEM_BTST, MNEM_BCHG, MNEM_BCLR, MNEM_BSET };

    i->mnemonic = mnems[(opcode & 0xc0) >> 6];

    if (opcode & 0x100)
    {
	i->opersize = SIZE_L;
	decode_ea_rn(REG_DN + ((opcode & 0xe00) >> 9), i, &i->oper1);
    }
    else
    {
	i->opersize = SIZE_B;
	decode_ea_imm(i, &i->oper1);
	i->oper1.immsize = SIZE_B;
	i->oper1.immtype = IMM_ABS;
    }

    if (i->mnemonic == MNEM_BTST)
      {
	i->opersize = SIZE_B;
	if (!decode_stdalu(opcode, i, 1, 0, 0xff00, 0,
			     (i->oper1.type == OPER_RN) ? 0 : OPER_IMM))
	  return FALSE;
	i->opersize = (i->oper2.type == OPER_RN) ? SIZE_L : SIZE_B;
	return TRUE;
      }
    else
      {
	i->opersize = SIZE_B;
	if (!decode_stdalu(opcode, i, 1, 0, 0x1ff00, 0,
			   (1 << OPER_IMM) | (1 << OPER_IIMM)))
	  return FALSE;
	i->opersize = (i->oper2.type == OPER_RN) ? SIZE_L : SIZE_B;
	return TRUE;
      }
}

int decode_xxxi(int opcode, instr *i)
{
    int mnems[] = { MNEM_ORI, MNEM_ANDI, MNEM_SUBI, MNEM_ADDI, 0,
		    MNEM_EORI, MNEM_CMPI };

    if ((i->opersize = ((opcode & 0xc0) >> 6) + 1) == 4)
	return FALSE;

    i->mnemonic = mnems[(opcode & 0xe00) >> 9];

    decode_ea_imm(i, &i->oper1);
    
    if ((opcode & 0xbf) == 0x38
	&& (i->mnemonic == MNEM_ORI
	    || i->mnemonic == MNEM_ANDI
	    || i->mnemonic == MNEM_EORI))
    {
	decode_ea_rn(i->opersize == SIZE_W ? REG_SR : REG_CCR, i, &i->oper2);
	i->opcodelen = 4;
	return decode_ea_extra(i->address + 2, i, &i->oper1);
    }
    else
	return decode_stdalu(opcode & 0x3f, i, 1, 0,
			     i->mnemonic == MNEM_CMPI ? 0xff00 : 0x1ff00, 0,
			     (1 << OPER_IMM) | (1 << OPER_IIMM));
}

int decode_notnegx(int opcode, instr *i)
{
    int mnems[] = { MNEM_NEGX, MNEM_NEG, 0, MNEM_NOT };

    assert((opcode & 0xf900) == 0x4000
	   && (opcode & 0x600) != 0x200
	   && (opcode & 0xc0) != 0xc0);

    i->mnemonic = mnems[(opcode & 0x600) >> 9];
    i->opersize = ((opcode & 0xc0) >> 6) + 1;

    return decode_stdalu(opcode, i, 1, 0, 0x1ff00, 0,
			 (1 << OPER_IMM) | (1 << OPER_IIMM));
}

int decode_muldiv(int opcode, instr *i)
{
    int mnems[] = { MNEM_DIVU, MNEM_DIVS, MNEM_MULU, MNEM_MULS };

    i->mnemonic = mnems[((opcode & 0x4000) >> 13) + ((opcode & 0x100) >> 8)];
    i->opersize = SIZE_W;

    decode_ea_rn(REG_DN + ((opcode & 0xe00) >> 9), i, &i->oper2);

    return decode_stdalu(opcode, i, 0, 0, 0xff00, 0, 0);
}

int decode_asnbcd(int opcode, instr *i)
{
    assert((opcode & 0xb1f0) == 0x8100 || (opcode & 0xffc0) == 0x4800);

    i->opersize = SIZE_B;

    if ((opcode & 0xffc0) == 0x4800)
    {
	i->mnemonic = MNEM_NBCD;
	return decode_stdalu(opcode, i, 1, 0, 0x1ff00, 0,
			     (1 << OPER_IMM) | (1 << OPER_IIMM));
    }
    else
    {
	i->mnemonic = (opcode & 0x4000) ? MNEM_SBCD : MNEM_ABCD;
	decode_ea_rn(((opcode & 8) ? REG_AN : REG_DN)
		     + (opcode & 7), i, &i->oper1);
	decode_ea_rn(((opcode & 8) ? REG_AN : REG_DN)
		     + ((opcode & 0xe00) >> 9), i, &i->oper2);
	i->opcodelen = 2;
    }

    return TRUE;
}

int decode_tastst(int opcode, instr *i)
{
    assert((opcode & 0xff00) == 0x4a00);

    if ((opcode & 0xc0) == 0xc0)
    {
	i->mnemonic = MNEM_TAS;
	i->opersize = SIZE_B;
	return decode_stdalu(opcode & 0x3f, i, 1, 0, 0x1ff00, 0,
			     (1 << OPER_IMM) | (1 << OPER_IIMM));
    }
    else
    {
	i->mnemonic = MNEM_TST;
	i->opersize = ((opcode & 0xc0) >> 6) + 1;
	return decode_stdalu(opcode & 0x3f, i, 0,
			     options.cpu >= CPU_68020 ? 0 : 0x1ff00, 0,
			     options.cpu >= CPU_68020 ? 0 : OPER_IMM, 0);
    }

}

int decode_move(int opcode, instr *i)
{
    char sizes[3] = { SIZE_B, SIZE_L, SIZE_W };

    i->opersize = sizes[((opcode & 0x3000) >> 12) - 1];

    i->mnemonic = (opcode & 0x1c0) == 0x40 ? MNEM_MOVEA : MNEM_MOVE;

    return decode_ea(((opcode & 0xe00) >> 9) | ((opcode & 0x1c0) >> 3),
		     i, &i->oper2)
	&& decode_stdalu(opcode & 0x3f, i, 0, 0, 0x10000, 0,
			 (1 << OPER_IMM) | (1 << OPER_IIMM));
}

int decode_movep(int opcode, instr *i)
{
    i->mnemonic = MNEM_MOVEP;
    i->opersize = (opcode & 0x40) ? SIZE_L : SIZE_W;

    if (opcode & 0x80)
    {
	decode_ea_d16_rn(REG_AN + (opcode & 7), i, &i->oper1);
	decode_ea_rn(REG_DN + ((opcode & 0xe00) >> 9), i, &i->oper2);
    }
    else
    {
	decode_ea_rn(REG_DN + ((opcode & 0xe00) >> 9), i, &i->oper1);
	decode_ea_d16_rn(REG_AN + (opcode & 7), i, &i->oper2);
    }

    if (!decode_ea_extra(i->address + 2, i, &i->oper1)
	|| !decode_ea_extra(i->address + 2 + ea_extrasize(i, &i->oper1),
			    i, &i->oper2))
	return FALSE;

    i->opcodelen = 4;
    return TRUE;
}

int decode_moveq(int opcode, instr *i)
{
    i->mnemonic = MNEM_MOVEQ;
    i->opersize = SIZE_L;
    i->oper1.type = OPER_IMM;
    i->oper1.immsize = SIZE_L;
    i->oper1.immtype = IMM_ABS;
    i->oper1.imm = (char) (opcode & 0xff);
    decode_ea_rn(REG_DN + ((opcode & 0xe00) >> 9), i, &i->oper2);
    i->opcodelen = 2;
    return TRUE;
}

int decode_moveusp(int opcode, instr *i)
{
    assert((opcode & 0xfff0) == 0x4e60);
    i->mnemonic = MNEM_MOVE;
    i->opersize = SIZE_L;
    if (opcode & 8)
    {
	decode_ea_rn(REG_USP, i, &i->oper1);
	decode_ea_rn(REG_AN + (opcode & 7), i, &i->oper2);
    }
    else
    {
	decode_ea_rn(REG_AN + (opcode & 7), i, &i->oper1);
	decode_ea_rn(REG_USP, i, &i->oper2);
    }

    i->opcodelen = 2;
    return TRUE;
}

int decode_movesr(int opcode, instr *i)
{
    assert((opcode & 0xf9c0) == 0x40c0);

    i->mnemonic = MNEM_MOVE;
    i->opersize = ((opcode & 0x20) << 1) == (opcode & 0x40) ? SIZE_W : SIZE_B;

    decode_ea_rn(i->opersize == SIZE_W ? REG_SR : REG_CCR, i,
		 (opcode & 0x40) ? &i->oper2 : &i->oper1);

    return decode_stdalu(opcode, i, !(opcode & 0x40), 0xff00, 0x1ff00, 0,
			 (1 << OPER_IMM) | (1 << OPER_IIMM));
}

int decode_movem(int opcode, instr *i)
{
    int j, k;
    
    assert((opcode & 0xfb80) == 0x4880);

    i->mnemonic = MNEM_MOVEM;
    i->opersize = (opcode & 0x40) ? SIZE_L : SIZE_W;

    if (opcode & 0x400)
    {
	i->oper2.type = OPER_REGLIST;
	if (!decode_ea(opcode & 0x3f, i, &i->oper1)
	    || i->oper1.type == OPER_RN
	    || i->oper1.type == OPER_DEC_RN
	    || i->oper1.type == OPER_IMM
	    || ((i->oper1.type == OPER_D16_RN
		 || i->oper1.type == OPER_D8_RN_RN)
		&& i->oper1.reg1 == REG_PC)
	    || !decode_ea_extra(i->address + 2 + 2, i, &i->oper1)
	    || !getsword(&j, i->address + 2))
	    return FALSE;

	i->oper2.reglist = j;
    }
    else
    {
	i->oper1.type = OPER_REGLIST;
	if (!decode_ea(opcode & 0x3f, i, &i->oper2)
	    || i->oper2.type == OPER_RN
	    || i->oper2.type == OPER_RN_INC
	    || i->oper2.type == OPER_IMM
	    || ((i->oper2.type == OPER_D16_RN
		 || i->oper2.type == OPER_D8_RN_RN)
		&& i->oper2.reg1 == REG_PC)
	    || !decode_ea_extra(i->address + 2 + 2, i, &i->oper2)
	    || !getsword(&j, i->address + 2))
	    return FALSE;
	if (i->oper2.type == OPER_DEC_RN)
	{
	    i->oper1.reglist = 0;
	    for (k = 0; k < 16; k++)
		i->oper1.reglist |= ((j >> (15 - k)) & 1) << k;
	}
    }

    i->opcodelen = 4 + ea_extrasize(i, &i->oper1) + ea_extrasize(i, &i->oper2);
    return TRUE;
}

int decode_exg(int opcode, instr *i)
{
    i->mnemonic = MNEM_EXG;
    i->opersize = SIZE_L;
    switch (opcode & 0x1f8)
    {
    case 0x140:
	decode_ea_rn(REG_DN + ((opcode & 0xe00) >> 9), i, &i->oper1);
	decode_ea_rn(REG_DN + (opcode & 7), i, &i->oper2);
	break;
    case 0x148:
	decode_ea_rn(REG_AN + ((opcode & 0xe00) >> 9), i, &i->oper1);
	decode_ea_rn(REG_AN + (opcode & 7), i, &i->oper2);
	break;
    case 0x188:
	decode_ea_rn(REG_DN + ((opcode & 0xe00) >> 9), i, &i->oper1);
	decode_ea_rn(REG_AN + (opcode & 7), i, &i->oper2);
	break;
    default: 
	return FALSE;
    }

    i->opcodelen = 2;
    return TRUE;
}

int decode_extswap(int opcode, instr *i)
{
    assert((opcode & 0xfff8) == 0x49c0
	   || (opcode & 0xfff8) == 0x4880
	   || (opcode & 0xfff8) == 0x48c0
	   || (opcode & 0xfff8) == 0x4840);

    switch (opcode & 0xfff8)
    {
    case 0x49c0:
	if (options.cpu < CPU_68020)
	    return FALSE;
	i->mnemonic = MNEM_EXTB;
	i->opersize = SIZE_L;
	decode_ea_rn(REG_DN + (opcode & 7), i, &i->oper1);
	break;
    case 0x4880:
    case 0x48C0:
	i->mnemonic = MNEM_EXT;
	i->opersize = (opcode & 0x40) ? SIZE_L : SIZE_W;
	decode_ea_rn(REG_DN + (opcode & 7), i, &i->oper1);
	break;
    case 0x4840:
	i->mnemonic = MNEM_SWAP;
	i->opersize = SIZE_NONE;
	decode_ea_rn(REG_DN + (opcode & 7), i, &i->oper1);
	break;
    }

    i->opcodelen = 2;
    return TRUE;
}

int decode_jmpjsr(int opcode, instr *i)
{
    assert((opcode & 0xff80) == 0x4e80);

    i->mnemonic = (opcode & 0x40) ? MNEM_JMP : MNEM_JSR;
    i->opersize = SIZE_L;
    return (decode_stdalu(opcode, i, 0, 0xff00, 0,
			  (1 << OPER_RN) | (1 << OPER_RN_INC)
			  | (1 << OPER_DEC_RN) | (1 << OPER_IMM), 0)
	    && (i->oper1.type != OPER_ADDR || accessword(i->oper1.imm)));
}

int decode_bcc(int opcode, instr *i)
{
    int j;
    
    i->mnemonic = MNEM_BRA + ((opcode & 0xf00) >> 8);
    i->opersize = (opcode & 0xff) ? SIZE_B : SIZE_W;
    i->oper1.type = OPER_IIMM;
    i->oper1.immsize = i->opersize;
    i->oper1.immtype = IMM_RELPC;

    if (i->oper1.immsize == SIZE_B)
	j = (char) (opcode & 0xff);
    else
	if (!getsword(&j, i->address + 2))
	    return FALSE;

    i->oper1.imm = i->address + 2 + j;
    i->opcodelen = (i->oper1.immsize == SIZE_B) ? 2 : 4;
    return accessword(i->oper1.imm);
}

int decode_dbcc(int opcode, instr *i)
{
    i->mnemonic = MNEM_DBT + ((opcode & 0xf00) >> 8);
    i->opersize = SIZE_W;

    decode_ea_rn(REG_DN + (opcode & 7), i, &i->oper1);

    i->oper2.type = OPER_IIMM;
    i->oper2.immsize = SIZE_W;
    i->oper2.immtype = IMM_RELPC;

    if (!getsword(&i->oper2.imm, i->address + 2))
	return FALSE;

    i->opcodelen = 4;
    i->oper2.imm += i->address + 2;

    if (!accessword(i->oper2.imm))
      return FALSE;
    return TRUE;
}

int decode_scc(int opcode, instr *i)
{
    i->mnemonic = MNEM_ST + ((opcode & 0xf00) >> 8);
    i->opersize = SIZE_B;

    return decode_stdalu(opcode, i, 1, 0, 0xff00, 0,
			 (1 << OPER_IMM) | (1 << OPER_IIMM));
}

int decode_trapcc(int opcode, instr *i)
{
    i->mnemonic = MNEM_TRAPT + ((opcode & 0xf00) >> 8);
    i->opersize =
	(opcode & 7) == 2 ? SIZE_W : (opcode & 7) == 3 ? SIZE_L : SIZE_NONE;
    if (i->opersize)
	decode_ea_imm(i, &i->oper1);

    if (!decode_ea_extra(i->address + 2, i, &i->oper1))
	return FALSE;

    i->opcodelen = 2 + ea_extrasize(i, &i->oper1);
    return TRUE;
}

int decode_shiftrot(int opcode, instr *i)
{
    int mnems[] = { MNEM_ASL, MNEM_ASR, MNEM_LSL, MNEM_LSR,
		    MNEM_ROXL, MNEM_ROXR, MNEM_ROL, MNEM_ROR };

    i->mnemonic = mnems[((opcode & 0x18) >> 2) + 1 - ((opcode & 0x100) >> 8)];

    if ((i->opersize = ((opcode & 0xc0) >> 6) + 1) != 4)
    {
	i->oper1.type = (opcode & 0x20) ? OPER_RN : OPER_IMM;
	if (i->oper1.type == OPER_RN)
	    i->oper1.reg1 = REG_DN + ((opcode & 0xe00) >> 9);
	else
	{
	    i->oper1.immsize = SIZE_B;
	    i->oper1.immtype = IMM_ABS;
	    i->oper1.imm = (opcode & 0xe00) >> 9;
	    if (!i->oper1.imm)
		i->oper1.imm = 8;
	}
	i->oper2.type = OPER_RN;
	i->oper2.reg1 = REG_DN + (opcode & 7);
	i->opcodelen = 2;
    }
    else
    {
	i->opersize = SIZE_W;
	i->oper1.type = OPER_IMM;
	i->oper1.immsize = SIZE_B;
	i->oper1.immtype = IMM_ABS;
	i->oper1.imm = 1;

	if (!decode_ea(opcode, i, &i->oper2)
	    || !decode_ea_extra(i->address + 2, i, &i->oper2)
	    || !validate_ea_srcdest(i, 0, 0x1ffff, 0, 1 << OPER_IMM))
	    return FALSE;
      
	i->opcodelen = 2 + ea_extrasize(i, &i->oper2);
    }

    return TRUE;
}

int decode_clr(int opcode, instr *i)
{
    i->mnemonic = MNEM_CLR;
    if ((i->opersize = ((opcode & 0xc0) >> 6) + 1) == 4)
	return FALSE;

    return decode_stdalu(opcode, i, 1, 0, 0x1ff00, 0,
			 (1 << OPER_IMM) | (1 << OPER_IIMM));
}

int decode_lea(int opcode, instr *i)
{
    i->mnemonic = MNEM_LEA;
    i->opersize = SIZE_L;
    decode_ea_rn(REG_AN + ((opcode & 0xe00) >> 9), i, &i->oper2);

    return decode_stdalu(opcode, i, 0, 0xffff, 0,
			 (1 << OPER_RN_INC) | (1 << OPER_DEC_RN) | (OPER_IMM),
			 0);
}

int decode_pea(int opcode, instr *i)
{
    assert((opcode & 0xffc0) == 0x4840);
    i->mnemonic = MNEM_PEA;
    i->opersize = SIZE_L;
    return decode_stdalu(opcode, i, 0, 0xffff, 0,
			 (1 << OPER_RN_INC) | (1 << OPER_DEC_RN) | (OPER_IMM),
			 0);
}


int decode_link(int opcode, instr *i)
{
    assert((opcode & 0xfff0) == 0x4e50);
    i->mnemonic = (opcode & 8) ? MNEM_UNLK : MNEM_LINK;
    i->opersize = SIZE_W;
    decode_ea_rn(REG_AN + (opcode & 7), i, &i->oper1);
    decode_ea_imm(i, &i->oper2);
    if (!decode_ea_extra(i->address + 2, i, &i->oper2))
	return FALSE;

    i->opcodelen = 2 + ea_extrasize(i, &i->oper2);
    return TRUE;
}

int decode_trap(int opcode, instr *i)
{
    assert((opcode & 0xfff0) == 0x4e40);

    i->mnemonic = MNEM_TRAP;
    i->opersize = SIZE_L;
    i->oper1.type = OPER_IMM;
    i->oper1.immsize = SIZE_B;
    i->oper1.immtype = IMM_ABS;
    i->oper1.imm = opcode & 0xf;

    i->opcodelen = 2;
    return TRUE;
}

int decode_uniq(int opcode, instr *i)
{
    int mnems[] = { MNEM_RESET, MNEM_NOP, MNEM_STOP, MNEM_RTE, 0, MNEM_RTS,
		    MNEM_TRAPV, MNEM_RTR };

    assert(opcode == 0x4afc || (opcode >= 0x4e70 && opcode <= 0x4e77));

    i->opersize = SIZE_NONE;

    if (opcode == 0x4afc)
	i->mnemonic = MNEM_ILLEGAL;
    else if (opcode >= 0x4e70 && opcode <= 0x4e77 && opcode != 0x4e74)
    {
	i->mnemonic = mnems[opcode - 0x4e70];

	if (i->mnemonic == MNEM_STOP)
	{
	    i->opersize = SIZE_W;
	    decode_ea_imm(i, &i->oper1);
	    i->oper1.immtype = IMM_ABS;
	    if (!decode_ea_extra(i->address + 2, i, &i->oper1))
		return FALSE;
	}
    }
    else
	return FALSE;

    i->opcodelen = 2 + ea_extrasize(i, &i->oper1);

    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 opcodetab[] = {
    { 0xf138, 0x0108,   decode_movep }, /* bxxx */
    { 0xf100, 0x0100, decode_bxxx },
    { 0xff00, 0x0800,   decode_bxxx }, /* xxxi */
    { 0xff00, 0x0e00,   0 },	/* xxxi */
    { 0xf100, 0x0000, decode_xxxi },
    { 0xf000, 0x1000, decode_move },
    { 0xf000, 0x2000, decode_move },
    { 0xf000, 0x3000, decode_move },
    { 0xfff8, 0x49c0, decode_extswap },
    { 0xf1c0, 0x41c0, decode_lea },
    { 0xf9c0, 0x40c0, decode_movesr },
    { 0xff00, 0x4200, decode_clr },
    { 0xf900, 0x4000, decode_notnegx },
    { 0xffc0, 0x4800, decode_asnbcd },
    { 0xfff8, 0x4840, decode_extswap },
    { 0xffc0, 0x4840, decode_pea },
    { 0xffb8, 0x4880, decode_extswap },
    { 0xfb80, 0x4880, decode_movem },
    { 0xffff, 0x4afc, decode_uniq },
    { 0xff00, 0x4a00, decode_tastst },
    { 0xff80, 0x4e80, decode_jmpjsr },
    { 0xfff0, 0x4e40, decode_trap },
    { 0xfff0, 0x4e50, decode_link },
    { 0xfff0, 0x4e60, decode_moveusp },
    { 0xfff8, 0x4e70, decode_uniq },
    { 0xf0f8, 0x50c8,     decode_dbcc }, /* scc */
    { 0xf0fe, 0x50fa,     decode_trapcc }, /* scc */
    { 0xf0ff, 0x50fc,     decode_trapcc }, /* scc */
    { 0xf0c0, 0x50c0,   decode_scc }, /* addsubq */
    { 0xf000, 0x5000, decode_addsubq },
    { 0xf000, 0x6000, decode_bcc },
    { 0xf100, 0x7000, decode_moveq },
    { 0xf100, 0x7100, 0 },
    { 0xf0c0, 0x80c0,   decode_muldiv }, /* andor */
    { 0xf1f0, 0x8100,   decode_asnbcd }, /* andor */
    { 0xf000, 0x8000, decode_andor },
    { 0xf0c0, 0x90c0,   decode_addsub }, /* addsub */
    { 0xf130, 0x9100,   decode_addsubx }, /* addsub */
    { 0xf000, 0x9000, decode_addsub },
    { 0xf000, 0xa000, 0 },
    { 0xf0c0, 0xb0c0,   decode_cmp }, /* cmp */
    { 0xf138, 0xb108,   decode_cmp }, /* cmp */
    { 0xf100, 0xb100,   decode_eor }, /* cmp */
    { 0xf000, 0xb000, decode_cmp },
    { 0xf0c0, 0xc0c0,   decode_muldiv }, /* andor */
    { 0xf1f0, 0xc100,   decode_asnbcd }, /* andor */
    { 0xf1f0, 0xc140,   decode_exg }, /* andor */
    { 0xf1f8, 0xc188,   decode_exg }, /* andor */
    { 0xf000, 0xc000, decode_andor },
    { 0xf0c0, 0xd0c0,   decode_addsub }, /* addsub */
    { 0xf130, 0xd100,   decode_addsubx }, /* addsub */
    { 0xf000, 0xd000, decode_addsub },
    { 0xf000, 0xe000, decode_shiftrot }
};

void print_oper(oper *o)
{
    int start = -1;
    int in = 0;
    int i, j;

    switch (o->type)
    {
    case OPER_RN:
	printf("%s", regnames[o->reg1]);
	break;
    case OPER_IND_RN:
	printf("(%s)", regnames[o->reg1]);
	break;
    case OPER_RN_INC: 
	printf("(%s)+", regnames[o->reg1]);
	break;
    case OPER_DEC_RN: 
	printf("-(%s)", regnames[o->reg1]);
	break;
    case OPER_D16_RN: 
	if (o->immtype & IMM_REL)
	{
	    printf("l%x", o->immlabeladdr);
	    j = (unsigned) o->imm - (unsigned) o->immlabeladdr;
	    if (o->immlabeladdr != o->imm)
	      printf("%c%s%x", (j >= 0 ? '+' : '-'), (abs(j) <= 9 ? "" : "$"), abs(j));
	}
	else
	  if (o->reg1 == REG_PC)
	    printf("%s%x", (o->imm >= 0 && o->imm <= 9) ? "" : "$", o->imm);
	  else
	    printf("%s%s%x", (o->imm >= 0 ? "" : "-"), (abs(o->imm) <= 9) ? "" : "$", abs(o->imm));
	printf("(%s)", regnames[o->reg1]);
	break;
    case OPER_D8_RN_RN: 
	if (o->immtype & IMM_REL)
	{
	  printf("l%x", o->immlabeladdr);
	  j = (unsigned) o->imm - (unsigned) o->immlabeladdr;
	  if (o->immlabeladdr != o->imm)
	    printf("%c%s%x", (j >= 0 ? '+' : '-'), (abs(j) <= 9 ? "" : "$"), abs(j));
	}
	else
	  if (o->reg1 == REG_PC)
	    printf("%s%x", (o->imm >= 0 && o->imm <= 9) ? "" : "$", o->imm);
	  else
	    printf("%s%s%x", (o->imm >= 0 ? "" : "-"), (abs(o->imm) <= 9) ? "" : "$", abs(o->imm));
	printf("(%s,%s.%c", regnames[o->reg1], regnames[o->reg2],
	       sizenames[o->reg2size]);
	if (o->reg2scale)
	    printf("*%c", scalenames[o->reg2scale]);
	printf(")");
	break;
    case OPER_ADDR:
	if (o->immtype & IMM_REL)
	{
	    printf("l%x%s", o->immlabeladdr, o->immsize == SIZE_W ? ".w" : "");
	  j = (unsigned) o->imm - (unsigned) o->immlabeladdr;
	  if (o->immlabeladdr != o->imm)
	    printf("%c%s%x", (j >= 0 ? '+' : '-'), (abs(j) <= 9 ? "" : "$"), abs(j));
	}
	else
	    printf("%s%x%s", (o->imm >= 0 && o->imm <= 9) ? "" : "$", o->imm, o->immsize == SIZE_W ? ".w" : "");
	break;
    case OPER_IMM:
	printf("#");
    case OPER_IIMM:
	if (o->immtype & IMM_REL)
	{
	    printf("l%x", o->immlabeladdr);
	  j = (unsigned) o->imm - (unsigned) o->immlabeladdr;
	  if (o->immlabeladdr != o->imm)
	    printf("%c%s%x", (j >= 0 ? '+' : '-'), (abs(j) <= 9 ? "" : "$"), abs(j));
	}
	else
	    printf("%s%x", (o->imm >= 0 && o->imm <= 9) ? "" : "$", o->imm);
	break;
    case OPER_REGLIST:
	for (i = 0; i < 17; i++)
	{
	    if (!in && ((o->reglist & (1 << i)) & 0xffff))
	    {
		if (start >= 0)
		    printf("/");
	      
		start = i;
		in = TRUE;
	    }
	  
	    if (in && !((o->reglist & (1 << i)) & 0xffff))
	    {
		printf("%c%d", (start & 8) ? 'a' : 'd', start & 7);
		if (i - 1 > start)
		    printf("-%c%d", ((i - 1) & 8) ? 'a' : 'd', (i - 1) & 7);
		in = FALSE;
	    }
	}
	break;
    }
}

void print_instr(instr *ins)
{
    printf("%s", mnemnames[ins->mnemonic]);
    if (ins->opersize)
	printf(".%c", sizenames[ins->opersize]);
    printf("\t");
    if (ins->oper1.type)
	print_oper(&ins->oper1);
    if (ins->oper1.type && ins->oper2.type)
	printf(",");
    if (ins->oper2.type)
	print_oper(&ins->oper2);
}

/*
 * 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 = 0xff;	/* magic -- should be changed later */
    i->mnemonic = 0xff;		/* magic -- should be changed later */
    i->opersize = 0xff;		/* magic -- should be changed later */
}

/*
 *  Perform limited sanity checking on an <instr> initialized by init_instr().
 */
void sanitycheck_instr(instr *i, unsigned pc)
{
    /* Iä! Shub-Niggurath! */

    assert(i->address == pc);
    assert(i->opcodelen != 0xff);
    assert(i->mnemonic != 0xff);
    assert(i->opersize != 0xff);
}

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

    if (!getuword(&opcode, pc))
	assert(0);
    init_instr(i, pc);
    for (p = opcodetab; p < opcodetab + numopcodes; p++)
	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 */
	}
    if (p == opcodetab + numopcodes) /* matching entry not found in table? */
	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;
    
    if (((o->immtype & IMM_ANY) && ((o->immsize == SIZE_L) || (o->immsize == SIZE_W && (decodeflags & DECODE_16BIT_REL)))) || (o->immtype & IMM_REL))
      {
	if (accessbyte(o->imm))	/* within reach at all? */
	    for (ip = instab + inscount - 1; ip >= instab; ip--) /* backward */
		if (ip->address <= (unsigned) o->imm) /* 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... */
		}
	if (o->immtype & IMM_REL)
	  {
	    debug("Label set from %x at %x\n", address, instab->address);
	    instab->label = TRUE; /* yep */
	    o->immlabeladdr = instab->address;
	    o->immtype &= ~(IMM_ANY | IMM_ABS);
	    o->immtype |= IMM_REL;
	  }
	else
	  o->immtype = IMM_ABS;
      }
    else
      o->immtype = IMM_ABS;
}

/* Remove any relativity flags */
void kill_label(oper *o)
{
  if (o->immtype != IMM_NONE)
    o->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_label(instab, inscount, &ins.oper1, ins.address);
	    set_label(instab, inscount, &ins.oper2, ins.address);
	}
}

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

    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);
    
    for (ip = instab; ip < instab + inscount; ip++)
    {
	if (!accessword(ip->address))
	    break;
	decode_instr(&ins, ip->address);
	if (options.labels)
	  {
	    set_label(instab, inscount, &ins.oper1, ins.address);
	    set_label(instab, inscount, &ins.oper2, ins.address);
	  }
	else
	  {
	    kill_label(&ins.oper1);
	    kill_label(&ins.oper2);
	  }

	if (options.labels && ip->label) /* label wanted here? */
	    printf("l%x:\n", ip->address);
	
	switch (options.format)
	{
	case FORMAT_LISTING:
	    printf("%08x\t", ins.address);
	    for (i = 0; i < ins.opcodelen; i++)
	    {
		if (!getubyte(&j, ins.address + i))
		    assert(0);
		printf("%02x", j);
	    }
	    for (j = ins.opcodelen; j < 7; j++)
	      printf("  ");
	    break;
	case FORMAT_SOURCE:
	    break;
	default:
	    assert(0);
	}
	printf("\t");
	print_instr(&ins);
	printf("\n");
    }
    if (options.format == FORMAT_SOURCE)
	printf("\tend\n");
}

/*
 *  Here comes the nice.
 */
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 */
}

/*
 *  In his house at R'lyeh dead Cthulhu waits dreaming.
 */
void version(void)
{
    printf("%s version 0.1 (Dec 17, 1998) by Mikael & Martin Kalms\n", programname);
    printf("Disassembler for Motorola M680x0 binaries\n");
}

void usage(void)
{
    version();
    printf("\n");
    printf("Usage: %s [options] <file>\n", programname);
    printf("\n");
    printf("  -c <0..4,6>\tSelect CPU type (%d=68000..%d=68060)"
	   " (default %d)\n", CPU_68000, CPU_68060, options.cpu);
    printf("  -f <l,s>\tOutput format (l=listing, s=source)"
	   " (default %c)\n", options.format);
    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)
{
    int i;
    char *end;
    
    while (mygetopt(argc, argv, "c:df:hl:o:s:v") != -1)
    {
	switch (optopt)
	{
	case 'c':		/* select cpu type */
	    i = strtol(optarg, &end, 0);
	    if (end != optarg + strlen(optarg))
		error("Unknown CPU type: %s\n", optarg);
	    switch (i)
	    {
	    case CPU_68000:
	    case CPU_68010:
	    case CPU_68020:
	    case CPU_68030:
	    case CPU_68040:
	    case CPU_68060:
		options.cpu = i;
		break;
	    default:
		error("Unknown CPU type: %s\n", optarg);
	    }
	    break;
	case 'd':		/* set debug flag */
	    options.debug = TRUE;
	    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
		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("cpu\t%d\n", options.cpu);
    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;
}

/*
 *  Green circles.
 */
int main(int argc, char **argv)
{
    char *fname;
    
    fname = parseopts(argc, argv);
    if (!strcmp(fname, "xyzzy"))
    {
	printf("Nothing happens\n");
	exit(1);
    }
    else if (!strcmp(fname, "cthulhu"))
    {
	printf("ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn\n");
	exit(1);
    }
    options.fname = fname;
    readfile(fname);
    disasm();
    free(prog.bin);
    return 0;
}
