/*
 *  SAM.c - Simple Assembler and Monitor With Integrated System Explorer
 *
 *  Copyright (C) 1994-1996 by Christian Bauer
 */

#include <exec/types.h>
#include <clib/dos_protos.h>
#include <clib/utility_protos.h>

#include "SAM.h"
#include "6526.h"
#include "6581.h"
#include "6569.h"


// Aus 6510.asm
extern UBYTE RA, RX, RY, RP, RPR, RDDR;
extern UWORD RPC, RS;
extern UBYTE SAMReadByte(UWORD address);
extern void SAMWriteByte(UWORD address, UBYTE byte);
extern UBYTE SAMMemConfig;


// Prototypes
void error(char *s);			// Scanner
BOOL aborted(void);
void read_line(void);
char get_char(void);
void put_back(char c);
enum Token get_token(void);
enum RegToken get_reg_token(void);
UWORD get_number(void);
enum Token get_string(char *str);

BOOL expression(UWORD *number);	// Parser
BOOL term(UWORD *number);
BOOL factor(UWORD *number);
BOOL address_args(void);
BOOL range_args(int def_range);
BOOL instr_args(UWORD *number, char *mode);

void help(void);				// Routinen für die Befehle
void registers(void);
void display_registers(void);
void memory_dump(void);
void ascii_dump(void);
char conv_from_64(char c);
void screen_dump(void);
char conv_from_scode(char c);
void binary_dump(void);
void sprite_dump(void);
void byte_to_bin(UBYTE byte, char *str);
void disassemble(void);
int disass_line(UWORD adr, UBYTE op, UBYTE lo, UBYTE hi);
void assemble(void);
char find_mnemonic(char op1, char op2, char op3);
BOOL find_opcode(char mnem, char mode, UBYTE *opcode);
void mem_config(void);
void fill(void);
void compare(void);
void transfer(void);
void modify(void);
void print_expr(void);
void redir_output(void);
void int_vectors(void);
void view_state(void);
void view_cia_state(void);
void dump_cia_ints(UBYTE int);
void view_sid_state(void);
void dump_sid_waveform(UBYTE wave);
void view_vic_state(void);
void dump_spr_flags(UBYTE f);
void dump_vic_ints(UBYTE int);
void load_data(void);
void save_data(void);


// FileHandles für Eingabe, Ausgabe und Fehler
BPTR fin, fout, ferr;

// Eingabezeile
#define INPUT_LENGTH 80
char input[INPUT_LENGTH];
char *in_ptr;

UWORD address, end_address;

// Eingabetoken
enum Token {
  T_NULL,		// Ungültiges Token
  T_END,		// Ende der Zeile
  T_NUMBER,		// Hexadezimalzahl
  T_STRING,     // In "" eingeschlossener String
  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'
};

// Registernamen
enum RegToken {
  RT_NULL,		// Ungültiges Token
  RT_END,		// Ende der Zeile
  RT_A,			// 'a'
  RT_X,			// 'x'
  RT_Y,			// 'y'
  RT_PC,		// 'pc'
  RT_SP,		// 'sp'
  RT_DR,		// 'dr'
  RT_PR			// 'pr'
};

enum Token the_token;			// Auch RegToken für get_reg_token
UWORD the_number;				// Enthält die Zahl, wenn the_token==T_NUMBER
char the_string[INPUT_LENGTH];	// Enthält den String, wenn the_token==T_STRING

// Adressierungsarten
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,  // Ab hier kommen die undokumentierten Opcodes

  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  // Höchstes Element
};

// Zu jedem Opcode das Mnemonic
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
};

// Zu jedem Opcode die Adressierungsart
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
};

// Zu jedem Mnemonic die Zeichen
const char mnem_1[] = "aaabbbbbbbbbbcccccccdddeiiijjllllnopppprrrrssssssstttttt?aaaadijnlllrrsssssssss";
const char mnem_2[] = "dnscceimnprvvllllmppeeeonnnmsdddsorhhlloottbeeetttaasxxy?nnrscsaoaaxlrabbhhhhlr";
const char mnem_3[] = "cdlcsqtielkcscdivpxycxyrcxypraxyrpaapaplrisccdiaxyxyxasa?cerrpbmpsxaaaxcxasxyoe";

// Zu jeder Adressierungsart die Befehlslänge
const char adr_length[] = {1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 2, 2};


/*
 *  SAM öffnen und handhaben
 */

void SAM(void)
{
  BOOL done = FALSE;
  char c;

  if (fin = fout = ferr = Open("CON:0/0/640/480/SAM", MODE_NEWFILE)) {
    FPuts(ferr, "\n *** SAM - Simple Assembler and Monitor ***\n ***         Press 'h' for help         ***\n\n");
    display_registers();

    address = RPC;

    while (!done) {
      Write(ferr, "> ", 2);
      read_line();
      while ((c = get_char()) == ' ') ;

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

        case 'b':		// Binär-Dump
          get_token();
          binary_dump();
          break;

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

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

        case 'e':       // Interrupt-Vektoren
          int_vectors();
          break;

        case 'f':		// Füllen
          get_token();
          fill();
          break;

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

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

        case 'k':		// Speicherkonfiguration
          get_token();
          mem_config();
          break;

        case 'l':		// Daten laden
          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':		// Ausgabe umleiten
          get_token();
          redir_output();
          break;

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

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

        case 's':		// Daten speichern
          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 ':':		// Ändern
          get_token();
          modify();
          break;

        case '?':		// Ausdruck berechnen
          get_token();
          print_expr();
          break;

        case '\n':		// Leerzeile
          break;

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

    Close(fin);
    if (fout != ferr)
      Close(fout);
  }
}


/*
 *  Fehlermeldung ausgeben
 */

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


/*
 *  CTRL-C gedrückt?
 */

#include <dos/dos.h>
#include <clib/exec_protos.h>
BOOL aborted(void)
{
  return SetSignal(0, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C;
}


/*
 *  Eine Zeile von der Tastatur lesen
 */

void read_line(void)
{
  FGets(fin, in_ptr = input, INPUT_LENGTH);
}


/*
 *  Ein Zeichen aus der Eingabezeile lesen
 */

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


/*
 *  Zeichen in die Eingabezeile zurückpacken
 */

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


/*
 *  Scanner: Ein Token aus der Eingabezeile lesen
 */

enum Token get_token(void)
{
  char c;

  // Leerzeichen überspringen
  while ((c = get_char()) == ' ') ;

  switch (ToLower(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 (ToLower(get_char()) == 'c')
        return the_token = T_PC;
      else {
        error("Unrecognized token");
        return the_token = T_NULL;
      }
    case 's':
      if (ToLower(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;
  }
}

enum RegToken get_reg_token(void)
{
  char c;

  // Leerzeichen überspringen
  while ((c = get_char()) == ' ') ;

  switch (ToLower(c)) {
    case '\n':
      return the_token = RT_END;
    case 'a':
      return the_token = RT_A;
    case 'd':
      if (ToLower(get_char()) == 'r')
        return the_token = RT_DR;
      else {
        error("Unrecognized token");
        return the_token = RT_NULL;
      }
    case 'p':
      if ((c = ToLower(get_char())) == 'c')
        return the_token = RT_PC;
      else if (c == 'r')
        return the_token = RT_PR;
      else {
        error("Unrecognized token");
        return the_token = RT_NULL;
      }
    case 's':
      if (ToLower(get_char()) == 'p')
        return the_token = RT_SP;
      else {
        error("Unrecognized token");
        return the_token = RT_NULL;
      }
    case 'x':
      return the_token = RT_X;
    case 'y':
      return the_token = RT_Y;
    default:
      error("Unrecognized token");
      return the_token = RT_NULL;
  }
}

UWORD get_number(void)
{
  char c;
  UWORD i = 0;

  while (((c = ToLower(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;
}

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: Fehler
 */

BOOL expression(UWORD *number)
{
  UWORD 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: Fehler
 */

BOOL term(UWORD *number)
{
  UWORD 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: Fehler
 */

BOOL factor(UWORD *number)
{
  switch (the_token) {
    case T_NUMBER:
      *number = the_number;
      get_token();
      return TRUE;

    case T_PC:
      get_token();
      *number = RPC;
      return TRUE;

    case T_SP:
      get_token();
      *number = RS;
      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
 *
 *  Startadresse nach address lesen
 *
 *  TRUE: OK, FALSE: Fehler
 */

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
 *
 *  Startadresse nach address, Endadresse nach end_address lesen
 *
 *  TRUE: OK, FALSE: Fehler
 */

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
 *
 *  Argumente eines 6510-Befehls lesen, Adresse und Adressierungsart ermitteln
 *
 *  TRUE: OK, FALSE: Fehler
 */

BOOL instr_args(UWORD *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;
  }
}


/*
 *  Hilfstext anzeigen
 *  ?
 */

void help(void)
{
  FPuts(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 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 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"
              "vs                  View SID state\n"
              "vv                  View VIC state\n"
              "x                   Return to Frodo\n"
              ": addr {byte}       Modify memory\n"
              "? expression        Calculate expression\n");
}


/*
 *  6510-Register anzeigen/ändern
 *  r [reg value]
 */

void registers(void)
{
  enum RegToken the_reg;
  UWORD value;

  if (the_token != RT_END)
    switch (the_reg = the_token) {
      case RT_A:
      case RT_X:
      case RT_Y:
      case RT_PC:
      case RT_SP:
      case RT_DR:
      case RT_PR:
        get_token();
        if (!expression(&value))
          return;

        switch (the_reg) {
          case RT_A:
            RA = value;
            break;
          case RT_X:
            RX = value;
            break;
          case RT_Y:
            RY = value;
            break;
          case RT_PC:
            RPC = value;
            break;
          case RT_SP:
            RS = (value & 0xff) | 0x0100;
            break;
          case RT_DR:
            RDDR = value;
            break;
          case RT_PR:
            RPR = value;
            break;
        }
        break;

      default:
        return;
    }

  display_registers();
}

void display_registers(void)
{
  FPuts(fout, " PC  A  X  Y   SP  DR PR NVDIZC  Instruction\n");
  FPrintf(fout, "%04lx %02lx %02lx %02lx %04lx %02lx %02lx %lc%lc%lc%lc%lc%lc ",
    RPC, RA, RX, RY, RS, RDDR, RPR,
    RP & 0x80 ? '1' : '0', RP & 0x40 ? '1' : '0', RP & 0x08 ? '1' : '0',
    RP & 0x04 ? '1' : '0', RP & 0x02 ? '1' : '0', RP & 0x01 ? '1' : '0');
  disass_line(RPC, SAMReadByte(RPC), SAMReadByte(RPC+1), SAMReadByte(RPC+2));
}


/*
 *  Memory-Dump
 *  m [start] [end]
 */

#define MEMDUMP_BPL 16  // Bytes pro Zeile

void memory_dump(void)
{
  BOOL done = FALSE;
  short i;
  UBYTE mem[MEMDUMP_BPL + 2];
  UBYTE byte;

  mem[MEMDUMP_BPL] = 0;

  if (!range_args(16 * MEMDUMP_BPL - 1))  // 16 Zeilen, wenn keine Endadresse angegeben
    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 pro Zeile

void ascii_dump(void)
{
  BOOL done = FALSE;
  short i;
  UBYTE mem[ASCIIDUMP_BPL + 2];
  UBYTE byte;

  mem[ASCIIDUMP_BPL] = 0;

  if (!range_args(16 * ASCIIDUMP_BPL - 1))  // 16 Zeilen, wenn keine Endadresse angegeben
    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());
}


/*
 *  Umwandlung PETSCII->ASCII
 */

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 pro Zeile

void screen_dump(void)
{
  BOOL done = FALSE;
  short i;
  UBYTE mem[SCRDUMP_BPL + 2];
  UBYTE byte;

  mem[SCRDUMP_BPL] = 0;

  if (!range_args(16 * SCRDUMP_BPL - 1))  // 16 Zeilen, wenn keine Endadresse angegeben
    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());
}


/*
 *  Umwandlung Bildschirmcode->ASCII
 */

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

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


/*
 *  Binär-Dump
 *  b [start] [end]
 */

void binary_dump(void)
{
  BOOL done = FALSE;
  char bin[10];

  bin[8] = 0;

  if (!range_args(7))  // 8 Zeilen, wenn keine Endadresse angegeben
    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-Daten-Dump
 *  p [start] [end]
 */

void sprite_dump(void)
{
  BOOL done = FALSE;
  short i;
  char bin[10];

  bin[8] = 0;

  if (!range_args(21 * 3 - 1))  // 21 Zeilen, wenn keine Endadresse angegeben
    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(fout, '\n');
  } while (!done && !aborted());
}


/*
 *  Ein Byte in Binärfolge umwandeln
 */

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

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


/*
 *  Disassemblieren
 *  d [start] [end]
 */

void disassemble(void)
{
  BOOL done = FALSE;
  short i;
  UBYTE op[3];
  UWORD adr;

  if (!range_args(31))  // 32 Bytes, wenn keine Endadresse angegeben
    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());
}


/*
 *  Einen Befehl disassemblieren, Länge zurückgeben
 */

int disass_line(UWORD adr, UBYTE op, UBYTE lo, UBYTE hi)
{
  char mode = adr_mode[op], mnem = mnemonic[op];

  // Befehlsbytes hexadezimal ausgeben
  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;
  }

  // Undokumentierte Opcodes mit Stern markieren
  if (mnem > M_ILLEGAL)
    FPutC(fout, '*');
  else
    FPutC(fout, ' ');

  // Mnemonic ausgeben
  FPrintf(fout, "%lc%lc%lc ", mnem_1[mnem], mnem_2[mnem], mnem_3[mnem]);

  // Argument ausgeben
  switch (mode) {
    case A_IMPL:
      break;

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

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

    case A_REL:
      FPrintf(fout, "%04lx", ((adr + 2) + (BYTE)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(fout, '\n');
  return adr_length[mode];
}


/*
 *  Assemblieren
 *  a [start]
 */

void assemble(void)
{
  BOOL done = FALSE;
  char c1, c2, c3;
  char mnem, mode;
  UBYTE opcode;
  UWORD arg;
  WORD rel;

  // Parameter lesen
  if (!address_args())
    return;

  do {
    FPrintf(fout, "%04lx> ", address);
    read_line();

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

    if (c1 != '\n') {

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

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

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

          // Relative Adressierung getrennt behandeln
          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)) {

            // Disassemblierte Zeile ausgeben
            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	// Adressierungsart paßt nicht zum Befehl
            error("Addressing mode not supported by instruction");

        } else	// Nicht erkannte Adressierungsart
          error("Unrecognized addressing mode");

      } else		// Mnemonic nicht gefunden
        error("Unknown instruction");

    } else			// Leerzeile beendet die Eingabe
      done = TRUE;
  } while (!done);
}


/*
 *  Zu drei Buchstaben den passenden Mnemonic-Code finden
 *  M_ILLEGAL: Kein passendes Mnemonic gefunden
 */

char find_mnemonic(char op1, char op2, char op3)
{
  int i;
  char c1, c2, c3;

  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;
}


/*
 *  Opcode eines Befehls ermitteln, Mnemonic und Adressierungsart gegeben
 *  TRUE: OK, FALSE: Mnemonic kommt mit dieser Adressierungsart nicht vor
 */

BOOL find_opcode(char mnem, char mode, UBYTE *opcode)
{
  int i;

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

  return FALSE;
}


/*
 *  Speicherkonfiguration anzeigen/setzen
 *  k [config]
 */

void mem_config(void)
{
  UWORD con;

  if (the_token != T_END)
    if (!expression(&con))
      return;
    else
      SAMMemConfig = con;
  else
    con = SAMMemConfig;

  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");
}


/*
 *  Füllen
 *  k start end byte
 */

void fill(void)
{
  BOOL done = FALSE;
  UWORD 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);
}


/*
 *  Vergleichen
 *  c start end dest
 */

void compare(void)
{
  BOOL done = FALSE;
  UWORD 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(fout, '\n');
    }
    adr++; dest++;
  } while (!done && !aborted());

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


/*
 *  Kopieren
 *  t start end dest
 */

void transfer(void)
{
  BOOL done = FALSE;
  UWORD 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);
  }
}


/*
 *  Speicher ändern
 *  : addr {byte}
 */

void modify(void)
{
  UWORD adr, val;

  if (!expression(&adr))
    return;

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


/*
 *  Ausdruck berechnen und anzeigen
 *  ? expression
 */

void print_expr(void)
{
  UWORD val;

  if (!expression(&val))
    return;

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


/*
 *  Ausgabe umleiten
 *  o ["file"]
 */

void redir_output(void)
{
  // Alte Datei schließen
  if (fout != ferr) {
    Close(fout);
    fout = ferr;
    return;
  }

  // Kein Argument angegeben, dann weiter nichts tun
  if (the_token == T_END)
    return;

  // Sonst Datei öffnen
  if (the_token == T_STRING) {
    if (!(fout = Open(the_string, MODE_NEWFILE)))
      error("Unable to open file");
  } else
    error("'\"' around file name expected");
}


/*
 *  Interrupt-Vektoren anzeigen
 */

void int_vectors(void)
{
  FPuts(fout, "        IRQ  BRK  NMI\n");
  FPrintf(fout, "6510  : %04lx %04lx %04lx\n",
    SAMReadByte(0xffff) << 8 | SAMReadByte(0xfffe),
    SAMReadByte(0xffff) << 8 | SAMReadByte(0xfffe),
    SAMReadByte(0xfffb) << 8 | SAMReadByte(0xfffa));

  if (SAMMemConfig & 2)
    FPrintf(fout, "Kernal: %04lx %04lx %04lx\n",
      SAMReadByte(0x0315) << 8 | SAMReadByte(0x0314),
      SAMReadByte(0x0317) << 8 | SAMReadByte(0x0316),
      SAMReadByte(0x0319) << 8 | SAMReadByte(0x0318));
}


/*
 *  Zustand der Customchips anzeigen
 */

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;

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

void view_cia_state(void)
{
  CIADump cd;

  switch (get_char()) {
    case '1':
      GetCIA1Dump(&cd);
      break;
    case '2':
      GetCIA2Dump(&cd);
      break;
    default:
      error("Unknown command");
      return;
  }

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

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

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

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

  FPuts(fout, "Pending int.: ");
  dump_cia_ints(cd.int_data);
  FPuts(fout, "Enabled int.: ");
  dump_cia_ints(cd.int_mask);
}

void dump_cia_ints(UBYTE int)
{
  if (int & 0x1f) {
    if (int & 1) FPuts(fout, "TA ");
    if (int & 2) FPuts(fout, "TB ");
    if (int & 4) FPuts(fout, "Alarm ");
    if (int & 8) FPuts(fout, "Serial ");
    if (int & 0x10) FPuts(fout, "Flag");
  } else
    FPuts(fout, "None");
  FPutC(fout, '\n');
}

void view_sid_state(void)
{
  SIDDump sd;

  GetSIDDump(&sd);

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

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

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

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

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

void view_vic_state(void)
{
  VICDump vd;
  short i;

  GetVICDump(&vd);

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

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

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

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

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

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

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

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

  FPuts(fout, "\n\nPending interrupts: ");
  dump_vic_ints(vd.irq_flag);
  FPuts(fout, "Enabled interrupts: ");
  dump_vic_ints(vd.irq_mask);
}

void dump_spr_flags(UBYTE f)
{
  short i;

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

  FPutC(fout, '\n');
}

void dump_vic_ints(UBYTE int)
{
  if (int & 0x1f) {
    if (int & 1) FPuts(fout, "Raster ");
    if (int & 2) FPuts(fout, "Spr-Data ");
    if (int & 4) FPuts(fout, "Spr-Spr ");
    if (int & 8) FPuts(fout, "Lightpen");
  } else
    FPuts(fout, "None");
  FPutC(fout, '\n');
}


/*
 *  Daten laden
 *  l start "file"
 */

void load_data(void)
{
  UWORD adr;
  BPTR file;
  LONG 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 = Open(the_string, MODE_OLDFILE)))
    error("Unable to open file");
  else {
    while ((fc = FGetC(file)) >= 0)
      SAMWriteByte(adr++, fc);
    Close(file);
  }
}


/*
 *  Daten speichern
 *  s start end "file"
 */

void save_data(void)
{
  BOOL done = FALSE;
  UWORD adr, end_adr;
  BPTR 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 = Open(the_string, MODE_NEWFILE)))
    error("Unable to create file");
  else {
    do {
      if (adr == end_adr) done = TRUE;

      FPutC(file, SAMReadByte(adr++));
    } while (!done);
    Close(file);
  }
}
