/*
 *  SAM.h - Simple Assembler and Monitor With Integrated System Explorer
 *
 *  Frodo (C) 1994-1997,2002-2009 Christian Bauer
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "sysdeps.h"

#include "SAM.h"
#include "C64.h"
#include "CPUC64.h"
#include "CPU1541.h"
#include "VIC.h"
#include "SID.h"
#include "CIA.h"


// Pointers to chips
static MOS6510 *TheCPU;
static MOS6502_1541 *TheCPU1541;
static MOS6569 *TheVIC;
static MOS6581 *TheSID;
static MOS6526_1 *TheCIA1;
static MOS6526_2 *TheCIA2;

// 6510/6502 registers
static MOS6510State R64;
static MOS6502State R1541;

static bool access_1541;	// false: accessing C64, true: accessing 1541

// Access to 6510/6502 address space
static inline uint8 SAMReadByte(uint16 adr)
{
	if (access_1541)
		return TheCPU1541->ExtReadByte(adr);
	else
		return TheCPU->ExtReadByte(adr);
}

static inline void SAMWriteByte(uint16 adr, uint8 byte)
{
	if (access_1541)
		TheCPU1541->ExtWriteByte(adr, byte);
	else
		TheCPU->ExtWriteByte(adr, byte);
}


// Streams for input, output and error messages
static FILE *fin, *fout, *ferr;

// Input line
#define INPUT_LENGTH 80
static char input[INPUT_LENGTH];
static char *in_ptr;

static uint16 address, end_address;


// Input tokens
enum Token {
	T_NULL,		// Invalid token
	T_END,		// End of line
	T_NUMBER,	// Hexadecimal number
	T_STRING,	// String enclosed in ""
	T_LPAREN,	// '('
	T_RPAREN,	// ')'
	T_ADD,		// '+'
	T_SUB,		// '-'
	T_MUL,		// '*'
	T_DIV,		// '/'
	T_COMMA,	// ','
	T_IMMED,	// '#'
	T_X,		// 'x'
	T_Y,		// 'y'
	T_PC,		// 'pc'
	T_SP,		// 'sp'

	T_A,		// 'a'	(get_reg_token() only)
	T_DR,		// 'dr'	(get_reg_token() only)
	T_PR		// 'pr'	(get_reg_token() only)
};

static enum Token the_token;			// Last token read
static uint16 the_number;				// Contains the number if the_token==T_NUMBER
static char the_string[INPUT_LENGTH];	// Contains the string if the_token==T_STRING


// Addressing modes
enum {
	A_IMPL,
	A_ACCU,		// A
	A_IMM,		// #zz
	A_REL,		// Branches
	A_ZERO,		// zz
	A_ZEROX,	// zz,x
	A_ZEROY,	// zz,y
	A_ABS,		// zzzz
	A_ABSX,		// zzzz,x
	A_ABSY,		// zzzz,y
	A_IND,		// (zzzz)
	A_INDX,		// (zz,x)
	A_INDY		// (zz),y
};

// Mnemonics
enum {
	M_ADC, M_AND, M_ASL, M_BCC, M_BCS, M_BEQ, M_BIT, M_BMI, M_BNE, M_BPL,
	M_BRK, M_BVC, M_BVS, M_CLC, M_CLD, M_CLI, M_CLV, M_CMP, M_CPX, M_CPY,
	M_DEC, M_DEX, M_DEY, M_EOR, M_INC, M_INX, M_INY, M_JMP, M_JSR, M_LDA,
	M_LDX, M_LDY, M_LSR, M_NOP, M_ORA, M_PHA, M_PHP, M_PLA, M_PLP, M_ROL,
	M_ROR, M_RTI, M_RTS, M_SBC, M_SEC, M_SED, M_SEI, M_STA, M_STX, M_STY,
	M_TAX, M_TAY, M_TSX, M_TXA, M_TXS, M_TYA,

	M_ILLEGAL,  // Undocumented opcodes start here

	M_IANC, M_IANE, M_IARR, M_IASR, M_IDCP, M_IISB, M_IJAM, M_INOP, M_ILAS,
	M_ILAX, M_ILXA, M_IRLA, M_IRRA, M_ISAX, M_ISBC, M_ISBX, M_ISHA, M_ISHS,
	M_ISHX, M_ISHY, M_ISLO, M_ISRE,

	M_MAXIMUM  // Highest element
};

// Mnemonic for each opcode
static const char mnemonic[256] = {
	M_BRK , M_ORA , M_IJAM, M_ISLO, M_INOP, M_ORA, M_ASL , M_ISLO,	// 00
	M_PHP , M_ORA , M_ASL , M_IANC, M_INOP, M_ORA, M_ASL , M_ISLO,
	M_BPL , M_ORA , M_IJAM, M_ISLO, M_INOP, M_ORA, M_ASL , M_ISLO,	// 10
	M_CLC , M_ORA , M_INOP, M_ISLO, M_INOP, M_ORA, M_ASL , M_ISLO,
	M_JSR , M_AND , M_IJAM, M_IRLA, M_BIT , M_AND, M_ROL , M_IRLA,	// 20
	M_PLP , M_AND , M_ROL , M_IANC, M_BIT , M_AND, M_ROL , M_IRLA,
	M_BMI , M_AND , M_IJAM, M_IRLA, M_INOP, M_AND, M_ROL , M_IRLA,	// 30
	M_SEC , M_AND , M_INOP, M_IRLA, M_INOP, M_AND, M_ROL , M_IRLA,
	M_RTI , M_EOR , M_IJAM, M_ISRE, M_INOP, M_EOR, M_LSR , M_ISRE,	// 40
	M_PHA , M_EOR , M_LSR , M_IASR, M_JMP , M_EOR, M_LSR , M_ISRE,
	M_BVC , M_EOR , M_IJAM, M_ISRE, M_INOP, M_EOR, M_LSR , M_ISRE,	// 50
	M_CLI , M_EOR , M_INOP, M_ISRE, M_INOP, M_EOR, M_LSR , M_ISRE,
	M_RTS , M_ADC , M_IJAM, M_IRRA, M_INOP, M_ADC, M_ROR , M_IRRA,	// 60
	M_PLA , M_ADC , M_ROR , M_IARR, M_JMP , M_ADC, M_ROR , M_IRRA,
	M_BVS , M_ADC , M_IJAM, M_IRRA, M_INOP, M_ADC, M_ROR , M_IRRA,	// 70
	M_SEI , M_ADC , M_INOP, M_IRRA, M_INOP, M_ADC, M_ROR , M_IRRA,
	M_INOP, M_STA , M_INOP, M_ISAX, M_STY , M_STA, M_STX , M_ISAX,	// 80
	M_DEY , M_INOP, M_TXA , M_IANE, M_STY , M_STA, M_STX , M_ISAX,
	M_BCC , M_STA , M_IJAM, M_ISHA, M_STY , M_STA, M_STX , M_ISAX,	// 90
	M_TYA , M_STA , M_TXS , M_ISHS, M_ISHY, M_STA, M_ISHX, M_ISHA,
	M_LDY , M_LDA , M_LDX , M_ILAX, M_LDY , M_LDA, M_LDX , M_ILAX,	// a0
	M_TAY , M_LDA , M_TAX , M_ILXA, M_LDY , M_LDA, M_LDX , M_ILAX,
	M_BCS , M_LDA , M_IJAM, M_ILAX, M_LDY , M_LDA, M_LDX , M_ILAX,	// b0
	M_CLV , M_LDA , M_TSX , M_ILAS, M_LDY , M_LDA, M_LDX , M_ILAX,
	M_CPY , M_CMP , M_INOP, M_IDCP, M_CPY , M_CMP, M_DEC , M_IDCP,	// c0
	M_INY , M_CMP , M_DEX , M_ISBX, M_CPY , M_CMP, M_DEC , M_IDCP,
	M_BNE , M_CMP , M_IJAM, M_IDCP, M_INOP, M_CMP, M_DEC , M_IDCP,	// d0
	M_CLD , M_CMP , M_INOP, M_IDCP, M_INOP, M_CMP, M_DEC , M_IDCP,
	M_CPX , M_SBC , M_INOP, M_IISB, M_CPX , M_SBC, M_INC , M_IISB,	// e0
	M_INX , M_SBC , M_NOP , M_ISBC, M_CPX , M_SBC, M_INC , M_IISB,
	M_BEQ , M_SBC , M_IJAM, M_IISB, M_INOP, M_SBC, M_INC , M_IISB,	// f0
	M_SED , M_SBC , M_INOP, M_IISB, M_INOP, M_SBC, M_INC , M_IISB
};

// Addressing mode for each opcode
static const char adr_mode[256] = {
	A_IMPL, A_INDX, A_IMPL, A_INDX, A_ZERO , A_ZERO , A_ZERO , A_ZERO,	// 00
	A_IMPL, A_IMM , A_ACCU, A_IMM , A_ABS  , A_ABS  , A_ABS  , A_ABS,
	A_REL , A_INDY, A_IMPL, A_INDY, A_ZEROX, A_ZEROX, A_ZEROX, A_ZEROX,	// 10
	A_IMPL, A_ABSY, A_IMPL, A_ABSY, A_ABSX , A_ABSX , A_ABSX , A_ABSX,
	A_ABS , A_INDX, A_IMPL, A_INDX, A_ZERO , A_ZERO , A_ZERO , A_ZERO,	// 20
	A_IMPL, A_IMM , A_ACCU, A_IMM , A_ABS  , A_ABS  , A_ABS  , A_ABS,
	A_REL , A_INDY, A_IMPL, A_INDY, A_ZEROX, A_ZEROX, A_ZEROX, A_ZEROX,	// 30
	A_IMPL, A_ABSY, A_IMPL, A_ABSY, A_ABSX , A_ABSX , A_ABSX , A_ABSX,
	A_IMPL, A_INDX, A_IMPL, A_INDX, A_ZERO , A_ZERO , A_ZERO , A_ZERO,	// 40
	A_IMPL, A_IMM , A_ACCU, A_IMM , A_ABS  , A_ABS  , A_ABS  , A_ABS,
	A_REL , A_INDY, A_IMPL, A_INDY, A_ZEROX, A_ZEROX, A_ZEROX, A_ZEROX,	// 50
	A_IMPL, A_ABSY, A_IMPL, A_ABSY, A_ABSX , A_ABSX , A_ABSX , A_ABSX,
	A_IMPL, A_INDX, A_IMPL, A_INDX, A_ZERO , A_ZERO , A_ZERO , A_ZERO,	// 60
	A_IMPL, A_IMM , A_ACCU, A_IMM , A_IND  , A_ABS  , A_ABS  , A_ABS,
	A_REL , A_INDY, A_IMPL, A_INDY, A_ZEROX, A_ZEROX, A_ZEROX, A_ZEROX,	// 70
	A_IMPL, A_ABSY, A_IMPL, A_ABSY, A_ABSX , A_ABSX , A_ABSX , A_ABSX,
	A_IMM , A_INDX, A_IMM , A_INDX, A_ZERO , A_ZERO , A_ZERO , A_ZERO,	// 80
	A_IMPL, A_IMM , A_IMPL, A_IMM , A_ABS  , A_ABS  , A_ABS  , A_ABS,
	A_REL , A_INDY, A_IMPL, A_INDY, A_ZEROX, A_ZEROX, A_ZEROY, A_ZEROY,	// 90
	A_IMPL, A_ABSY, A_IMPL, A_ABSY, A_ABSX , A_ABSX , A_ABSY , A_ABSY,
	A_IMM , A_INDX, A_IMM , A_INDX, A_ZERO , A_ZERO , A_ZERO , A_ZERO,	// a0
	A_IMPL, A_IMM , A_IMPL, A_IMM , A_ABS  , A_ABS  , A_ABS  , A_ABS,
	A_REL , A_INDY, A_IMPL, A_INDY, A_ZEROX, A_ZEROX, A_ZEROY, A_ZEROY,	// b0
	A_IMPL, A_ABSY, A_IMPL, A_ABSY, A_ABSX , A_ABSX , A_ABSY , A_ABSY,
	A_IMM , A_INDX, A_IMM , A_INDX, A_ZERO , A_ZERO , A_ZERO , A_ZERO,	// c0
	A_IMPL, A_IMM , A_IMPL, A_IMM , A_ABS  , A_ABS  , A_ABS  , A_ABS,
	A_REL , A_INDY, A_IMPL, A_INDY, A_ZEROX, A_ZEROX, A_ZEROX, A_ZEROX,	// d0
	A_IMPL, A_ABSY, A_IMPL, A_ABSY, A_ABSX , A_ABSX , A_ABSX , A_ABSX,
	A_IMM , A_INDX, A_IMM , A_INDX, A_ZERO , A_ZERO , A_ZERO , A_ZERO,	// e0
	A_IMPL, A_IMM , A_IMPL, A_IMM , A_ABS  , A_ABS  , A_ABS  , A_ABS,
	A_REL , A_INDY, A_IMPL, A_INDY, A_ZEROX, A_ZEROX, A_ZEROX, A_ZEROX,	// f0
	A_IMPL, A_ABSY, A_IMPL, A_ABSY, A_ABSX , A_ABSX , A_ABSX , A_ABSX
};

// Chars for each mnemonic
static const char mnem_1[] = "aaabbbbbbbbbbcccccccdddeiiijjllllnopppprrrrssssssstttttt?aaaadijnlllrrsssssssss";
static const char mnem_2[] = "dnscceimnprvvllllmppeeeonnnmsdddsorhhlloottbeeetttaasxxy?nnrscsaoaaxlrabbhhhhlr";
static const char mnem_3[] = "cdlcsqtielkcscdivpxycxyrcxypraxyrpaapaplrisccdiaxyxyxasa?cerrpbmpsxaaaxcxasxyoe";

// Instruction length for each addressing mode
static const char adr_length[] = {1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 2, 2};


// Prototypes
static void error(const char *s);
static void handle_abort(...);
static void init_abort(void);
static void exit_abort(void);
static bool aborted(void);

static void read_line(void);			// Scanner
static char get_char(void);
static void put_back(char c);
static enum Token get_token(void);
static enum Token get_reg_token(void);
static uint16 get_number(void);
static enum Token get_string(char *str);

static bool expression(uint16 *number);	// Parser
static bool term(uint16 *number);
static bool factor(uint16 *number);
static bool address_args(void);
static bool range_args(int def_range);
static bool instr_args(uint16 *number, char *mode);

static void help(void);				// Routines for commands
static void registers(void);
static void display_registers(void);
static void memory_dump(void);
static void ascii_dump(void);
static char conv_from_64(char c);
static void screen_dump(void);
static char conv_from_scode(char c);
static void binary_dump(void);
static void sprite_dump(void);
static void byte_to_bin(uint8 byte, char *str);
static void disassemble(void);
static int disass_line(uint16 adr, uint8 op, uint8 lo, uint8 hi);
static void assemble(void);
static char find_mnemonic(char op1, char op2, char op3);
static bool find_opcode(char mnem, char mode, uint8 *opcode);
static void mem_config(void);
static void fill(void);
static void compare(void);
static void transfer(void);
static void modify(void);
static void print_expr(void);
static void redir_output(void);
static void int_vectors(void);
static void view_state(void);
static void view_cia_state(void);
static void dump_cia_ints(uint8 i);
static void view_sid_state(void);
static void dump_sid_waveform(uint8 wave);
static void view_vic_state(void);
static void dump_spr_flags(uint8 f);
static void dump_vic_ints(uint8 i);
static void view_1541_state(void);
static void dump_via_ints(uint8 i);
static void load_data(void);
static void save_data(void);


/*
 *  Open and handle SAM
 */

void SAM(C64 *the_c64)
{
	bool done = false;
	char c;

	TheCPU = the_c64->TheCPU;
	TheCPU1541 = the_c64->TheCPU1541;
	TheVIC = the_c64->TheVIC;
	TheSID = the_c64->TheSID;
	TheCIA1 = the_c64->TheCIA1;
	TheCIA2 = the_c64->TheCIA2;

	// Get CPU registers and current memory configuration
	TheCPU->GetState(&R64);
	TheCPU->ExtConfig = (~R64.ddr | R64.pr) & 7;
	TheCPU1541->GetState(&R1541);

#ifdef __riscos__
	Wimp_CommandWindow((int)"SAM");
#endif

#ifdef AMIGA
	if (!(fin = fout = ferr = fopen("CON:0/0/640/480/SAM", "w+")))
		return;
#else
	fin = stdin;
	fout = stdout;
	ferr = stdout;
#endif

	access_1541 = false;
	address = R64.pc;

	fprintf(ferr, "\n *** SAM - Simple Assembler and Monitor ***\n ***         Press 'h' for help         ***\n\n");
	init_abort();
	display_registers();

	while (!done) {
		if (access_1541)
			fprintf(ferr, "1541> ");
		else
			fprintf(ferr, "C64> ");
		fflush(ferr);
		read_line();
		while ((c = get_char()) == ' ') ;

		switch (c) {
			case 'a':		// Assemble
				get_token();
				assemble();
				break;

			case 'b':		// Binary dump
				get_token();
				binary_dump();
				break;

			case 'c':		// Compare
				get_token();
				compare();
				break;

			case 'd':		// Disassemble
				get_token();
				disassemble();
				break;

			case 'e':       // Interrupt vectors
				int_vectors();
				break;

			case 'f':		// Fill
				get_token();
				fill();
				break;

			case 'h':		// Help
				help();
				break;

			case 'i':		// ASCII dump
				get_token();
				ascii_dump();
				break;

			case 'k':		// Memory configuration
				get_token();
				mem_config();
				break;

			case 'l':		// Load data
				get_token();
				load_data();
				break;

			case 'm':		// Memory dump
				get_token();
				memory_dump();
				break;

			case 'n':		// Screen code dump
				get_token();
				screen_dump();
				break;

			case 'o':		// Redirect output
				get_token();
				redir_output();
				break;

			case 'p':		// Sprite dump
				get_token();
				sprite_dump();
				break;

			case 'r':		// Registers
				get_reg_token();
				registers();
				break;

			case 's':		// Save data
				get_token();
				save_data();
				break;

			case 't':		// Transfer
				get_token();
				transfer();
				break;

			case 'v':		// View machine state
				view_state();
				break;

			case 'x':		// Exit
				done = true;
				break;

			case ':':		// Change memory
				get_token();
				modify();
				break;

			case '1':		// Switch to 1541 mode
				access_1541 = true;
				break;

			case '6':		// Switch to C64 mode
				access_1541 = false;
				break;

			case '?':		// Compute expression
				get_token();
				print_expr();
				break;

			case '\n':		// Blank line
				break;

			default:		// Unknown command
				error("Unknown command");
				break;
		}
	}

	exit_abort();

#ifdef AMIGA
	fclose(fin);
#endif
	if (fout != ferr)
		fclose(fout);

#ifdef __riscos__
	Wimp_CommandWindow(-1);
#endif

	// Set CPU registers
	TheCPU->SetState(&R64);
	TheCPU1541->SetState(&R1541);
}


/*
 *  Print error message
 */

static void error(const char *s)
{
	fprintf(ferr, "*** %s\n", s);
}


/*
 *  CTRL-C pressed?
 */

static bool WasAborted;

#ifdef HAVE_SIGACTION
struct sigaction my_sa;
#endif

static void handle_abort(...)
{
	WasAborted = true;
#if !defined(HAVE_SIGACTION) && defined(HAVE_SIGNAL)
#ifdef __riscos__
	signal(SIGINT, (Handler*) handle_abort);
#else
	signal(SIGINT, (sighandler_t) handle_abort);
#endif
#endif
}

static void init_abort(void)
{
	WasAborted = false;
#if defined(HAVE_SIGACTION)
	my_sa.sa_handler = (void (*)(int))handle_abort;
	my_sa.sa_flags = 0;
	sigemptyset(&my_sa.sa_mask);
	sigaction(SIGINT, &my_sa, NULL);
#elif defined(HAVE_SIGNAL)
#ifdef __riscos__
	signal(SIGINT, (Handler*) handle_abort);
#else
	signal(SIGINT, (sighandler_t) handle_abort);
#endif
#endif
}

static void exit_abort(void)
{
#if defined(HAVE_SIGACTION)
	my_sa.sa_handler = SIG_DFL;
	sigaction(SIGINT, &my_sa, NULL);
#elif defined(HAVE_SIGNAL)
	signal(SIGINT, SIG_DFL);
#endif
}

static bool aborted(void)
{
	bool ret = WasAborted;

	WasAborted = false;
	return ret;
}


/*
 *  Read a line from the keyboard
 */

static void read_line(void)
{
#ifdef __riscos__
	OS_ReadLine(in_ptr = input, INPUT_LENGTH, 0, 255, 0);
#else
	fgets(in_ptr = input, INPUT_LENGTH, fin);
#endif
}


/*
 *  Read a character from the input line
 */

static char get_char(void)
{
	return *in_ptr++;
}


/*
 *  Stuff back a character into the input line
 */

static void put_back(char c)
{
	*(--in_ptr) = c;
}


/*
 *  Scanner: Get a token from the input line
 */

static enum Token get_token(void)
{
	char c;

	// Skip spaces
	while ((c = get_char()) == ' ') ;

	switch (c) {
		case '\n':
			return the_token = T_END;
		case '(':
			return the_token = T_LPAREN;
		case ')':
			return the_token = T_RPAREN;
		case '+':
			return the_token = T_ADD;
		case '-':
			return the_token = T_SUB;
		case '*':
			return the_token = T_MUL;
		case '/':
			return the_token = T_DIV;
		case ',':
			return the_token = T_COMMA;
		case '#':
			return the_token = T_IMMED;
		case 'x':
			return the_token = T_X;
		case 'y':
			return the_token = T_Y;
		case 'p':
			if (get_char() == 'c')
				return the_token = T_PC;
			else {
				error("Unrecognized token");
				return the_token = T_NULL;
			}
		case 's':
			if (get_char() == 'p')
				return the_token = T_SP;
			else {
				error("Unrecognized token");
				return the_token = T_NULL;
			}
		case '0': case '1': case '2': case '3': case '4':
		case '5': case '6': case '7': case '8': case '9':
		case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
			put_back(c);
			the_number = get_number();
			return the_token = T_NUMBER;
		case '"':
			return the_token = get_string(the_string);
		default:
			error("Unrecognized token");
			return the_token = T_NULL;
	}
}

static enum Token get_reg_token(void)
{
	char c;

	// Skip spaces
	while ((c = get_char()) == ' ') ;

	switch (c) {
		case '\n':
			return the_token = T_END;
		case 'a':
			return the_token = T_A;
		case 'd':
			if (get_char() == 'r')
				return the_token = T_DR;
			else {
				error("Unrecognized token");
				return the_token = T_NULL;
			}
		case 'p':
			if ((c = get_char()) == 'c')
				return the_token = T_PC;
			else if (c == 'r')
				return the_token = T_PR;
			else {
				error("Unrecognized token");
				return the_token = T_NULL;
			}
		case 's':
			if (get_char() == 'p')
				return the_token = T_SP;
			else {
				error("Unrecognized token");
				return the_token = T_NULL;
			}
		case 'x':
			return the_token = T_X;
		case 'y':
			return the_token = T_Y;
		default:
			error("Unrecognized token");
			return the_token = T_NULL;
	}
}

static uint16 get_number(void)
{
	char c;
	uint16 i = 0;

	while (((c = get_char()) >= '0') && (c <= '9') || (c >= 'a') && (c <= 'f'))
		if (c < 'a')
			i = (i << 4) + (c - '0');
		else
			i = (i << 4) + (c - 'a' + 10);

	put_back(c);
	return i;
}

static enum Token get_string(char *str)
{
	char c;

	while ((c = get_char()) != '\n') {
		if (c == '"') {
			*str = 0;
			return T_STRING;
		}
		*str++ = c;
	}

	error("Unterminated string");
	return T_NULL;
}


/*
 *  expression = term {(ADD | SUB) term}
 *  true: OK, false: Error
 */

static bool expression(uint16 *number)
{
	uint16 accu, trm;

	if (!term(&accu))
		return false;

	for (;;)
		switch (the_token) {
			case T_ADD:
				get_token();
				if (!term(&trm))
					return false;
				accu += trm;
				break;

			case T_SUB:
				get_token();
				if (!term(&trm))
					return false;
				accu -= trm;
				break;

			default:
				*number = accu;
				return true;
		}
}


/*
 *  term = factor {(MUL | DIV) factor}
 *  true: OK, false: Error
 */

static bool term(uint16 *number)
{
	uint16 accu, fact;

	if (!factor(&accu))
		return false;

	for (;;)
		switch (the_token) {
			case T_MUL:
				get_token();
				if (!factor(&fact))
					return false;
				accu *= fact;
				break;

			case T_DIV:
				get_token();
				if (!factor(&fact))
					return false;
				if (fact == 0) {
					error("Division by 0");
					return false;
				}
				accu /= fact;
				break;

			default:
				*number = accu;
				return true;
		}
}


/*
 *  factor = NUMBER | PC | SP | LPAREN expression RPAREN
 *  true: OK, false: Error
 */

static bool factor(uint16 *number)
{
	switch (the_token) {
		case T_NUMBER:
			*number = the_number;
			get_token();
			return true;

		case T_PC:
			get_token();
			*number = access_1541 ? R1541.pc : R64.pc;
			return true;

		case T_SP:
			get_token();
			*number = access_1541 ? R1541.sp : R64.sp;
			return true;

		case T_LPAREN:
			get_token();
			if (expression(number))
				if (the_token == T_RPAREN) {
					get_token();
					return true;
				} else {
					error("Missing ')'");
					return false;
				}
			else {
				error("Error in expression");
				return false;
			}

		case T_END:
			error("Required argument missing");
			return false;

		default:
			error("'pc', 'sp', '(' or number expected");
			return false;
	}
}


/*
 *  address_args = [expression] END
 *
 *  Read start to "address"
 *
 *  true: OK, false: Error
 */

static bool address_args(void)
{
	if (the_token == T_END)
		return true;
	else {
		if (!expression(&address))
			return false;
		return the_token == T_END;
	}
}


/*
 *  range_args = [expression] [[COMMA] expression] END
 *
 *  Read start address to "address", end address to "end_address"
 *
 *  true: OK, false: Error
 */

static bool range_args(int def_range)
{
	end_address = address + def_range;

	if (the_token == T_END)
		return true;
	else {
		if (!expression(&address))
			return false;
		end_address = address + def_range;
		if (the_token == T_END)
			return true;
		else {
			if (the_token == T_COMMA) get_token();
			if (!expression(&end_address))
				return false;
			return the_token == T_END;
		}
	}
}


/*
 *  instr_args = END
 *             | IMMED NUMBER END
 *             | NUMBER [COMMA (X | Y)] END
 *             | LPAREN NUMBER (RPAREN [COMMA Y] | COMMA X RPAREN) END
 *
 *  Read arguments of a 6510 instruction, determine address and addressing mode
 *
 *  true: OK, false: Error
 */

static bool instr_args(uint16 *number, char *mode)
{
	switch (the_token) {

		case T_END:
			*mode = A_IMPL;
			return true;

		case T_IMMED:
			get_token();
			if (the_token == T_NUMBER) {
				*number = the_number;
				*mode = A_IMM;
				get_token();
				return the_token == T_END;
			} else {
				error("Number expected");
				return false;
			}

		case T_NUMBER:
			*number = the_number;
			get_token();
			switch (the_token) {

				case T_END:
					if (*number < 0x100)
						*mode = A_ZERO;
					else
						*mode = A_ABS;
					return true;

				case T_COMMA:
					get_token();
					switch (the_token) {

						case T_X:
							get_token();
							if (*number < 0x100)
								*mode = A_ZEROX;
							else
								*mode = A_ABSX;
							return the_token == T_END;

						case T_Y:
							get_token();
							if (*number < 0x100)
								*mode = A_ZEROY;
							else
								*mode = A_ABSY;
							return the_token == T_END;

						default:
							error("Illegal index register");
							return false;
					}

				default:
					return false;
			}

		case T_LPAREN:
			get_token();
			if (the_token == T_NUMBER) {
				*number = the_number;
				get_token();
				switch (the_token) {

					case T_RPAREN:
						get_token();
						switch (the_token) {

							case T_END:
								*mode = A_IND;
								return true;

							case T_COMMA:
								get_token();
								if (the_token == T_Y) {
									*mode = A_INDY;
									get_token();
									return the_token == T_END;
								} else {
									error("Only 'y' index register allowed");
									return false;
								}

							default:
								error("Illegal characters after ')'");
								return false;
						}

					case T_COMMA:
						get_token();
						if (the_token == T_X) {
							get_token();
							if (the_token == T_RPAREN) {
								*mode = A_INDX;
								get_token();
								return the_token == T_END;
							} else {
								error("')' expected");
								return false;
							}
						} else {
							error("Only 'x' index register allowed");
							return false;
						}

					default:
						error("')' or ',' expected");
						return false;
				}
			} else {
				error("Number expected");
				return false;
			}

		default:
			error("'(', '#' or number expected");
			return false;
	}
}


/*
 *  Display help
 *  h
 */

static void help(void)
{
	fprintf(fout, "a [start]           Assemble\n"
				"b [start] [end]     Binary dump\n"
				"c start end dest    Compare memory\n"
				"d [start] [end]     Disassemble\n"
				"e                   Show interrupt vectors\n"
				"f start end byte    Fill memory\n"
				"i [start] [end]     ASCII/PETSCII dump\n"
				"k [config]          Show/set C64 memory configuration\n"
				"l start \"file\"      Load data\n"
				"m [start] [end]     Memory dump\n"
				"n [start] [end]     Screen code dump\n"
				"o [\"file\"]          Redirect output\n"
				"p [start] [end]     Sprite dump\n"
				"r [reg value]       Show/set CPU registers\n"
				"s start end \"file\"  Save data\n"
				"t start end dest    Transfer memory\n"
				"vc1                 View CIA 1 state\n"
				"vc2                 View CIA 2 state\n"
				"vf                  View 1541 state\n"
				"vs                  View SID state\n"
				"vv                  View VIC state\n"
				"x                   Return to Frodo\n"
				": addr {byte}       Modify memory\n"
				"1541                Switch to 1541\n"
				"64                  Switch to C64\n"
				"? expression        Calculate expression\n");
}


/*
 *  Display/change 6510 registers
 *  r [reg value]
 */

static void registers(void)
{
	enum Token the_reg;
	uint16 value;

	if (the_token != T_END)
		switch (the_reg = the_token) {
			case T_A:
			case T_X:
			case T_Y:
			case T_PC:
			case T_SP:
			case T_DR:
			case T_PR:
				get_token();
				if (!expression(&value))
					return;

				switch (the_reg) {
					case T_A:
						if (access_1541)
							R1541.a = value;
						else
							R64.a = value;
						break;
					case T_X:
						if (access_1541)
							R1541.x = value;
						else
							R64.x = value;
						break;
					case T_Y:
						if (access_1541)
							R1541.y = value;
						else
							R64.y = value;
						break;
					case T_PC:
						if (access_1541)
							R1541.pc = value;
						else
							R64.pc = value;
						break;
					case T_SP:
						if (access_1541)
							R1541.sp = (value & 0xff) | 0x0100;
						else
							R64.sp = (value & 0xff) | 0x0100;
						break;
					case T_DR:
						if (!access_1541)
							R64.ddr = value;
						break;
					case T_PR:
						if (!access_1541)
							R64.pr = value;
						break;
					default:
						break;
				}
				break;

			default:
				return;
		}

	display_registers();
}

static void display_registers(void)
{
	if (access_1541) {
		fprintf(fout, " PC  A  X  Y   SP  NVDIZC  Instruction\n");
		fprintf(fout, "%04lx %02lx %02lx %02lx %04lx %c%c%c%c%c%c ",
			R1541.pc, R1541.a, R1541.x, R1541.y, R1541.sp,
			R1541.p & 0x80 ? '1' : '0', R1541.p & 0x40 ? '1' : '0', R1541.p & 0x08 ? '1' : '0',
			R1541.p & 0x04 ? '1' : '0', R1541.p & 0x02 ? '1' : '0', R1541.p & 0x01 ? '1' : '0');
		disass_line(R1541.pc, SAMReadByte(R1541.pc), SAMReadByte(R1541.pc+1), SAMReadByte(R1541.pc+2));
	} else {
		fprintf(fout, " PC  A  X  Y   SP  DR PR NVDIZC  Instruction\n");
		fprintf(fout, "%04lx %02lx %02lx %02lx %04lx %02lx %02lx %c%c%c%c%c%c ",
			R64.pc, R64.a, R64.x, R64.y, R64.sp, R64.ddr, R64.pr,
			R64.p & 0x80 ? '1' : '0', R64.p & 0x40 ? '1' : '0', R64.p & 0x08 ? '1' : '0',
			R64.p & 0x04 ? '1' : '0', R64.p & 0x02 ? '1' : '0', R64.p & 0x01 ? '1' : '0');
		disass_line(R64.pc, SAMReadByte(R64.pc), SAMReadByte(R64.pc+1), SAMReadByte(R64.pc+2));
	}
}


/*
 *  Memory dump
 *  m [start] [end]
 */

#define MEMDUMP_BPL 16  // Bytes per line

static void memory_dump(void)
{
	bool done = false;
	short i;
	uint8 mem[MEMDUMP_BPL + 2];
	uint8 byte;

	mem[MEMDUMP_BPL] = 0;

	if (!range_args(16 * MEMDUMP_BPL - 1))  // 16 lines unless end address specified
		return;

	do {
		fprintf(fout, "%04lx:", address);
		for (i=0; i<MEMDUMP_BPL; i++, address++) {
			if (address == end_address) done = true;

			fprintf(fout, " %02lx", byte = SAMReadByte(address));
			if ((byte >= ' ') && (byte <= '~'))
				mem[i] = conv_from_64(byte);
			else
				mem[i] = '.';
		}
		fprintf(fout, "  '%s'\n", mem);
	} while (!done && !aborted());
}


/*
 *  ASCII dump
 *  i [start] [end]
 */

#define ASCIIDUMP_BPL 64  // Bytes per line

static void ascii_dump(void)
{
	bool done = false;
	short i;
	uint8 mem[ASCIIDUMP_BPL + 2];
	uint8 byte;

	mem[ASCIIDUMP_BPL] = 0;

	if (!range_args(16 * ASCIIDUMP_BPL - 1))  // 16 lines unless end address specified
		return;

	do {
		fprintf(fout, "%04lx:", address);
		for (i=0; i<ASCIIDUMP_BPL; i++, address++) {
			if (address == end_address) done = true;

			byte = SAMReadByte(address);
			if ((byte >= ' ') && (byte <= '~'))
				mem[i] = conv_from_64(byte);
			else
				mem[i] = '.';
		}
		fprintf(fout, " '%s'\n", mem);
	} while (!done && !aborted());
}


/*
 *  Convert PETSCII->ASCII
 */

static char conv_from_64(char c)
{
	if ((c >= 'A') && (c <= 'Z') || (c >= 'a') && (c <= 'z'))
		return c ^ 0x20;
	else
		return c;
}


/*
 *  Screen code dump
 *  n [start] [end]
 */

#define SCRDUMP_BPL 64  // Bytes per line

static void screen_dump(void)
{
	bool done = false;
	short i;
	uint8 mem[SCRDUMP_BPL + 2];
	uint8 byte;

	mem[SCRDUMP_BPL] = 0;

	if (!range_args(16 * SCRDUMP_BPL - 1))  // 16 Zeilen unless end address specified
		return;

	do {
		fprintf(fout, "%04lx:", address);
		for (i=0; i<SCRDUMP_BPL; i++, address++) {
			if (address == end_address) done = true;

			byte = SAMReadByte(address);
			if (byte < 90)
				mem[i] = conv_from_scode(byte);
			else
				mem[i] = '.';
		}
		fprintf(fout, " '%s'\n", mem);
	} while (!done && !aborted());
}


/*
 *  Convert screen code->ASCII
 */

static char conv_from_scode(char c)
{
	c &= 0x7f;

	if (c <= 31)
		return c + 64;
	else
		if (c >= 64)
			return c + 32;
		else
			return c;
}


/*
 *  Binary dump
 *  b [start] [end]
 */

static void binary_dump(void)
{
	bool done = false;
	char bin[10];

	bin[8] = 0;

	if (!range_args(7))  // 8 lines unless end address specified
		return;

	do {
		if (address == end_address) done = true;

		byte_to_bin(SAMReadByte(address), bin);
		fprintf(fout, "%04lx: %s\n", address++, bin);
	} while (!done && !aborted());
}


/*
 *  Sprite data dump
 *  p [start] [end]
 */

static void sprite_dump(void)
{
	bool done = false;
	short i;
	char bin[10];

	bin[8] = 0;

	if (!range_args(21 * 3 - 1))  // 21 lines unless end address specified
		return;

	do {
		fprintf(fout, "%04lx: ", address);
		for (i=0; i<3; i++, address++) {
			if (address == end_address) done = true;

			byte_to_bin(SAMReadByte(address), bin);
			fprintf(fout, "%s", bin);
		}
		fputc('\n', fout);
	} while (!done && !aborted());
}


/*
 *  Convert byte to binary representation
 */

static void byte_to_bin(uint8 byte, char *str)
{
	short i;

	for (i=0; i<8; i++, byte<<=1)
		if (byte & 0x80)
			str[i] = '#';
		else
			str[i] = '.';
}


/*
 *  Disassemble
 *  d [start] [end]
 */

static void disassemble(void)
{
	bool done = false;
	short i;
	uint8 op[3];
	uint16 adr;

	if (!range_args(31))  // 32 bytes unless end address specified
		return;

	do {
		fprintf(fout, "%04lx:", adr = address);
		for (i=0; i<3; i++, adr++) {
			if (adr == end_address) done = true;
			op[i] = SAMReadByte(adr);
		}
		address += disass_line(address, op[0], op[1], op[2]);
	} while (!done && !aborted());
}


/*
 *  Disassemble one instruction, return length
 */

static int disass_line(uint16 adr, uint8 op, uint8 lo, uint8 hi)
{
	char mode = adr_mode[op], mnem = mnemonic[op];

	// Display instruction bytes in hex
	switch (adr_length[mode]) {
		case 1:
			fprintf(fout, " %02lx       ", op);
			break;

		case 2:
			fprintf(fout, " %02lx %02lx    ", op, lo);
			break;

		case 3:
			fprintf(fout, " %02lx %02lx %02lx ", op, lo, hi);
			break;
	}

	// Tag undocumented opcodes with an asterisk
	if (mnem > M_ILLEGAL)
		fputc('*', fout);
	else
		fputc(' ', fout);

	// Print mnemonic
	fprintf(fout, "%c%c%c ", mnem_1[mnem], mnem_2[mnem], mnem_3[mnem]);

	// Pring argument
	switch (mode) {
		case A_IMPL:
			break;

		case A_ACCU:
			fprintf(fout, "a");
			break;

		case A_IMM:
			fprintf(fout, "#%02lx", lo);
			break;

		case A_REL:
			fprintf(fout, "%04lx", ((adr + 2) + (int8)lo) & 0xffff);
			break;

		case A_ZERO:
			fprintf(fout, "%02lx", lo);
			break;

		case A_ZEROX:
			fprintf(fout, "%02lx,x", lo);
			break;

		case A_ZEROY:
			fprintf(fout, "%02lx,y", lo);
			break;

		case A_ABS:
			fprintf(fout, "%04lx", (hi << 8) | lo);
			break;

		case A_ABSX:
			fprintf(fout, "%04lx,x", (hi << 8) | lo);
			break;

		case A_ABSY:
			fprintf(fout, "%04lx,y", (hi << 8) | lo);
			break;

		case A_IND:
			fprintf(fout, "(%04lx)", (hi << 8) | lo);
			break;

		case A_INDX:
			fprintf(fout, "(%02lx,x)", lo);
			break;

		case A_INDY:
			fprintf(fout, "(%02lx),y", lo);
			break;
	}

	fputc('\n', fout);
	return adr_length[mode];
}


/*
 *  Assemble
 *  a [start]
 */

static void assemble(void)
{
	bool done = false;
	char c1, c2, c3;
	char mnem, mode;
	uint8 opcode;
	uint16 arg;
	int16 rel;

	// Read parameters
	if (!address_args())
		return;

	do {
		fprintf(fout, "%04lx> ", address);
		fflush(ferr);
		read_line();

		c1 = get_char();
		c2 = get_char();
		c3 = get_char();

		if (c1 != '\n') {

			if ((mnem = find_mnemonic(c1, c2, c3)) != M_ILLEGAL) {

				get_token();
				if (instr_args(&arg, &mode)) {

					// Convert A_IMPL -> A_ACCU if necessary
					if ((mode == A_IMPL) && find_opcode(mnem, A_ACCU, &opcode))
						mode = A_ACCU;

					// Handle relative addressing seperately
					if (((mode == A_ABS) || (mode == A_ZERO)) && find_opcode(mnem, A_REL, &opcode)) {
						mode = A_REL;
						rel = arg - (address + 2) & 0xffff;
						if ((rel < -128) || (rel > 127)) {
							error("Branch too long");
							continue;
						} else
							arg = rel & 0xff;
					}

					if (find_opcode(mnem, mode, &opcode)) {

						// Print disassembled line
						fprintf(fout, "\v%04lx:", address);
						disass_line(address, opcode, arg & 0xff, arg >> 8);

						switch (adr_length[mode]) {
							case 1:
								SAMWriteByte(address++, opcode);
								break;

							case 2:
								SAMWriteByte(address++, opcode);
								SAMWriteByte(address++, arg);
								break;

							case 3:
								SAMWriteByte(address++, opcode);
								SAMWriteByte(address++, arg & 0xff);
								SAMWriteByte(address++, arg >> 8);
								break;

							default:
								error("Internal error");
								break;
						}

					} else
						error("Addressing mode not supported by instruction");

				} else
					error("Unrecognized addressing mode");

			} else
				error("Unknown instruction");

		} else			// Input is terminated with a blank line
			done = true;
	} while (!done);
}


/*
 *  Find mnemonic code to three letters
 *  M_ILLEGAL: No matching mnemonic found
 */

static char find_mnemonic(char op1, char op2, char op3)
{
	int i;

	for (i=0; i<M_MAXIMUM; i++)
		if ((mnem_1[i] == op1) && (mnem_2[i] == op2) && (mnem_3[i] == op3))
			return i;

	return M_ILLEGAL;
}


/*
 *  Determine opcode of an instruction given mnemonic and addressing mode
 *  true: OK, false: Mnemonic can't have specified addressing mode
 */

static bool find_opcode(char mnem, char mode, uint8 *opcode)
{
	int i;

	for (i=0; i<256; i++)
		if ((mnemonic[i] == mnem) && (adr_mode[i] == mode)) {
			*opcode = i;
			return true;
		}

	return false;
}


/*
 *  Show/set memory configuration
 *  k [config]
 */

static void mem_config(void)
{
	uint16 con;

	if (the_token != T_END)
		if (!expression(&con))
			return;
		else
			TheCPU->ExtConfig = con;
	else
		con = TheCPU->ExtConfig;

	fprintf(fout, "Configuration: %ld\n", con & 7);
	fprintf(fout, "A000-BFFF: %s\n", (con & 3) == 3 ? "Basic" : "RAM");
	fprintf(fout, "D000-DFFF: %s\n", (con & 3) ? ((con & 4) ? "I/O" : "Char") : "RAM");
	fprintf(fout, "E000-FFFF: %s\n", (con & 2) ? "Kernal" : "RAM");
}


/*
 *  Fill
 *  f start end byte
 */

static void fill(void)
{
	bool done = false;
	uint16 adr, end_adr, value;

	if (!expression(&adr))
		return;
	if (!expression(&end_adr))
		return;
	if (!expression(&value))
		return;

	do {
		if (adr == end_adr) done = true;

		SAMWriteByte(adr++, value);
	} while (!done);
}


/*
 *  Compare
 *  c start end dest
 */

static void compare(void)
{
	bool done = false;
	uint16 adr, end_adr, dest;
	int num = 0;

	if (!expression(&adr))
		return;
	if (!expression(&end_adr))
		return;
	if (!expression(&dest))
		return;

	do {
		if (adr == end_adr) done = true;

		if (SAMReadByte(adr) != SAMReadByte(dest)) {
			fprintf(fout, "%04lx ", adr);
			num++;
			if (!(num & 7))
				fputc('\n', fout);
		}
		adr++; dest++;
	} while (!done && !aborted());

	if (num & 7)
		fputc('\n', fout);
	fprintf(fout, "%ld byte(s) different\n", num);
}


/*
 *  Transfer memory
 *  t start end dest
 */

static void transfer(void)
{
	bool done = false;
	uint16 adr, end_adr, dest;

	if (!expression(&adr))
		return;
	if (!expression(&end_adr))
		return;
	if (!expression(&dest))
		return;

	if (dest < adr)
		do {
			if (adr == end_adr) done = true;
			SAMWriteByte(dest++, SAMReadByte(adr++));
		} while (!done);
	else {
		dest += end_adr - adr;
		do {
			if (adr == end_adr) done = true;
			SAMWriteByte(dest--, SAMReadByte(end_adr--));
		} while (!done);
	}
}


/*
 *  Change memory
 *  : addr {byte}
 */

static void modify(void)
{
	uint16 adr, val;

	if (!expression(&adr))
		return;

	while (the_token != T_END)
		if (expression(&val))
			SAMWriteByte(adr++, val);
		else
			return;
}


/*
 *  Compute and display expression
 *  ? expression
 */

static void print_expr(void)
{
	uint16 val;

	if (!expression(&val))
		return;

	fprintf(fout, "Hex: %04lx\nDec: %lu\n", val, val);
}


/*
 *  Redirect output
 *  o [file]
 */

static void redir_output(void)
{
	// Close old file
	if (fout != ferr) {
		fclose(fout);
		fout = ferr;
		return;
	}

	// No argument given?
	if (the_token == T_END)
		return;

	// Otherwise open file
	if (the_token == T_STRING) {
		if (!(fout = fopen(the_string, "w")))
			error("Unable to open file");
	} else
		error("'\"' around file name expected");
}


/*
 *  Display interrupt vectors
 */

static void int_vectors(void)
{
	fprintf(fout, "        IRQ  BRK  NMI\n");
	fprintf(fout, "%d  : %04lx %04lx %04lx\n",
		access_1541 ? 6502 : 6510,
		SAMReadByte(0xffff) << 8 | SAMReadByte(0xfffe),
		SAMReadByte(0xffff) << 8 | SAMReadByte(0xfffe),
		SAMReadByte(0xfffb) << 8 | SAMReadByte(0xfffa));

	if (!access_1541 && TheCPU->ExtConfig & 2)
		fprintf(fout, "Kernal: %04lx %04lx %04lx\n",
			SAMReadByte(0x0315) << 8 | SAMReadByte(0x0314),
			SAMReadByte(0x0317) << 8 | SAMReadByte(0x0316),
			SAMReadByte(0x0319) << 8 | SAMReadByte(0x0318));
}


/*
 *  Display state of custom chips
 */

static void view_state(void)
{
	switch (get_char()) {
		case 'c':		// CIA
			view_cia_state();
			break;

		case 's':		// SID
			view_sid_state();
			break;

		case 'v':		// VIC
			view_vic_state();
			break;

		case 'f':		// Floppy
			view_1541_state();
			break;

		default:
			error("Unknown command");
			break;
	}
}

static void view_cia_state(void)
{
	MOS6526State cs;

	switch (get_char()) {
		case '1':
			TheCIA1->GetState(&cs);
			break;
		case '2':
			TheCIA2->GetState(&cs);
			break;
		default:
			error("Unknown command");
			return;
	}

	fprintf(fout, "Timer A  : %s\n", cs.cra & 1 ? "On" : "Off");
	fprintf(fout, " Counter : %04lx  Latch: %04lx\n", (cs.ta_hi << 8) | cs.ta_lo, cs.latcha);
	fprintf(fout, " Run mode: %s\n", cs.cra & 8 ? "One-shot" : "Continuous");
	fprintf(fout, " Input   : %s\n", cs.cra & 0x20 ? "CNT" : "Phi2");
	fprintf(fout, " Output  : ");
	if (cs.cra & 2)
		if (cs.cra & 4)
			fprintf(fout, "PB6 Toggle\n\n");
		else
			fprintf(fout, "PB6 Pulse\n\n");
	else
		fprintf(fout, "None\n\n");

	fprintf(fout, "Timer B  : %s\n", cs.crb & 1 ? "On" : "Off");
	fprintf(fout, " Counter : %04lx  Latch: %04lx\n", (cs.tb_hi << 8) | cs.tb_lo, cs.latchb);
	fprintf(fout, " Run mode: %s\n", cs.crb & 8 ? "One-shot" : "Continuous");
	fprintf(fout, " Input   : ");
	if (cs.crb & 0x40)
		if (cs.crb & 0x20)
			fprintf(fout, "Timer A underflow (CNT high)\n");
		else
			fprintf(fout, "Timer A underflow\n");
	else
		if (cs.crb & 0x20)
			fprintf(fout, "CNT\n");
		else
			fprintf(fout, "Phi2\n");
	fprintf(fout, " Output  : ");
	if (cs.crb & 2)
		if (cs.crb & 4)
			fprintf(fout, "PB7 Toggle\n\n");
		else
			fprintf(fout, "PB7 Pulse\n\n");
	else
		fprintf(fout, "None\n\n");

	fprintf(fout, "TOD         : %lx%lx:%lx%lx:%lx%lx.%lx %s\n",
		(cs.tod_hr >> 4) & 1, cs.tod_hr & 0x0f,
		(cs.tod_min >> 4) & 7, cs.tod_min & 0x0f,
		(cs.tod_sec >> 4) & 7, cs.tod_sec & 0x0f,
		cs.tod_10ths & 0x0f, cs.tod_hr & 0x80 ? "PM" : "AM");
	fprintf(fout, "Alarm       : %lx%lx:%lx%lx:%lx%lx.%lx %s\n",
		(cs.alm_hr >> 4) & 1, cs.alm_hr & 0x0f,
		(cs.alm_min >> 4) & 7, cs.alm_min & 0x0f,
		(cs.alm_sec >> 4) & 7, cs.alm_sec & 0x0f,
		cs.alm_10ths & 0x0f, cs.alm_hr & 0x80 ? "PM" : "AM");
	fprintf(fout, "TOD input   : %s\n", cs.cra & 0x80 ? "50Hz" : "60Hz");
	fprintf(fout, "Write to    : %s registers\n\n", cs.crb & 0x80 ? "Alarm" : "TOD");

	fprintf(fout, "Serial data : %02lx\n", cs.sdr);
	fprintf(fout, "Serial mode : %s\n\n", cs.cra & 0x40 ? "Output" : "Input");

	fprintf(fout, "Pending int.: ");
	dump_cia_ints(cs.int_data);
	fprintf(fout, "Enabled int.: ");
	dump_cia_ints(cs.int_mask);
}

static void dump_cia_ints(uint8 i)
{
	if (i & 0x1f) {
		if (i & 1) fprintf(fout, "TA ");
		if (i & 2) fprintf(fout, "TB ");
		if (i & 4) fprintf(fout, "Alarm ");
		if (i & 8) fprintf(fout, "Serial ");
		if (i & 0x10) fprintf(fout, "Flag");
	} else
		fprintf(fout, "None");
	fputc('\n', fout);
}

static void view_sid_state(void)
{
	MOS6581State ss;

	TheSID->GetState(&ss);

	fprintf(fout, "Voice 1\n");
	fprintf(fout, " Frequency  : %04lx\n", (ss.freq_hi_1 << 8) | ss.freq_lo_1);
	fprintf(fout, " Pulse Width: %04lx\n", ((ss.pw_hi_1 & 0x0f) << 8) | ss.pw_lo_1);
	fprintf(fout, " Env. (ADSR): %lx %lx %lx %lx\n", ss.AD_1 >> 4, ss.AD_1 & 0x0f, ss.SR_1 >> 4, ss.SR_1 & 0x0f);
	fprintf(fout, " Waveform   : ");
	dump_sid_waveform(ss.ctrl_1);
	fprintf(fout, " Gate       : %s  Ring mod.: %s\n", ss.ctrl_1 & 0x01 ? "On " : "Off", ss.ctrl_1 & 0x04 ? "On" : "Off");
	fprintf(fout, " Test bit   : %s  Synchron.: %s\n", ss.ctrl_1 & 0x08 ? "On " : "Off", ss.ctrl_1 & 0x02 ? "On" : "Off");
	fprintf(fout, " Filter     : %s\n", ss.res_filt & 0x01 ? "On" : "Off");

	fprintf(fout, "\nVoice 2\n");
	fprintf(fout, " Frequency  : %04lx\n", (ss.freq_hi_2 << 8) | ss.freq_lo_2);
	fprintf(fout, " Pulse Width: %04lx\n", ((ss.pw_hi_2 & 0x0f) << 8) | ss.pw_lo_2);
	fprintf(fout, " Env. (ADSR): %lx %lx %lx %lx\n", ss.AD_2 >> 4, ss.AD_2 & 0x0f, ss.SR_2 >> 4, ss.SR_2 & 0x0f);
	fprintf(fout, " Waveform   : ");
	dump_sid_waveform(ss.ctrl_2);
	fprintf(fout, " Gate       : %s  Ring mod.: %s\n", ss.ctrl_2 & 0x01 ? "On " : "Off", ss.ctrl_2 & 0x04 ? "On" : "Off");
	fprintf(fout, " Test bit   : %s  Synchron.: %s\n", ss.ctrl_2 & 0x08 ? "On " : "Off", ss.ctrl_2 & 0x02 ? "On" : "Off");
	fprintf(fout, " Filter     : %s\n", ss.res_filt & 0x02 ? "On" : "Off");

	fprintf(fout, "\nVoice 3\n");
	fprintf(fout, " Frequency  : %04lx\n", (ss.freq_hi_3 << 8) | ss.freq_lo_3);
	fprintf(fout, " Pulse Width: %04lx\n", ((ss.pw_hi_3 & 0x0f) << 8) | ss.pw_lo_3);
	fprintf(fout, " Env. (ADSR): %lx %lx %lx %lx\n", ss.AD_3 >> 4, ss.AD_3 & 0x0f, ss.SR_3 >> 4, ss.SR_3 & 0x0f);
	fprintf(fout, " Waveform   : ");
	dump_sid_waveform(ss.ctrl_3);
	fprintf(fout, " Gate       : %s  Ring mod.: %s\n", ss.ctrl_3 & 0x01 ? "On " : "Off", ss.ctrl_3 & 0x04 ? "On" : "Off");
	fprintf(fout, " Test bit   : %s  Synchron.: %s\n", ss.ctrl_3 & 0x08 ? "On " : "Off", ss.ctrl_3 & 0x02 ? "On" : "Off");
	fprintf(fout, " Filter     : %s  Mute     : %s\n", ss.res_filt & 0x04 ? "On" : "Off", ss.mode_vol & 0x80 ? "Yes" : "No");

	fprintf(fout, "\nFilters/Volume\n");
	fprintf(fout, " Frequency: %04lx\n", (ss.fc_hi << 3) | (ss.fc_lo & 0x07));
	fprintf(fout, " Resonance: %lx\n", ss.res_filt >> 4);
	fprintf(fout, " Mode     : ");
	if (ss.mode_vol & 0x70) {
		if (ss.mode_vol & 0x10) fprintf(fout, "Low-pass ");
		if (ss.mode_vol & 0x20) fprintf(fout, "Band-pass ");
		if (ss.mode_vol & 0x40) fprintf(fout, "High-pass");
	} else
		fprintf(fout, "None");
	fprintf(fout, "\n Volume   : %lx\n", ss.mode_vol & 0x0f);
}

static void dump_sid_waveform(uint8 wave)
{
	if (wave & 0xf0) {
		if (wave & 0x10) fprintf(fout, "Triangle ");
		if (wave & 0x20) fprintf(fout, "Sawtooth ");
		if (wave & 0x40) fprintf(fout, "Rectangle ");
		if (wave & 0x80) fprintf(fout, "Noise");
	} else
		fprintf(fout, "None");
	fputc('\n', fout);
}

static void view_vic_state(void)
{
	MOS6569State vs;
	short i;

	TheVIC->GetState(&vs);

	fprintf(fout, "Raster line       : %04lx\n", vs.raster | ((vs.ctrl1 & 0x80) << 1));
	fprintf(fout, "IRQ raster line   : %04lx\n\n", vs.irq_raster);

	fprintf(fout, "X scroll          : %ld\n", vs.ctrl2 & 7);
	fprintf(fout, "Y scroll          : %ld\n", vs.ctrl1 & 7);
	fprintf(fout, "Horizontal border : %ld columns\n", vs.ctrl2 & 8 ? 40 : 38);
	fprintf(fout, "Vertical border   : %ld rows\n\n", vs.ctrl1 & 8 ? 25 : 24);

	fprintf(fout, "Display mode      : ");
	switch (((vs.ctrl1 >> 4) & 6) | ((vs.ctrl2 >> 4) & 1)) {
		case 0:
			fprintf(fout, "Standard text\n");
			break;
		case 1:
			fprintf(fout, "Multicolor text\n");
			break;
		case 2:
			fprintf(fout, "Standard bitmap\n");
			break;
		case 3:
			fprintf(fout, "Multicolor bitmap\n");
			break;
		case 4:
			fprintf(fout, "ECM text\n");
			break;
		case 5:
			fprintf(fout, "Invalid text (ECM+MCM)\n");
			break;
		case 6:
			fprintf(fout, "Invalid bitmap (ECM+BMM)\n");
			break;
		case 7:
			fprintf(fout, "Invalid bitmap (ECM+BMM+ECM)\n");
			break;
	}
	fprintf(fout, "Sequencer state   : %s\n", vs.display_state ? "Display" : "Idle");
	fprintf(fout, "Bad line state    : %s\n", vs.bad_line ? "Yes" : "No");
	fprintf(fout, "Bad lines enabled : %s\n", vs.bad_line_enable ? "Yes" : "No");
	fprintf(fout, "Video counter     : %04lx\n", vs.vc);
	fprintf(fout, "Video counter base: %04lx\n", vs.vc_base);
	fprintf(fout, "Row counter       : %ld\n\n", vs.rc);

	fprintf(fout, "VIC bank          : %04lx-%04lx\n", vs.bank_base, vs.bank_base + 0x3fff);
	fprintf(fout, "Video matrix base : %04lx\n", vs.matrix_base);
	fprintf(fout, "Character base    : %04lx\n", vs.char_base);
	fprintf(fout, "Bitmap base       : %04lx\n\n", vs.bitmap_base);

	fprintf(fout, "         Spr.0  Spr.1  Spr.2  Spr.3  Spr.4  Spr.5  Spr.6  Spr.7\n");
	fprintf(fout, "Enabled: "); dump_spr_flags(vs.me);
	fprintf(fout, "Data   : %04lx   %04lx   %04lx   %04lx   %04lx   %04lx   %04lx   %04lx\n",
		vs.sprite_base[0], vs.sprite_base[1], vs.sprite_base[2], vs.sprite_base[3],
		vs.sprite_base[4], vs.sprite_base[5], vs.sprite_base[6], vs.sprite_base[7]);
	fprintf(fout, "MC     : %02lx     %02lx     %02lx     %02lx     %02lx     %02lx     %02lx     %02lx\n",
		vs.mc[0], vs.mc[1], vs.mc[2], vs.mc[3], vs.mc[4], vs.mc[5], vs.mc[6], vs.mc[7]);

	fprintf(fout, "Mode   : ");
	for (i=0; i<8; i++)
		if (vs.mmc & (1<<i))
			fprintf(fout, "Multi  ");
		else
			fprintf(fout, "Std.   ");

	fprintf(fout, "\nX-Exp. : "); dump_spr_flags(vs.mxe);
	fprintf(fout, "Y-Exp. : "); dump_spr_flags(vs.mye);

	fprintf(fout, "Prio.  : ");
	for (i=0; i<8; i++)
		if (vs.mdp & (1<<i))
			fprintf(fout, "Back   ");
		else
			fprintf(fout, "Fore   ");

	fprintf(fout, "\nSS Coll: "); dump_spr_flags(vs.mm);
	fprintf(fout, "SD Coll: "); dump_spr_flags(vs.md);

	fprintf(fout, "\nPending interrupts: ");
	dump_vic_ints(vs.irq_flag);
	fprintf(fout, "Enabled interrupts: ");
	dump_vic_ints(vs.irq_mask);
}

static void dump_spr_flags(uint8 f)
{
	short i;

	for (i=0; i<8; i++)
		if (f & (1<<i))
			fprintf(fout, "Yes    ");
		else
			fprintf(fout, "No     ");

	fputc('\n', fout);
}

static void dump_vic_ints(uint8 i)
{
	if (i & 0x1f) {
		if (i & 1) fprintf(fout, "Raster ");
		if (i & 2) fprintf(fout, "Spr-Data ");
		if (i & 4) fprintf(fout, "Spr-Spr ");
		if (i & 8) fprintf(fout, "Lightpen");
	} else
		fprintf(fout, "None");
	fputc('\n', fout);
}

static void view_1541_state(void)
{
	fprintf(fout, "VIA 1:\n");
	fprintf(fout, " Timer 1 Counter: %04x  Latch: %04x\n", R1541.via1_t1c, R1541.via1_t1l);
	fprintf(fout, " Timer 2 Counter: %04x  Latch: %04x\n", R1541.via1_t2c, R1541.via1_t2l);
	fprintf(fout, " ACR: %02x\n", R1541.via1_acr);
	fprintf(fout, " PCR: %02x\n", R1541.via1_pcr);
	fprintf(fout, " Pending interrupts: ");
	dump_via_ints(R1541.via1_ifr);
	fprintf(fout, " Enabled interrupts: ");
	dump_via_ints(R1541.via1_ier);

	fprintf(fout, "\nVIA 2:\n");
	fprintf(fout, " Timer 1 Counter: %04x  Latch: %04x\n", R1541.via2_t1c, R1541.via2_t1l);
	fprintf(fout, " Timer 2 Counter: %04x  Latch: %04x\n", R1541.via2_t2c, R1541.via2_t2l);
	fprintf(fout, " ACR: %02x\n", R1541.via2_acr);
	fprintf(fout, " PCR: %02x\n", R1541.via2_pcr);
	fprintf(fout, " Pending interrupts: ");
	dump_via_ints(R1541.via2_ifr);
	fprintf(fout, " Enabled interrupts: ");
	dump_via_ints(R1541.via2_ier);
}

static void dump_via_ints(uint8 i)
{
	if (i & 0x7f) {
		if (i & 0x40) fprintf(fout, "T1 ");
		if (i & 0x20) fprintf(fout, "T2 ");
		if (i & 2) fprintf(fout, "CA1 ");
		if (i & 1) fprintf(fout, "CA2 ");
		if (i & 0x10) fprintf(fout, "CB1 ");
		if (i & 8) fprintf(fout, "CB2 ");
		if (i & 4) fprintf(fout, "Serial ");
	} else
		fprintf(fout, "None");
	fputc('\n', fout);
}


/*
 *  Load data
 *  l start "file"
 */

static void load_data(void)
{
	uint16 adr;
	FILE *file;
	int fc;

	if (!expression(&adr))
		return;
	if (the_token == T_END) {
		error("Missing file name");
		return;
	}
	if (the_token != T_STRING) {
		error("'\"' around file name expected");
		return;
	}

	if (!(file = fopen(the_string, "rb")))
		error("Unable to open file");
	else {
		while ((fc = fgetc(file)) != EOF)
			SAMWriteByte(adr++, fc);
		fclose(file);
	}
}


/*
 *  Save data
 *  s start end "file"
 */

static void save_data(void)
{
	bool done = false;
	uint16 adr, end_adr;
	FILE *file;

	if (!expression(&adr))
		return;
	if (!expression(&end_adr))
		return;
	if (the_token == T_END) {
		error("Missing file name");
		return;
	}
	if (the_token != T_STRING) {
		error("'\"' around file name expected");
		return;
	}

	if (!(file = fopen(the_string, "wb")))
		error("Unable to create file");
	else {
		do {
			if (adr == end_adr) done = true;

			fputc(SAMReadByte(adr++), file);
		} while (!done);
		fclose(file);
	}
}
