/* Soft64View.m - Commodore 64 Emulator for NeXT */
/* Neil Franklin, Morgenweg 8, 8404 Winterthur */
/* version (last revision) 2.5.93 */

/* Soft64View Object */

#import "Soft64View.h"
#import <appkit/Application.h>            /* NXApp */
#import <appkit/OpenPanel.h>

@implementation Soft64View

- initFrame:(const NXRect *)frameRect {
  [super initFrame: frameRect];
  [self setClipping: NO];
  [self drawInSuperview];
  auto_init();
  c64_init();
  Running = 1;
  [NXApp setDelegate: self];
  return (self); }

- free {
  timer_stop();
  return ([super free]); }

- drawSelf:(const NXRect *)rects :(int)rectCount {
  return (self); }

- appDidInit:sender {
  [[self window] makeKeyAndOrderFront:self];
  [[self window] makeFirstResponder:self];
  return (self); }

- (BOOL) acceptsFirstResponder {
  return (YES); }

- becomeFirstResponder {
  if (Running) timer_start(self);
  return (self); }

- resignFirstResponder {
  timer_stop();
  return (self); }

- (BOOL)appAcceptsAnotherFile:sender {
  if (!auto_load_done) {
    auto_load_done = 1;
    return (YES); }
  return (NO); }

- (int)app:sender openFile:(const char *)filename type:(const char *)aType {
  auto_load(filename);
  return (YES); }

- keyDown:(NXEvent *)theEvent {
  DATA Key = (DATA) theEvent->data.key.charCode;
  if (theEvent->data.key.charSet == NX_SYMBOLSET) {
    if (Key >= 0xAC && Key <= 0xAF) {   /* NeXT arrow keys */
      Key = 0x11|((Key&0x02)?0x00:0x80)|((Key&0x01)?0x00:0x0D);
      keyb_down(Key); } }
  if (theEvent->data.key.charSet == NX_ASCIISET) {
    if (Key == 0x7F) {                  /* ASCII Delete to C64 Delete */
      Key = 0x14; }
    if (Key == 0xA3) {                  /* NeXT Pound sign to C64 Pound */
      Key = 0x5C; }
    if (Key >= 0x7B) {                  /* drop NeXT keys w no C64 eqivalent */
      return (self); }
    if (Key >= 0x41 && Key <= 0x5A) {
      Key |= 0x80; }                    /* ASCII shifted to C64 shifted */
    if (Key >= 0x61 && Key <= 0x7A) {
      Key &= 0xDF; }                    /* ASCII unshifted to C64 unshifted */
    keyb_down(Key); }
  return (self); }

- keyUp:(NXEvent *)theEvent {
  keyb_up();
  return (self); }

- openprg:sender {
  id Open = [OpenPanel new];
  char *Types[] = { "prg", NULL };
  [Open runModalForTypes: Types];
  if ([Open filename]) {
    auto_load([Open filename]); }
  return (self); }

- freeze64:sender {
  timer_stop();
  Running = 1-Running;                  /* toggle sys_c64running */
  if (Running) timer_start(self);
  return (self); }

- reset64:sender {
  timer_stop();
  auto_init();
  c64_init();                           /* reinitialise */
  Running = 1;
  timer_start(self);
  return (self); }

@end

/* timer for executing c64_slice() */

#import <dpsclient/dpsNeXT.h>           /* DPSAdd/RemoveTimedEntry() */
#import <appkit/Application.h>          /* NX_BASETHRESHOLD */

DPSTimedEntry TimedEntry;

void timer_start(id Self) {
  TimedEntry = DPSAddTimedEntry(1.0/68.0, (DPSTimedEntryProc)timer_entry,
      (void *)Self, NX_BASETHRESHOLD); }

void timer_stop(void) {
  if (TimedEntry != NULL) {
    DPSRemoveTimedEntry(TimedEntry); TimedEntry = NULL; } }

void timer_entry(DPSTimedEntry teNumber, double now, char *userData) {
  id Self = (id)userData;
  [Self lockFocus];
  auto_slice();
  c64_slice(230);                       /* 230*64us in 1/68s */
  [Self unlockFocus]; }

/* autotyper - types automatically instead of user */

#include <string.h>                     /* for str*(), rindex() */
#include <sys/param.h>                  /* for MAXPATHLEN */

DATA AutoKeys[MAXPATHLEN+1], *AutoKey;
int AutoKeystep;

void auto_init(void) {
  auto_type(""); }

void auto_type(DATA *Keys) {
  strcpy(AutoKeys, Keys);
  AutoKey = AutoKeys;
  AutoKeystep = 0; }

void auto_load(const DATA *FileName) {
  DATA LoadCommand[MAXPATHLEN+1], *Key;
  strcpy(LoadCommand, "load \"");
  strcat(LoadCommand, FileName);
  *rindex(LoadCommand, '.') = '\0';     /* cut off .prg */
  strcat(LoadCommand, "\",8,1\r");      /* only carriage return for C64 */
  for (Key = LoadCommand; *Key; Key++) {
    if (*Key >= 0x41 && *Key <= 0x5A) {
      *Key |= 0x80; }                   /* ASCII shifted to C64 shifted */
    if (*Key >= 0x61 && *Key <= 0x7A) {
      *Key &= 0xDF; } }                 /* ASCII unshifted to C64 unshifted */
  auto_type(LoadCommand); }

void auto_slice(void) {
  if (*AutoKey) {
    if (AutoKeystep == 0) {
      keyb_down(*AutoKey++); }
    if (AutoKeystep == 2) {
      keyb_up(); }
    if (AutoKeystep++ == 4) {
      AutoKeystep = 0; } } }

/* entire C64 */

#import <appkit/Panel.h>                /* NXRunAlertPanel() */

void c64_init(void) {
  mpu_init();
  mem_init();
  vic_init();
  sid_init();
  cia1_init();
  cia2_init(); }

void c64_slice(int ScreenLines) {
  for (; ScreenLines >= 0; ScreenLines--) {
    mpu_64us(vic_64us());               /* VIC determines MPU cycles/64us */
    cia1_64us();
    cia2_64us(); } }

void c64_abort(char *ErrorMessage) {
  NXRunAlertPanel(NULL, "%s,\nSoft 64 will terminate", NULL, NULL, NULL,
      ErrorMessage);
  exit(1); }

/* Processor MPU 6510 */

DATA A, X, Y, SP, FlagN, FlagV, FlagZ, FlagC, TempData;
ADDR PC, TempAddr;
int Interrupt, IntMask, TempInt;

#define MEM_IMMED PC++
#define MEM_DIREC (ADDR)mem_rd(PC++)
#define MEM_DIREX (ADDR)(mem_rd(PC++)+X)
#define MEM_DIREY (ADDR)(mem_rd(PC++)+Y)
#define MEM_ABSOL (ADDR)mem_rd(PC++)+((ADDR)mem_rd(PC++)<<8)
#define MEM_ABSOX (ADDR)mem_rd(PC++)+((ADDR)mem_rd(PC++)<<8)+(ADDR)X
#define MEM_ABSOY (ADDR)mem_rd(PC++)+((ADDR)mem_rd(PC++)<<8)+(ADDR)Y
#define MEM_INDIX (ADDR)mem_rd((ADDR)(mem_rd(PC)+X))+ \
                  ((ADDR)mem_rd((ADDR)(mem_rd(PC++)+X+1))<<8)
#define MEM_INDIY (ADDR)mem_rd((ADDR)mem_rd(PC))+ \
                  ((ADDR)mem_rd((ADDR)mem_rd(PC++)+1)<<8)+(ADDR)Y
#define MEM_SPINC 0x0100+(ADDR)(++SP)
#define MEM_SPDEC 0x0100+(ADDR)(SP--)

void mpu_init(void) {
  mem_wr(0x0001, 0x3F);                  /* all ones on processor port */
  Interrupt = IntMask = 0x1F; }

#define MPU_INT_VIC  0x01
#define MPU_INT_CIA1 0x02
#define MPU_INT_CIA2 0x08

void mpu_interrupt(int Source, int Set) {
  if (Set) Interrupt |= Source; else Interrupt &= ~Source; }

void mpu_php(void) {
  mem_wr(MEM_SPDEC, (FlagN&0x80)|(FlagV&0x40)|
                    0x20|((Interrupt&0x04)?0x10:0)|
                    0x00|((IntMask&0x03)?0:0x04)|
                    (FlagZ?0:0x02)|(FlagC&0x01)); }

void mpu_plp(void) {
  FlagC = FlagV = FlagN = mem_rd(MEM_SPINC);
  if (FlagN&0x08) c64_abort("decimal arithmetic not supported");
  IntMask = FlagN&0x04 ? 0x1C : 0x1F;
  FlagZ = FlagN&0x02 ? 0 : 1; }

#include <stdio.h>
void trace(void) {
  if (PC == 0xEAAE) {
    printf("A=%2X X=%2X Y=%2X SP=%2X %c%c%c%c PC=%4X next OP=%2X\n",
        (unsigned int)A, (unsigned int)X, (unsigned int)Y, (unsigned int)SP,
        FlagN&0x80 ? 0x4E : 0x6E, FlagV&0x40 ? 0x56 : 0x76,
        !FlagZ ? 0x5A : 0x7A, FlagC&0x01 ? 0x43 : 0x63,
        (unsigned int)PC, (unsigned int)mem_rd(PC)); } }

void mpu_64us(int ClockCycles) {
  for(; ClockCycles >= 0; ClockCycles -= 3) {
    if (Interrupt&IntMask) {
      mem_wr(MEM_SPDEC,(DATA)(PC>>8)); mem_wr(MEM_SPDEC,(DATA)PC); mpu_php();
      if (Interrupt&0x10) {        Interrupt = 0; PC = 0xFFFC; }
      else { if (Interrupt&0x08) { Interrupt &= 0x17; PC = 0xFFFA; }
             else {                Interrupt &= 0x1B; PC = 0xFFFE; } }
      PC = MEM_ABSOL; IntMask = 0x1C; }
    /*trace();*/
    switch (mem_rd(PC++)) {
      /* NOP JMP JMP() BRK JSR RTI RTS */
      case 0xEA: break;
      case 0x4C: PC = MEM_ABSOL; break;
      case 0x6C: TempAddr = MEM_ABSOL; PC = (ADDR)mem_rd(TempAddr);
            TempAddr = (TempAddr&0xFF00)|(ADDR)((DATA)TempAddr+1);
            PC += (ADDR)mem_rd(TempAddr)<<8; break;
      case 0x00: Interrupt |= 0x04; break;
      case 0x20: PC += 1; mem_wr(MEM_SPDEC, (DATA)(PC>>8));
            mem_wr(MEM_SPDEC, (DATA)PC); PC -= 1; PC = MEM_ABSOL; break;
      case 0x40: mpu_plp(); PC = (ADDR)mem_rd(MEM_SPINC)+
            ((ADDR)mem_rd(MEM_SPINC)<<8); break;
      case 0x60: PC = (ADDR)mem_rd(MEM_SPINC)+
            ((ADDR)mem_rd(MEM_SPINC)<<8)+1; break;
      /* BPL BMI BVC BVS BCC BCS BNE BEQ */
      case 0x10: PC += FlagN&0x80 ? 1 : (ADDR)((char)mem_rd(PC))+1; break;
      case 0x30: PC += FlagN&0x80 ? (ADDR)((char)mem_rd(PC))+1 : 1; break;
      case 0x50: PC += FlagV&0x40 ? 1 : (ADDR)((char)mem_rd(PC))+1; break;
      case 0x70: PC += FlagV&0x40 ? (ADDR)((char)mem_rd(PC))+1 : 1; break;
      case 0x90: PC += FlagC&0x01 ? 1 : (ADDR)((char)mem_rd(PC))+1; break;
      case 0xB0: PC += FlagC&0x01 ? (ADDR)((char)mem_rd(PC))+1 : 1; break;
      case 0xD0: PC += !FlagZ     ? 1 : (ADDR)((char)mem_rd(PC))+1; break;
      case 0xF0: PC += !FlagZ     ? (ADDR)((char)mem_rd(PC))+1 : 1; break;
      /* CLC SEC CLI SEI CLV CLD SED */
      case 0x18: FlagC = 0; break;
      case 0x38: FlagC = 1; break;
      case 0x58: IntMask = 0x1F; break;    /* CLI: all allowed */
      case 0x78: IntMask = 0x1C; break;    /* SEI: IRQ suppressed */
      case 0xB8: FlagV = 0; break;
      case 0xD8: break;                    /* no decimal flag */
      case 0xF8: c64_abort("decimal arithmetic not supported"); break;
      /* PHP PLP PHA PLA TXS TSX */
      case 0x08: mpu_php(); break;
      case 0x28: mpu_plp(); break;
      case 0x48: mem_wr(MEM_SPDEC, A); break;
      case 0x68: FlagZ = FlagN = A = mem_rd(MEM_SPINC); break;
      case 0x9A: SP = X; break;
      case 0xBA: FlagZ = FlagN = X = SP; break;
      /* ORA */
      case 0x01: FlagZ = FlagN = A |= mem_rd(MEM_INDIX); break;
      case 0x05: FlagZ = FlagN = A |= mem_rd(MEM_DIREC); break;
      case 0x09: FlagZ = FlagN = A |= mem_rd(MEM_IMMED); break;
      case 0x0D: FlagZ = FlagN = A |= mem_rd(MEM_ABSOL); break;
      case 0x11: FlagZ = FlagN = A |= mem_rd(MEM_INDIY); break;
      case 0x15: FlagZ = FlagN = A |= mem_rd(MEM_DIREX); break;
      case 0x19: FlagZ = FlagN = A |= mem_rd(MEM_ABSOY); break;
      case 0x1D: FlagZ = FlagN = A |= mem_rd(MEM_ABSOX); break;
      /* AND */
      case 0x21: FlagZ = FlagN = A &= mem_rd(MEM_INDIX); break;
      case 0x25: FlagZ = FlagN = A &= mem_rd(MEM_DIREC); break;
      case 0x29: FlagZ = FlagN = A &= mem_rd(MEM_IMMED); break;
      case 0x2D: FlagZ = FlagN = A &= mem_rd(MEM_ABSOL); break;
      case 0x31: FlagZ = FlagN = A &= mem_rd(MEM_INDIY); break;
      case 0x35: FlagZ = FlagN = A &= mem_rd(MEM_DIREX); break;
      case 0x39: FlagZ = FlagN = A &= mem_rd(MEM_ABSOY); break;
      case 0x3D: FlagZ = FlagN = A &= mem_rd(MEM_ABSOX); break;
      /* EOR */
      case 0x41: FlagZ = FlagN = A ^= mem_rd(MEM_INDIX); break;
      case 0x45: FlagZ = FlagN = A ^= mem_rd(MEM_DIREC); break;
      case 0x49: FlagZ = FlagN = A ^= mem_rd(MEM_IMMED); break;
      case 0x4D: FlagZ = FlagN = A ^= mem_rd(MEM_ABSOL); break;
      case 0x51: FlagZ = FlagN = A ^= mem_rd(MEM_INDIY); break;
      case 0x55: FlagZ = FlagN = A ^= mem_rd(MEM_DIREX); break;
      case 0x59: FlagZ = FlagN = A ^= mem_rd(MEM_ABSOY); break;
      case 0x5D: FlagZ = FlagN = A ^= mem_rd(MEM_ABSOX); break;
      /* ADC */
      case 0x61: TempInt = (int)A+(int)mem_rd(MEM_INDIX)+(int)(FlagC&0x01);
            FlagV = (A^(DATA)TempInt)>>1; FlagZ = FlagN = A = (DATA)TempInt;
            FlagC = (DATA)(TempInt>>8); break;
      case 0x65: TempInt = (int)A+(int)mem_rd(MEM_DIREC)+(int)(FlagC&0x01);
            FlagV = (A^(DATA)TempInt)>>1; FlagZ = FlagN = A = (DATA)TempInt;
            FlagC = (DATA)(TempInt>>8); break;
      case 0x69: TempInt = (int)A+(int)mem_rd(MEM_IMMED)+(int)(FlagC&0x01);
            FlagV = (A^(DATA)TempInt)>>1; FlagZ = FlagN = A = (DATA)TempInt;
            FlagC = (DATA)(TempInt>>8); break;
      case 0x6D: TempInt = (int)A+(int)mem_rd(MEM_ABSOL)+(int)(FlagC&0x01);
            FlagV = (A^(DATA)TempInt)>>1; FlagZ = FlagN = A = (DATA)TempInt;
            FlagC = (DATA)(TempInt>>8); break;
      case 0x71: TempInt = (int)A+(int)mem_rd(MEM_INDIY)+(int)(FlagC&0x01);
            FlagV = (A^(DATA)TempInt)>>1; FlagZ = FlagN = A = (DATA)TempInt;
            FlagC = (DATA)(TempInt>>8); break;
      case 0x75: TempInt = (int)A+(int)mem_rd(MEM_DIREX)+(int)(FlagC&0x01);
            FlagV = (A^(DATA)TempInt)>>1; FlagZ = FlagN = A = (DATA)TempInt;
            FlagC = (DATA)(TempInt>>8); break;
      case 0x79: TempInt = (int)A+(int)mem_rd(MEM_ABSOY)+(int)(FlagC&0x01);
            FlagV = (A^(DATA)TempInt)>>1; FlagZ = FlagN = A = (DATA)TempInt;
            FlagC = (DATA)(TempInt>>8); break;
      case 0x7D: TempInt = (int)A+(int)mem_rd(MEM_ABSOX)+(int)(FlagC&0x01);
            FlagV = (A^(DATA)TempInt)>>1; FlagZ = FlagN = A = (DATA)TempInt;
            FlagC = (DATA)(TempInt>>8); break;
      /* STA */
      case 0x81: mem_wr(MEM_INDIX, A); break;
      case 0x85: mem_wr(MEM_DIREC, A); break;
      case 0x89: mem_wr(MEM_IMMED, A); break;
      case 0x8D: mem_wr(MEM_ABSOL, A); break;
      case 0x91: mem_wr(MEM_INDIY, A); break;
      case 0x95: mem_wr(MEM_DIREX, A); break;
      case 0x99: mem_wr(MEM_ABSOY, A); break;
      case 0x9D: mem_wr(MEM_ABSOX, A); break;
      /* LDA */
      case 0xA1: FlagZ = FlagN = A = mem_rd(MEM_INDIX); break;
      case 0xA5: FlagZ = FlagN = A = mem_rd(MEM_DIREC); break;
      case 0xA9: FlagZ = FlagN = A = mem_rd(MEM_IMMED); break;
      case 0xAD: FlagZ = FlagN = A = mem_rd(MEM_ABSOL); break;
      case 0xB1: FlagZ = FlagN = A = mem_rd(MEM_INDIY); break;
      case 0xB5: FlagZ = FlagN = A = mem_rd(MEM_DIREX); break;
      case 0xB9: FlagZ = FlagN = A = mem_rd(MEM_ABSOY); break;
      case 0xBD: FlagZ = FlagN = A = mem_rd(MEM_ABSOX); break;
      /* CMP */
      case 0xC1: TempInt = (int)A-(int)mem_rd(MEM_INDIX);
            FlagZ = FlagN = (DATA)TempInt; FlagC = (DATA)(TempInt>>8)+1; break;
      case 0xC5: TempInt = (int)A-(int)mem_rd(MEM_DIREC);
            FlagZ = FlagN = (DATA)TempInt; FlagC = (DATA)(TempInt>>8)+1; break;
      case 0xC9: TempInt = (int)A-(int)mem_rd(MEM_IMMED);
            FlagZ = FlagN = (DATA)TempInt; FlagC = (DATA)(TempInt>>8)+1; break;
      case 0xCD: TempInt = (int)A-(int)mem_rd(MEM_ABSOL);
            FlagZ = FlagN = (DATA)TempInt; FlagC = (DATA)(TempInt>>8)+1; break;
      case 0xD1: TempInt = (int)A-(int)mem_rd(MEM_INDIY);
            FlagZ = FlagN = (DATA)TempInt; FlagC = (DATA)(TempInt>>8)+1; break;
      case 0xD5: TempInt = (int)A-(int)mem_rd(MEM_DIREX);
            FlagZ = FlagN = (DATA)TempInt; FlagC = (DATA)(TempInt>>8)+1; break;
      case 0xD9: TempInt = (int)A-(int)mem_rd(MEM_ABSOY);
            FlagZ = FlagN = (DATA)TempInt; FlagC = (DATA)(TempInt>>8)+1; break;
      case 0xDD: TempInt = (int)A-(int)mem_rd(MEM_ABSOX);
            FlagZ = FlagN = (DATA)TempInt; FlagC = (DATA)(TempInt>>8)+1; break;
      /* SBC */
      case 0xE1: TempInt =(int)A-(int)mem_rd(MEM_INDIX)+(int)(FlagC&0x01)-1;
            FlagV = (A^(DATA)TempInt)>>1; FlagZ = FlagN = A = (DATA)TempInt;
            FlagC = (DATA)(TempInt>>8)+1; break;
      case 0xE5: TempInt =(int)A-(int)mem_rd(MEM_DIREC)+(int)(FlagC&0x01)-1;
            FlagV = (A^(DATA)TempInt)>>1; FlagZ = FlagN = A = (DATA)TempInt;
            FlagC = (DATA)(TempInt>>8)+1; break;
      case 0xE9: TempInt =(int)A-(int)mem_rd(MEM_IMMED)+(int)(FlagC&0x01)-1;
            FlagV = (A^(DATA)TempInt)>>1; FlagZ = FlagN = A = (DATA)TempInt;
            FlagC = (DATA)(TempInt>>8)+1; break;
      case 0xED: TempInt =(int)A-(int)mem_rd(MEM_ABSOL)+(int)(FlagC&0x01)-1;
            FlagV = (A^(DATA)TempInt)>>1; FlagZ = FlagN = A = (DATA)TempInt;
            FlagC = (DATA)(TempInt>>8)+1; break;
      case 0xF1: TempInt =(int)A-(int)mem_rd(MEM_INDIY)+(int)(FlagC&0x01)-1;
            FlagV = (A^(DATA)TempInt)>>1; FlagZ = FlagN = A = (DATA)TempInt;
            FlagC = (DATA)(TempInt>>8)+1; break;
      case 0xF5: TempInt =(int)A-(int)mem_rd(MEM_DIREX)+(int)(FlagC&0x01)-1;
            FlagV = (A^(DATA)TempInt)>>1; FlagZ = FlagN = A = (DATA)TempInt;
            FlagC = (DATA)(TempInt>>8)+1; break;
      case 0xF9: TempInt =(int)A-(int)mem_rd(MEM_ABSOY)+(int)(FlagC&0x01)-1;
            FlagV = (A^(DATA)TempInt)>>1; FlagZ = FlagN = A = (DATA)TempInt;
            FlagC = (DATA)(TempInt>>8)+1; break;
      case 0xFD: TempInt =(int)A-(int)mem_rd(MEM_ABSOX)+(int)(FlagC&0x01)-1;
            FlagV = (A^(DATA)TempInt)>>1; FlagZ = FlagN = A = (DATA)TempInt;
            FlagC = (DATA)(TempInt>>8)+1; break;
      /* BIT */
      case 0x24: FlagV = FlagN = mem_rd(MEM_DIREC); FlagZ = FlagN&A; break;
      case 0x2C: FlagV = FlagN = mem_rd(MEM_ABSOL); FlagZ = FlagN&A; break;
      /* ASL */
      case 0x06: TempAddr = MEM_DIREC;FlagC = (TempData = mem_rd(TempAddr))>>7;
            mem_wr(TempAddr, FlagZ = FlagN = TempData<<1); break;
      case 0x0A: FlagC = A>>7; A = FlagZ = FlagN = A<<1; break;
      case 0x0E: TempAddr = MEM_ABSOL;FlagC = (TempData = mem_rd(TempAddr))>>7;
            mem_wr(TempAddr, FlagZ = FlagN = TempData<<1); break;
      case 0x16: TempAddr = MEM_DIREX;FlagC = (TempData = mem_rd(TempAddr))>>7;
            mem_wr(TempAddr, FlagZ = FlagN = TempData<<1); break;
      case 0x1E: TempAddr = MEM_ABSOX;FlagC = (TempData = mem_rd(TempAddr))>>7;
            mem_wr(TempAddr, FlagZ = FlagN = TempData<<1); break;
      /* ROL */
      case 0x26: TempAddr = MEM_DIREC; TempData = mem_rd(TempAddr);
            mem_wr(TempAddr, FlagZ = FlagN = (TempData<<1)|(FlagC&0x01));
            FlagC = TempData>>7; break;
      case 0x2A:                       TempData = A;
            A = FlagZ = FlagN = (TempData<<1)|(FlagC&0x01);
            FlagC = TempData>>7; break;
      case 0x2E: TempAddr = MEM_ABSOL; TempData = mem_rd(TempAddr);
            mem_wr(TempAddr, FlagZ = FlagN = (TempData<<1)|(FlagC&0x01));
            FlagC = TempData>>7; break;
      case 0x36: TempAddr = MEM_DIREX; TempData = mem_rd(TempAddr);
            mem_wr(TempAddr, FlagZ = FlagN = (TempData<<1)|(FlagC&0x01));
            FlagC = TempData>>7; break;
      case 0x3E: TempAddr = MEM_ABSOX; TempData = mem_rd(TempAddr);
            mem_wr(TempAddr, FlagZ = FlagN = (TempData<<1)|(FlagC&0x01));
            FlagC = TempData>>7; break;
      /* LSR */
      case 0x46: TempAddr = MEM_DIREC; FlagC = TempData = mem_rd(TempAddr);
	    mem_wr(TempAddr, FlagN = FlagZ = TempData>>1); break;
      case 0x4A: FlagC = A; A = FlagN = FlagZ = A>>1; break;
      case 0x4E: TempAddr = MEM_ABSOL; FlagC = TempData = mem_rd(TempAddr);
	    mem_wr(TempAddr, FlagN = FlagZ = TempData>>1); break;
      case 0x56: TempAddr = MEM_DIREX; FlagC = TempData = mem_rd(TempAddr);
	    mem_wr(TempAddr, FlagN = FlagZ = TempData>>1); break;
      case 0x5E: TempAddr = MEM_ABSOX; FlagC = TempData = mem_rd(TempAddr);
	    mem_wr(TempAddr, FlagN = FlagZ = TempData>>1); break;
      /* ROR */
      case 0x66: TempAddr = MEM_DIREC; TempData = mem_rd(TempAddr);
	    mem_wr(TempAddr, FlagN = FlagZ = (TempData>>1)|(FlagC<<7));
            FlagC = TempData; break;
      case 0x6A:                       TempData = A;
	    A = FlagN = FlagZ = (TempData>>1)|(FlagC<<7);
            FlagC = TempData; break;
      case 0x6E: TempAddr = MEM_ABSOL; TempData = mem_rd(TempAddr);
	    mem_wr(TempAddr, FlagN = FlagZ = (TempData>>1)|(FlagC<<7));
            FlagC = TempData; break;
      case 0x76: TempAddr = MEM_DIREX; TempData = mem_rd(TempAddr);
	    mem_wr(TempAddr, FlagN = FlagZ = (TempData>>1)|(FlagC<<7));
            FlagC = TempData; break;
      case 0x7E: TempAddr = MEM_ABSOX; TempData = mem_rd(TempAddr);
	    mem_wr(TempAddr, FlagN = FlagZ = (TempData>>1)|(FlagC<<7));
            FlagC = TempData; break;
      /* DEC */
      case 0xC6: TempAddr = MEM_DIREC;
	    mem_wr(TempAddr, FlagZ = FlagN = mem_rd(TempAddr)-1); break;
      case 0xCE: TempAddr = MEM_ABSOL;
	    mem_wr(TempAddr, FlagZ = FlagN = mem_rd(TempAddr)-1); break;
      case 0xD6: TempAddr = MEM_DIREX;
	    mem_wr(TempAddr, FlagZ = FlagN = mem_rd(TempAddr)-1); break;
      case 0xDE: TempAddr = MEM_ABSOX;
	    mem_wr(TempAddr, FlagZ = FlagN = mem_rd(TempAddr)-1); break;
      /* INC */
      case 0xE6: TempAddr = MEM_DIREC;
	    mem_wr(TempAddr, FlagZ = FlagN = mem_rd(TempAddr)+1); break;
      case 0xEE: TempAddr = MEM_ABSOL;
	    mem_wr(TempAddr, FlagZ = FlagN = mem_rd(TempAddr)+1); break;
      case 0xF6: TempAddr = MEM_DIREX;
	    mem_wr(TempAddr, FlagZ = FlagN = mem_rd(TempAddr)+1); break;
      case 0xFE: TempAddr = MEM_ABSOX;
	    mem_wr(TempAddr, FlagZ = FlagN = mem_rd(TempAddr)+1); break;
      /* STX TXA */
      case 0x86: mem_wr(MEM_DIREC, X);   break;
      case 0x8A: FlagZ = FlagN = A = X; break;
      case 0x8E: mem_wr(MEM_ABSOL, X);   break;
      case 0x96: mem_wr(MEM_DIREY, X);   break;
      /* LDX TAX */
      case 0xA2: FlagZ = FlagN = X = mem_rd(MEM_IMMED); break;
      case 0xA6: FlagZ = FlagN = X = mem_rd(MEM_DIREC); break;
      case 0xAA: FlagZ = FlagN = X = A;                 break;
      case 0xAE: FlagZ = FlagN = X = mem_rd(MEM_ABSOL); break;
      case 0xB6: FlagZ = FlagN = X = mem_rd(MEM_DIREY); break;
      case 0xBE: FlagZ = FlagN = X = mem_rd(MEM_ABSOY); break;
      /* CPX */
      case 0xE0: TempInt = (int)X-(int)mem_rd(MEM_IMMED);
            FlagZ = FlagN = (DATA)TempInt; FlagC = (DATA)(TempInt>>8)+1; break;
      case 0xE4: TempInt = (int)X-(int)mem_rd(MEM_DIREC);
            FlagZ = FlagN = (DATA)TempInt; FlagC = (DATA)(TempInt>>8)+1; break;
      case 0xEC: TempInt = (int)X-(int)mem_rd(MEM_ABSOL);
            FlagZ = FlagN = (DATA)TempInt; FlagC = (DATA)(TempInt>>8)+1; break;
      /* DEX INX */
      case 0xCA: FlagZ = FlagN = X -= 1; break;
      case 0xE8: FlagZ = FlagN = X += 1; break;
      /* STY TYA */
      case 0x84: mem_wr(MEM_DIREC, Y);   break;
      case 0x98: FlagZ = FlagN = A = Y; break;
      case 0x8C: mem_wr(MEM_ABSOL, Y);   break;
      case 0x94: mem_wr(MEM_DIREX, Y);   break;
      /* LDY TAY */
      case 0xA0: FlagZ = FlagN = Y = mem_rd(MEM_IMMED); break;
      case 0xA4: FlagZ = FlagN = Y = mem_rd(MEM_DIREC); break;
      case 0xA8: FlagZ = FlagN = Y = A;                 break;
      case 0xAC: FlagZ = FlagN = Y = mem_rd(MEM_ABSOL); break;
      case 0xB4: FlagZ = FlagN = Y = mem_rd(MEM_DIREX); break;
      case 0xBC: FlagZ = FlagN = Y = mem_rd(MEM_ABSOX); break;
      /* CPY */
      case 0xC0: TempInt = (int)Y-(int)mem_rd(MEM_IMMED);
            FlagZ = FlagN = (DATA)TempInt; FlagC = (DATA)(TempInt>>8)+1; break;
      case 0xC4: TempInt = (int)Y-(int)mem_rd(MEM_DIREC);
            FlagZ = FlagN = (DATA)TempInt; FlagC = (DATA)(TempInt>>8)+1; break;
      case 0xCC: TempInt = (int)Y-(int)mem_rd(MEM_ABSOL);
            FlagZ = FlagN = (DATA)TempInt; FlagC = (DATA)(TempInt>>8)+1; break;
      /* DEY INY */
      case 0x88: FlagZ = FlagN = Y -= 1; break;
      case 0xC8: FlagZ = FlagN = Y += 1; break;
      /* illegal opcodes */
      default: {
        c64_abort("undocumented opcodes not supported"); } } } }

/* Memory and Address Manager */

#import <objc/NXBundle.h>
#include <sys/param.h>                  /* for MAXPATHLEN */
#include <stdio.h>

DATA Ram[0x10000], Cram[0x0400];        /* Main and Character RAMs */
DATA Brom[0x2000], Crom[0x1000], Krom[0x2000];/* Basic, Char and Kernal ROMs */

void mem_init(void) {
  mem_loadrom("basic", Brom, 0x2000);
  mem_loadrom("char", Crom, 0x1000);
  mem_loadrom("kernal", Krom, 0x2000); }

void mem_loadrom(char *Name, DATA *Mem, int Length) {
  NXBundle *bundle;
  char FileName[MAXPATHLEN+1];
  FILE *File;
  bundle = [NXBundle mainBundle];
  [bundle getPath:FileName forResource:Name ofType:".rom"];
  File = fopen(FileName, "rb");
  fread(Mem, sizeof(DATA), Length, File);
  fclose(File); }

DATA mem_rd(ADDR Addr) {
  switch (Addr>>12) {
    case 0xA:
    case 0xB: {
      if (Ram[0x0001]&0x03 == 0x03) {   /* Address Manager control pins */
        return (Brom[Addr&0x1FFF]); } }
    case 0xD: {
      if (Ram[0x0001]&0x03) {
        if (Ram[0x0001]&0x04) {         /* charen pin */
          switch ((Addr>>10)&0x0003) {  /* first 74LS139 */
            case 0: {
              return (vic_rd(Addr&0x003F)); }
            case 1: {
              return (sid_rd(Addr&0x001F)); }
            case 2: {
              return (Cram[Addr&0x03FF]|0xF0); } /* 0xF0 for open coll. bus */
            case 3: {
              switch((Addr>>8)&0x0003) {/* second 74LS139 */
                case 0: {
	          return (cia1_rd(Addr&0x000F)); }
                case 1: {
	          return (cia2_rd(Addr&0x000F)); }
                default: {
                  return (0xFF); } } } } } /* 0xFF for open collector bus */
        else {
          return (Crom[Addr&0x0FFF]); } } }
    case 0xE:
    case 0xF: {
      if (Ram[0x0001]&0x02) {
        return (Krom[Addr&0x1FFF]); } } }
  return (Ram[Addr]); }

void mem_wr(ADDR Addr, DATA Data) {
  if (Addr>>12 == 0xD && Ram[0x0001]&0x07 >= 0x05) {
    switch ((Addr>>10)&0x0003) {
      case 0: {
        vic_wr(Addr&0x003F, Data); return; }
      case 1: {
        sid_wr(Addr&0x001F, Data); return; }
      case 2: {
        Cram[Addr&0x03FF] = Data&0x0F; return; } /* &0x0F to speed up VIC */
      case 3: {
        switch ((Addr>>8)&0x0003) {
          case 0: {
	    cia1_wr(Addr&0x000F, Data); return; }
          case 1: {
	    cia2_wr(Addr&0x000F, Data); return; }
          default: {
            return; } } } } }
  Ram[Addr] = Data; }

/* VIC */

DATA Vic[64];                           /* 64 Bytes to prevent errors */
int VicVideoOn, VicLine, VicIrqLine;
ADDR VicChargenAddr, VicVideoAddr; DATA *VicChargen;

void vic_init(void) {
  Krom[0x1CEF] = 0xEA;                  /* blank out Kernal ROM bug */
  Krom[0x1CF0] = 0xEA;                  /* else 38 char mode will be set */
  Krom[0x1CF1] = 0xEA;                  /* leading to c64_abort */
  vic_draw_init(); }

DATA vic_rd(ADDR Addr) {
  switch(Addr) {
    case 17: {                          /* cut up scan line information */
      return ((Vic[17]&0x7F)|((VicLine&0x0100)>>1)); }
    case 18: {
      return ((DATA)VicLine); }
    case 19:
    case 20: {
      c64_abort("lightpen not supported"); }
    case 30:
    case 31: {
      c64_abort("sprite collision detection not supported"); }
    default: {
      return (Vic[Addr]); } } }

void vic_wr(ADDR Addr, DATA Data) {
  switch(Addr) {
    case 17: {                          /* assemble scan line information */
      if (Data & 0x40) c64_abort("extended color mode not supported");
      if (Data & 0x20) c64_abort("bitmap graphic modes not supported");
      if (!(Data & 0x08))
          c64_abort("24 lines and vertical scroll not supported");
      VicVideoOn = Data&0x10;
      VicIrqLine = (VicIrqLine&0xFF)|(Data&0x80 ? 0 : 256);
      break; }
    case 18: {
      VicIrqLine = (VicIrqLine&0x100)|(int)Data; break; }
    case 21: {
      if (Data) c64_abort("sprites not supported"); break; }
    case 22: {
      if (Data & 0x10)  c64_abort("multicolor modes not supported");
      if (!(Data & 0x08))
          c64_abort("38 columns and horizontal scroll not supported");
      break; }
    case 24: {
      ADDR BaseAddr = 0xC000-((ADDR)cia2_rd(0) << 14);
      VicChargenAddr = BaseAddr+(((ADDR)Data << 10) & 0x3800);
      VicVideoAddr = BaseAddr+(((ADDR)Data << 6) & 0x3C00);
      if ((VicChargenAddr & 0x7000) == 0x1000) {
        VicChargen = Crom+(VicChargenAddr&0x0FFF); }
      else {
        VicChargen = Ram+VicChargenAddr; }
      break; }
    case 25:
    case 26: {
      Vic[Addr] = Data; mpu_interrupt(MPU_INT_VIC, Vic[25]&Vic[26]); } }
  if (Addr&0x0020) Data = Data&0x0F;    /* color registers */
  Vic[Addr] = Data; }

int vic_64us(void) {
  vic_draw(VicLine);
  if (VicLine == VicIrqLine) {
    Vic[25] |= 0x81;
    mpu_interrupt(MPU_INT_VIC, Vic[25]&Vic[26]); }
  if ((VicLine += 1) == 312) {
    VicLine = 0; }
  return ((VicLine < 200 && VicVideoOn) ? 24 : 63); } /* MPU cycles/64us */

#import <appkit/graphics.h>             /* NXDrawBitmap() */
#import <dpsclient/psops.h>             /* PS*() */

DATA VicDPat[8000], VicDColor[8000], VicDBack[200], VicDExt[312];

unsigned int VicDrawC[16] = {
  0x000000FF, 0xFFFFFFFF, 0xFF0000FF, 0x00FFFFFF, 
  0xFF00FFFF, 0x00FF00FF, 0x0000FFFF, 0xFFFF00FF, 
  0xFF7F00FF, 0x7F0000FF, 0xFF7F7FFF, 0x3F3F3FFF, 
  0x7F7F7FFF, 0x7FFF7FFF, 0x7F7FFFFF, 0xCFCFCFFF };
float VicDrawCR[16] = {
  0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0,
  1.0, 0.5, 1.0, 0.25, 0.5, 0.5, 0.5, 0.75 };
float VicDrawCG[16] = {
  0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0,
  0.5, 0.0, 0.5, 0.25, 0.5, 1.0, 0.5, 0.75 };
float VicDrawCB[16] = {
  0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0,
  0.0, 0.0, 0.5, 0.25, 0.5, 0.5, 1.0, 0.75 };

void vic_draw_init(void) {
  int Y;
  for (Y = 0; Y < 200; Y++) {           /* guaranteed wrong color value */
    VicDBack[Y] = 0x10; }               /* will lead to complete redraw */
  for (Y = 0; Y < 312; Y++) {
    VicDExt[Y] = 0x10; } }

void vic_draw(int Y) {
  if (Y < 200 && VicVideoOn) {
    static int NextPart;
    int X, FirstX, LastX, VidLine, ColLine;
    DATA *Chargen;
    if (Y&3) {                          /* reduce processor load */
      return; }
    if (Y == 0) {
      if ((NextPart += 50) == 200) {
        NextPart = 0; } }
    Y = (Y>>2)+NextPart;                /* advance at lower speed */
    VidLine = (int)VicVideoAddr+40*(Y>>3); ColLine = 40*(Y>>3);
    Chargen = VicChargen+(Y&7);
    FirstX = 40; LastX = 0;             /* nothing to be drawn */
    for (X = 0; X < 40; X++) {
      if (*(Chargen+8*Ram[VidLine+X]) != VicDPat[40*Y+X] ||
          Cram[ColLine+X] != VicDColor[40*Y+X]) { 
        VicDPat[40*Y+X] = *(Chargen+8*Ram[VidLine+X]);
        VicDColor[40*Y+X] = Cram[ColLine+X];
        if (FirstX == 40) FirstX = X;   /* range to be drawn */
        LastX = X+1; } }
    if (Vic[33] != VicDBack[Y]) { 
      VicDBack[Y] = Vic[33]; 
      FirstX = 0; LastX = 40; }         /* everything to be drawn */
    if (FirstX < 40) {
      NXRect Rect;
      unsigned int Pattern[2*40][16];
      unsigned char *Data[5];
      Rect.origin.x = (float)(50+16*FirstX);
      Rect.origin.y = (float)(448-2*Y);
      Rect.size.width = (float)((LastX-FirstX)*16);
      Rect.size.height = 2.0;
      for (X = FirstX; X < LastX; X++) {
        DATA CharPattern = VicDPat[40*Y+X];
        int Pixel;
        for (Pixel = 0; Pixel < 16; Pixel += 2) {
          Pattern[X-FirstX             ][Pixel] =
          Pattern[X-FirstX             ][Pixel+1] =
          Pattern[X-FirstX+LastX-FirstX][Pixel] =
          Pattern[X-FirstX+LastX-FirstX][Pixel+1] =
            VicDrawC[(CharPattern&0x80) ? VicDColor[40*Y+X] : Vic[33]];
          CharPattern = CharPattern<<1; } }
      Data[0] = (unsigned char *)Pattern;
      NXDrawBitmap(&Rect, (LastX-FirstX)*16, 2, 8, 4, 4, (LastX-FirstX)*64,
          0, 1, NX_RGBColorSpace, Data); }
    if (Vic[32] != VicDExt[Y]) {
      VicDExt[Y] = Vic[32];
      PSsetrgbcolor(VicDrawCR[Vic[32]],VicDrawCG[Vic[32]],VicDrawCB[Vic[32]]);
      PSrectfill(0.0, (float)(448-2*Y), 50.0, 2.0);
      PSrectfill(690.0, (float)(448-2*Y), 50.0, 2.0); } }
  else {
    if (Vic[32] != VicDExt[Y]) {
      VicDExt[Y] = Vic[32];
      if (Y >= 287) Y -= 312;
      if (Y >= 225) return;
      PSsetrgbcolor(VicDrawCR[Vic[32]],VicDrawCG[Vic[32]],VicDrawCB[Vic[32]]);
      PSrectfill(0.0, (float)(448-2*Y), 740.0, 2.0); } } }

/* SID */

#include <stdlib.h>                     /* for rand() */

void sid_init(void) { }

DATA sid_rd(ADDR Addr) {
 switch (Addr) {
    case 25:
    case 26: {
      c64_abort("paddles not supported"); }
    case 27: {
      return ((DATA)rand()); }
    case 28: {
      return ((DATA)rand()); }
    default: {
      return (0xFF); } } }

void sid_wr(ADDR Addr, DATA Data) { }    /* SID does nothing */

/* CIA1 */

DATA Cia1Pra, Cia1Prb, Cia1Ddra, Cia1Ddrb;
int Cia1Tima, Cia1Timast, Cia1Timb, Cia1Timbst;
DATA Cia1Icrd, Cia1Icrm, Cia1Cra, Cia1Crb;

void cia1_init(void) {
  keyb_init(); }

DATA cia1_rd(ADDR Addr) {
  switch(Addr) {
    case 0: {
      return (0xFF); }                  /* joystick not supported */
    case 1: {
      return (keyb_rd(Cia1Pra)); }      /* joystick not supported */
    case 2: {
      return (Cia1Ddra); }
    case 3: {
      return (Cia1Ddrb); }
    case 4: {
      return ((DATA)Cia1Tima); }
    case 5: {
      return ((DATA)(Cia1Tima>>8)); }
    case 6: {
      return ((DATA)Cia1Timb); }
    case 7: {
      return ((DATA)(Cia1Timb>>8)); }
    case 8:
    case 9:
    case 10:
    case 11: {
      c64_abort("real time clock not supported"); }
    case 12: {
      c64_abort("SDR not supported"); }
    case 13: {
      DATA Temp = Cia1Icrd; Cia1Icrd = 0; mpu_interrupt(MPU_INT_CIA1, 0);
      if (Temp) return (Temp|0x80); else return (0x00); }
    case 14: {
      return (Cia1Cra); }
    case 15: {
      return (Cia1Crb); } }
  return (0xFF); }                      /* for the compilers sake */

void cia1_wr(ADDR Addr, DATA Data) {
  switch(Addr) {
    case 0: {
      Cia1Pra = Data; return; }
    case 1: {
      Cia1Prb = Data; return; }
    case 2: {
      Cia1Ddra = Data; return; }
    case 3: {
      Cia1Ddrb = Data; return; }
    case 4: {                           /* timers run in increments of 64 */
      Cia1Timast = (Cia1Timast&0xFF00)|(int)(Data&0xC0); return; }
    case 5: {
      Cia1Timast = (Cia1Timast&0x00FF)|(int)(Data<<8); return; }
    case 6: {
      Cia1Timbst = (Cia1Timbst&0xFF00)|(int)(Data&0xC0); return; }
    case 7: {
      Cia1Timbst = (Cia1Timbst&0x00FF)|(int)(Data<<8); return; }
    case 8:
    case 9:
    case 10:
    case 11: {
      c64_abort("real time clock not supported"); }
    case 12: {
      c64_abort("SDR not supported"); }
    case 13: {
      if (Data&0x80) Cia1Icrm |= Data; else Cia1Icrm &= ~Data;
      mpu_interrupt(MPU_INT_CIA1, Cia1Icrd&Cia1Icrm); return; }
    case 14: {
      if (Data&0x10) Cia1Tima = Cia1Timast;
      Cia1Cra = Data; return; }
    case 15: {
      if (Data&0x20) c64_abort("timer b counts timer a not supported");
      if (Data&0x10) Cia1Timb = Cia1Timbst;
      Cia1Crb = Data; return; } } }

void cia1_64us(void) {
  if (Cia1Cra&1) {
    if ((Cia1Tima -= 64) == 0) {
      Cia1Icrd |= 0x01;
      mpu_interrupt(MPU_INT_CIA1, Cia1Icrd&Cia1Icrm);
      Cia1Tima = Cia1Timast;
      if (Cia1Cra&0x08) {
        Cia1Cra &= 0xFE; } } }
  if (Cia1Crb&1) {
    if ((Cia1Timb -= 64) == 0) {
      Cia1Icrd |= 0x02;
      mpu_interrupt(MPU_INT_CIA1, Cia1Icrd&Cia1Icrm);
      Cia1Timb = Cia1Timbst;
      if (Cia1Crb&0x08) {
        Cia1Crb &= 0xFE; } } } }

/* Keyboard - convert CBM-ASCII into scan codes */

DATA KeybCode[256], KeybRow1, KeybCol1, KeybRow2, KeybCol2;

void keyb_init(void) {
  int I,Table;
  ADDR PAddr,TAddr;
  for (I = 0; I < 256; I++) {           /* for every ASCII code */
    KeybCode[I] = 0x34; }               /* empty Shift key, null effect */
  for (Table = 3; Table >= 0; Table--) { /* for every key code */
    PAddr = 0xEB79+2*Table;             /* reserve it's ASCII code */
    TAddr = (ADDR)mem_rd(PAddr)+((ADDR)mem_rd(PAddr+1)<<8); /* prefer low */
    for (I = 63; I >= 0; I--) {
      KeybCode[mem_rd(TAddr+I)] = (DATA)((Table<<6)+I); } } }

DATA keyb_rd(DATA Row) {
  return (~(((~Row)&KeybRow1 ? KeybCol1 : 0) |
            ((~Row)&KeybRow2 ? KeybCol2 : 0))); }

void keyb_down(DATA Key) {
  switch(KeybCode[Key]&0x38) {
    case 0x00: KeybRow1 = 0x01; break;
    case 0x08: KeybRow1 = 0x02; break;
    case 0x10: KeybRow1 = 0x04; break;
    case 0x18: KeybRow1 = 0x08; break;
    case 0x20: KeybRow1 = 0x10; break;
    case 0x28: KeybRow1 = 0x20; break;
    case 0x30: KeybRow1 = 0x40; break;
    case 0x38: KeybRow1 = 0x80; }
  switch(KeybCode[Key]&0x07) {
    case 0x00: KeybCol1 = 0x01; break;
    case 0x01: KeybCol1 = 0x02; break;
    case 0x02: KeybCol1 = 0x04; break;
    case 0x03: KeybCol1 = 0x08; break;
    case 0x04: KeybCol1 = 0x10; break;
    case 0x05: KeybCol1 = 0x20; break;
    case 0x06: KeybCol1 = 0x40; break;
    case 0x07: KeybCol1 = 0x80; }
  switch(KeybCode[Key]&0xC0) {          /* normal, Shift right, C=, Ctrl */
    case 0x00: KeybRow2 = 0x00; KeybCol2 = 0x00; break;
    case 0x40: KeybRow2 = 0x40; KeybCol2 = 0x10; break;
    case 0x80: KeybRow2 = 0x80; KeybCol2 = 0x20; break;
    case 0xC0: KeybRow2 = 0x80; KeybCol2 = 0x04; } }

void keyb_up(void) {
  KeybRow1 = 0; KeybCol1 = 0; KeybRow2 = 0; KeybCol2 = 0; }

/* CIA2 */

DATA Cia2Pra, Cia2Prb, Cia2Ddra, Cia2Ddrb;
int Cia2Tima, Cia2Timast, Cia2Timb, Cia2Timbst;
DATA Cia2Icrd, Cia2Icrm, Cia2Cra, Cia2Crb;

void cia2_init(void) {
  Krom[0x0E13] = 0xAD;                  /* IEC Bus Hack: LDA $DD0C */
  Krom[0x0E14] = 0x0C;
  Krom[0x0E15] = 0xDD;
  Krom[0x0E16] = 0x4C;                  /* JMP $EE82 */
  Krom[0x0E17] = 0x82;
  Krom[0x0E18] = 0xEE;
  Krom[0x0D40] = 0x8D;                  /* STA $DD0C */
  Krom[0x0D41] = 0x0C;
  Krom[0x0D42] = 0xDD;
  Krom[0x0D43] = 0x4C;                  /* JMP $EDAB */
  Krom[0x0D44] = 0xAB;
  Krom[0x0D45] = 0xED;
  flo_init(); }

DATA cia2_rd(ADDR Addr) {
  switch(Addr) {
    case 0: {
      return (Cia2Pra); }
    case 1: {
      return (Cia2Prb); }
    case 2: {
      return (Cia2Ddra); }
    case 3: {
      return (Cia2Ddrb); }
    case 4: {
      return ((DATA)Cia2Tima); }
    case 5: {
      return ((DATA)(Cia2Tima>>8)); }
    case 6: {
      return ((DATA)Cia2Timb); }
    case 7: {
      return ((DATA)(Cia2Timb>>8)); }
    case 8:
    case 9:
    case 10:
    case 11: {
      c64_abort("real time clock not supported"); }
    case 12: {                          /* IEC bus Hack, read Byte from IEC */
      return (Ram[0x00A4] = flo_rd()); }
    case 13: {
      DATA Temp = Cia2Icrd; Cia2Icrd = 0; mpu_interrupt(MPU_INT_CIA2, 0);
      if (Temp) return (Temp|0x80); else return (0x00); }
    case 14: {
      return (Cia2Cra); }
    case 15: {
      return (Cia2Crb); } }
  return (0xFF); }                      /* for the compilers sake */

void cia2_wr(ADDR Addr, DATA Data) {
  switch(Addr) {
    case 0: {
      DATA Old = Cia2Pra;               /* recalculate video addresses */
      Cia2Pra = Data;
      if (Data&0x03 != Old&0x03) vic_wr(24, vic_rd(24)); return; }
    case 1: {
      Cia2Prb = Data; return; }
    case 2: {
      Cia2Ddra = Data; return; }
    case 3: {
      Cia2Ddrb = Data; return; }
    case 4: {                           /* timers run in increments of 64 */
      Cia2Timast = (Cia2Timast&0xFF00)|(int)(Data&0xC0); return; }
    case 5: {
      Cia2Timast = (Cia2Timast&0x00FF)|(int)(Data<<8); return; }
    case 6: {
      Cia2Timbst = (Cia2Timbst&0xFF00)|(int)(Data&0xC0); return; }
    case 7: {
      Cia2Timbst = (Cia2Timbst&0x00FF)|(int)(Data<<8); return; }
    case 8:
    case 9:
    case 10:
    case 11: {
      c64_abort("real time clock not supported"); }
    case 12: {                          /* IEC bus Hack, write Byte to IEC */
      if (Cia2Pra&0x08) {               /* if write with ATN */
        flo_atn(Ram[0x0095]); return; }
      flo_wr(Ram[0x0095]); return; }
    case 13: {
      if (Data&0x80) Cia2Icrm |= Data; else Cia2Icrm &= ~Data;
      mpu_interrupt(MPU_INT_CIA2, Cia2Icrd&Cia2Icrm); return; }
    case 14: {
      if (Data&0x10) Cia2Tima = Cia2Timast;
      Cia2Cra = Data; return; }
    case 15: {
      if (Data&0x20) c64_abort("timer b counts timer a not supported");
      if (Data&0x10) Cia2Timb = Cia2Timbst;
      Cia2Crb = Data; return; } } }

void cia2_64us(void) {
  if (Cia2Cra&1) {
    if ((Cia2Tima -= 64) == 0) {
      Cia2Icrd |= 0x01;
      mpu_interrupt(MPU_INT_CIA2, Cia2Icrd&Cia2Icrm);
      Cia2Tima = Cia2Timast;
      if (Cia2Cra&0x08) {
        Cia2Cra &= 0xFE; } } }
  if (Cia2Crb&1) {
    if ((Cia2Timb -= 64) == 0) {
      Cia2Icrd |= 0x02;
      mpu_interrupt(MPU_INT_CIA2, Cia2Icrd&Cia2Icrm);
      Cia2Timb = Cia2Timbst;
      if (Cia2Crb&0x08) {
        Cia2Crb &= 0xFE; } } } }

#define CIA2_IEC_STAT_RD_TIMEOUT 0x02
#define CIA2_IEC_STAT_WR_TIMEOUT 0x03
#define CIA2_IEC_STAT_EOI 0x40
#define CIA2_IEC_STAT_DEVNOTPRES 0x80

void cia2_iec_stat(DATA Status) {       /* set IEC device status bits */
  Ram[0x0090] |= Status; }

/* Floppy 1541 at address 8 */

#include <sys/param.h>                  /* for MAXPATHLEN */
#include <stdio.h>
#include <string.h>                     /* str*(), index() */

#define FLO_ADDR 8

enum { idle, ws_listen, ws_talk, l_open, l_data, l_close, t_data } FloState;
int FloSec;
DATA FloFileName[MAXPATHLEN+1];
FILE *FloFile[16];

void flo_init(void) {
  FloState = idle;
  for (FloSec = 0; FloSec < 16; FloSec++) {
    FloFile[FloSec] = NULL; } }

DATA flo_rd(void) {
  switch (FloState) {
    case t_data: {
      if (FloFile[FloSec] != NULL) {
        DATA Data = (DATA)fgetc(FloFile[FloSec]);
        if (feof(FloFile[FloSec])) {
          cia2_iec_stat(CIA2_IEC_STAT_EOI); }
        return (Data); } }
    default: {                          /* for the compilers sake */
      break; } }
  cia2_iec_stat(CIA2_IEC_STAT_RD_TIMEOUT);
  return (0); }

void flo_wr(DATA Data) {
  switch (FloState) {
    case l_open: {
      strncat(FloFileName, &Data, 1); return; }
    case l_data: {
      if (FloFile[FloSec] != NULL) {
        fputc((int)Data, FloFile[FloSec]); return; } }
    default: {
      break; } }
  cia2_iec_stat(CIA2_IEC_STAT_WR_TIMEOUT); }

void flo_atn(DATA Data) {
  switch (FloState) {
    case idle: {
      if (Data == 0x20+FLO_ADDR) {
        FloState = ws_listen; return; }
      if (Data == 0x40+FLO_ADDR) {
        FloState = ws_talk; return; }
      break; }
   case ws_listen: {
      if ((Data&0xF0) == 0xF0) {
        FloSec = Data&0x0F;
	strcpy(FloFileName,"");
	FloState = l_open; return; }
      if ((Data&0xE0) == 0x60) {
        FloSec = Data&0x0F;
        if (FloFile[FloSec] == NULL) {
	  FloState = idle; return; }
	FloState = l_data; return; }
      if ((Data&0xF0) == 0xE0) {
        FloSec = Data&0x0F;
	FloState = l_close; return; }
      break; }
    case ws_talk: {
      if ((Data&0xE0) == 0x60) {
        FloSec = Data&0x0F;
        if (FloFile[FloSec] == NULL) {
	  FloState = idle; return; }
	FloState = t_data; return; }
      break; }
    case l_open: {
      if (Data == 0x3F) {               /* end of filename, open */
        flo_fopen(FloSec,FloFileName);
        FloState = idle; return; }
      break; }
    case l_data: {
      if (Data == 0x3F) {               /* end write, not end file! */
        FloState = idle; return; }
      break; }
    case l_close: {
      if (Data == 0x3F) {               /* now its end of file */
        fclose(FloFile[FloSec]); FloFile[FloSec] = NULL;
        FloState = idle; return; }
      break; }
    case t_data: {
      if (Data == 0x5F) {
        FloState = idle; return; } } }
  cia2_iec_stat(CIA2_IEC_STAT_DEVNOTPRES); }

void flo_fopen(int FloSec, DATA *FileName) {
  DATA *Char, FileType, FileMode;
  for (Char = FileName; *Char; Char++) {
    if (*Char >= 0x41 && *Char <= 0x5A) {
      *Char |= 0x20; }                  /* C64 unshifted to ASCII unshifted */
    if (*Char >= 0xC1 && *Char <= 0xDA) {
      *Char &= 0x7F; } }                /* C64 shifted to ASCII shifted */
  if (FloSec == 15) {
    c64_abort("floppy command/status channel 15 not supported"); }
  if (*FileName == '$') {
    c64_abort("loading directory with $ not supported"); }
  if (index(FileName, ':')) {           /* get rid of 0:, @: or @0: */
    FileName = index(FileName, ':')+1; }
  FileType = 'p'; FileMode = 'r';
  if (FloSec == 1) {
    FileMode = 'w'; }
  if (FloSec >= 2) {
    char *End = FileName+strlen(FileName);
    if (*(End-2) != ',' || *(End-4) != ',') {
      return; }
    FileType = *(End-3); FileMode = *(End-1);
    *(End-4) = '\0'; }
  switch (FileType) {
    case 'p': {
      strcat(FileName,".prg"); break; }
    case 's': {
      strcat(FileName,".seq"); break; }
    case 'u': {
      strcat(FileName,".usr"); break; }
    case 'l': {
      c64_abort("relative files not supported"); }
    default: {
      return; } }
  switch (FileMode) {
    case 'r': {
      FloFile[FloSec] = fopen(FileName,"rb"); break; }
    case 'w': {
      FloFile[FloSec] = fopen(FileName,"wb"); break; }
    case 'a': {
      FloFile[FloSec] = fopen(FileName,"ab"); } } }
