/****************************************************************************
 *
 *      mcs48 disassembler
 *
 * This file is Copyright 1996 Michael Cuddy, Fen's Ende Sofware.
 * Redistribution is allowed in source and binary form as long as both
 * forms are distributed together with the file 'README'.  This copyright
 * notice must also accompany the files.
 *
 * This software should be considered a small token to all of the
 * emulator authors for thier dilligence in preserving our Arcade and
 * Computer history.
 *
 * Michael Cuddy, Fen's Ende Software.
 * 11/25/1996
 *
 * Adapted by Andrea Mazzoleni for use with MAME
 *
 * Modified by David Hovemeyer <daveho@cs.umd.edu> for standalone use
 *   Also fixed some bugs, adjusted the output format slightly,
 *   and made it less permissive (since we want it to be as accurate
 *   as possible)
 *
 ***************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>	/* DHH 1/23/03 */

/*#include "memory.h"*/	/* DHH 1/23/03 */

typedef unsigned char byte;

#define FMT(a,b) a, b
#define PTRS_PER_FORMAT 2

const char *Formats[] = {
	FMT("01101rrr", "add  a,%R"),
	FMT("0110000r", "add  a,@%R"),
	FMT("00000011dddddddd", "add a,#$%X"),
	FMT("01111rrr", "addc a,%R"),
	FMT("0111000r", "addc a,@%R"),
	FMT("00010011dddddddd", "addc a,#$%X"),
	FMT("01011rrr", "anl  a,%R"),
	FMT("0101000r", "anl  a,@%R"),
	FMT("01010011dddddddd", "anl  a,#$%X"),
//	FMT("10011000dddddddd", "anl  bus,#$%X"),
	FMT("10011001dddddddd", "anl  p1,#$%X"),
	FMT("10011010dddddddd", "anl  p2,#$%X"),
	FMT("100111pp", "anld %P,a"),
//	FMT("aaa10100aaaaaaaa", "call %A"),
	FMT("0aa10100aaaaaaaa", "call %A"),
	FMT("00100111", "clr  a"),
	FMT("10010111", "clr  c"),
	FMT("10100101", "clr  f1"),
	FMT("10000101", "clr  f0"),
	FMT("00110111", "cpl  a"),
	FMT("10100111", "cpl  c"),
	FMT("10010101", "cpl  f0"),
	FMT("10110101", "cpl  f1"),
	FMT("01010111", "da   a"),
	FMT("00000111", "dec  a"),
	FMT("11001rrr", "dec  %R"),
	FMT("00010101", "dis  i"),
	FMT("00110101", "dis  tcnti"),
	FMT("11101rrraaaaaaaa", "djnz %R,%J"),

	FMT("11100101", "en   DMA"),
	FMT("11110101", "en   FLAGS"),

	FMT("00000101", "en   i"),
	FMT("00100101", "en   tcnti"),
	FMT("01110101", "ent0 clk"),

	FMT("00100010", "in   a,DBB"),

	FMT("00001001", "in   a,p1"),
	FMT("00001010", "in   a,p2"),
	FMT("00010111", "inc  a"),
	FMT("00011rrr", "inc  %R"),
	FMT("0001000r", "inc  @%R"),
//	FMT("00001000", "ins  a,bus"),
	FMT("bbb10010aaaaaaaa", "jb%B  %J"),
	FMT("1111 0110aaaaaaaa", "jc   %J"),
	FMT("1011 0110aaaaaaaa", "jf0  %J"),
	FMT("0111 0110aaaaaaaa", "jf1  %J"),
	FMT("aaa00100aaaaaaaa", "jmp  %A"),
	FMT("10110011", "jmpp @a"),
	FMT("1110 0110aaaaaaaa", "jnc  %J"),

	FMT("1101 0110aaaaaaaa", "jnibf  %J"),

	FMT("0010 0110aaaaaaaa", "jnt0 %J"),
	FMT("0100 0110aaaaaaaa", "jnt1 %J"),
	FMT("1001 0110aaaaaaaa", "jnz  %J"),

	FMT("1000 0110aaaaaaaa", "jobf  %J"),

	FMT("0001 0110aaaaaaaa", "jtf  %J"),
	FMT("0011 0110aaaaaaaa", "jt0  %J"),
	FMT("0101 0110aaaaaaaa", "jt1  %J"),
	FMT("1100 0110aaaaaaaa", "jz   %J"),
//	FMT("0010 0110aaaaaaaa", "jnt0 %J"),
//	FMT("1000 0110aaaaaaaa", "jni  %J"),
//	FMT("1001 0110aaaaaaaa", "jnz  %J"),
	FMT("00100011dddddddd", "mov  a,#$%X"),
	FMT("11000111", "mov  a,psw"),
	FMT("11111rrr", "mov  a,%R"),
	FMT("1111000r", "mov  a,@%R"),
	FMT("01000010", "mov  a,t"),
	FMT("11010111", "mov  psw,a"),
	FMT("10101rrr", "mov  %R,a"),
	FMT("10111rrrdddddddd", "mov  %R,#$%X"),
	FMT("1010000r", "mov  @%R,a"),
	FMT("1011000rdddddddd", "mov  @%R,#$%X"),
	FMT("10010000", "mov  sts,a"),
	FMT("01100010", "mov  t,a"),
	FMT("000011pp", "movd a,%P"),
	FMT("001111pp", "movd %P,a"),
	FMT("10100011", "movp a,@a"),
	FMT("11100011", "movp3 a,@a"),
	FMT("00000000", "nop"),
	FMT("0100 1rrr", "orl  a,%R"),
	FMT("0100 000r", "orl  a,@%R"),
	FMT("0100 0011dddddddd", "orl  a,#$%X"),
//	FMT("1000 1000dddddddd", "orl  bus,#$%X"),
	FMT("1000 1001dddddddd", "orl  p1,#$%X"),
	FMT("1000 1010dddddddd", "orl  p2,#$%X"),
	FMT("1000 11pp", "orld %P,a"),

	FMT("00000010", "outl bus,a"),

	FMT("00111001", "outl p1,a"),
	FMT("00111010", "outl p2,a"),
//	FMT("001110pp", "outl %p,a"),
	FMT("10000011", "ret"),
	FMT("10010011", "retr"),
	FMT("11100111", "rl   a"),
	FMT("11110111", "rlc  a"),
	FMT("01110111", "rr   a"),
	FMT("01100111", "rrc  a"),
	FMT("11000101", "sel  rb0"),
	FMT("11010101", "sel  rb1"),
	FMT("01100101", "stop tcnt"),
	FMT("01000101", "strt cnt"),
	FMT("01010101", "strt t"),
	FMT("01000111", "swap a"),
	FMT("00101rrr", "xch  a,%R"),
	FMT("0010000r", "xch  a,@%R"),
	FMT("0011000r", "xchd a,@%R"),
	FMT("1101 1rrr", "xrl  a,%R"),
	FMT("1101 000r", "xrl  a,@%R"),
	FMT("1101 0011dddddddd", "xrl  a,#$%X"),
//	FMT("1001000r", "movx @%R,a"),
//	FMT("1000000r", "movx a,@%R"),
//	FMT("11100101", "sel  mb0"),
//	FMT("11110101", "sel  mb1"),
	NULL
};

#define MAX_OPS (((sizeof(Formats) / sizeof(Formats[0])) - 1) / PTRS_PER_FORMAT)

typedef struct opcode {
	byte mask;	/* instruction mask */
	byte bits;	/* constant bits */
	char extcode;	/* value that gets extension code */
	const char *parse;	/* how to parse bits */
	const char *fmt;	/* instruction format */
} M48Opcode;

static M48Opcode Op[MAX_OPS+1];
static int OpInizialized = 0;

static void InitDasm8741(void)
{
	const char *p, **ops;
	byte mask, bits;
	int bit;
	int i;

	ops = Formats; i = 0;
	while (*ops) {
	p = *ops;
	mask = 0; bits = 0; bit = 7;
	while (*p && bit >= 0) {
		switch (*p++) {
			case '1': mask |= 1<<bit; bits |= 1<<bit; bit--; break;
			case '0': mask |= 1<<bit; bit--; break;
			case ' ': break;
			case 'b':
			case 'a': case 'r': case 'd': case 'p':
				bit --;
				break;
			default:
				printf("Invalid instruction encoding '%s %s'\n", ops[0],ops[1]);
				exit(1);
		}
	}
	if (bit != -1 ) {
		printf("not enough bits in encoding '%s %s' %d\n", ops[0],ops[1],bit);
		exit(1);
	}
	while (isspace(*p)) p++;
	if (*p) Op[i].extcode = *p;
	Op[i].bits = bits;
	Op[i].mask = mask;
	Op[i].fmt = ops[1];
	Op[i].parse = ops[0];

	ops += PTRS_PER_FORMAT;
	i++;
	}

	OpInizialized = 1;
}

/*
 * DHH 1/23/03: Added these for use outside of MAME/MESS
 */
static unsigned char *codebuf;
static int cpu_readop(unsigned pc)
{
	return codebuf[pc];
}
static int cpu_readop_arg(unsigned pc)
{
	return codebuf[pc];
}

int Dasm8741(char *buffer, unsigned pc)
{
	int b, a, d, r, p;	/* these can all be filled in by parsing an instruction */
	int i;
	int op;
	int cnt = 1;
	int code, bit;
	const char *cp;

	if (!OpInizialized) InitDasm8741();

	code = cpu_readop(pc);
	op = -1;	/* no matching opcode */
	for ( i = 0; i < MAX_OPS; i++)
	{
		if( (code & Op[i].mask) == Op[i].bits )
		{
			if (op != -1)
			{
				fprintf(stderr, "Error: opcode %02X matches %d (%s) and %d (%s)\n",
					code,i,Op[i].fmt,op,Op[op].fmt);
			}
			op = i;
		}
	}

	if (op == -1)
	{
		sprintf(buffer,".db   0x%2.2x",code);
		return cnt;
	}

	if (Op[op].extcode)
	{
		cnt++;
		code <<= 8;
		code |= cpu_readop_arg((pc+1)&0xffff);
		bit = 15;
	}
	else
	{
		bit = 7;
	}

	/* shift out operands */
	cp = Op[op].parse;
	b = a = d = r = p = 0;

	while (bit >= 0)
	{
		/* printf("{%c/%d}",*cp,bit); */
		switch(*cp)
		{
			case 'a': a <<=1; a |= ((code & (1<<bit)) ? 1 : 0); bit--; break;
			case 'b': b <<=1; b |= ((code & (1<<bit)) ? 1 : 0); bit--; break;
			case 'd': d <<=1; d |= ((code & (1<<bit)) ? 1 : 0); bit--; break;
			case 'r': r <<=1; r |= ((code & (1<<bit)) ? 1 : 0); bit--; break;
			case 'p': p <<=1; p |= ((code & (1<<bit)) ? 1 : 0); bit--; break;
			case ' ': break;
			case '1': case '0': bit--; break;
			case '\0': printf("premature end of parse string, opcode %x, bit = %d\n",code,bit); exit(1);
		}
		cp++;
	}

	/* now traverse format string */
	cp = Op[op].fmt;
	while (*cp)
	{
		if (*cp == '%')
		{
			char num[10], *q;
			cp++;
			switch (*cp++)
			{
				case 'A': sprintf(num,"$%04X",a); break;
				case 'J': sprintf(num,"$%04X",(pc & 0xf00) | a); break;
				case 'B': sprintf(num,"%d",b); break;
				case 'D': sprintf(num,"%d",d); break;
				case 'X': sprintf(num,"%X",d); break;
				case 'R': sprintf(num,"r%d",r); break;
				case 'P': sprintf(num,"p%d",p + 4); break;
				default:
					printf("illegal escape character in format '%s'\n",Op[op].fmt);
					exit(1);
			}
			q = num; while (*q) *buffer++ = *q++;
			*buffer = '\0';
		}
		else
		{
			*buffer++ = *cp++;
			*buffer = '\0';
		}
	}

	return cnt;
}

/*
 * DHH 1/23/03: Added this driver for use outside of MAME/MESS.
 */
int main(int argc, char **argv)
{
	const char *filename;
	unsigned offset, length;
	char buf[256];
	int pc = 0;
	FILE *fp;

	if (argc != 4) {
		fprintf(stderr, "Usage: 8741dasm <filename> <offset> <len>\n");
		exit(1);
	}

	filename = argv[1];
	offset = (unsigned) atoi(argv[2]);
	length = (unsigned) atoi(argv[3]);

	fp = fopen(filename, "rb");
	if (fp == NULL) {
		fprintf(stderr, "Couldn't open %s: %s\n", filename, strerror(errno));
		exit(1);
	}

	if (fseek(fp, offset, SEEK_SET) < 0) {
		fprintf(stderr, "Couldn't seek to %u in %s: %s\n", offset, filename, strerror(errno));
		exit(1);
	}

	codebuf = (unsigned char *) malloc(length);
	if (codebuf == NULL) {
		fprintf(stderr, "Couldn't malloc %u bytes: %s\n", length, strerror(errno));
		exit(1);
	}

	if (fread(codebuf, 1, length, fp) != length) {
		fprintf(stderr, "Couldn't read %u bytes from %s: %s\n", length, filename, strerror(errno));
		exit(1);
	}

	InitDasm8741();

	while (pc < length) {
                printf("PC:%03X\t", pc);
		pc += Dasm8741(buf, pc);
		printf("%s\n", buf);
	}

	return 0;
}
