]> Shamusworld >> Repos - apple2/blobdiff - src/mmu.cpp
Docs were missing GPLv3. Thanks to schampailler for the heads up. :-)
[apple2] / src / mmu.cpp
index 5019b9de05b7169f0b004f861d44cac700170ea5..326451091748a04c4aa44407b7751ee51a15832a 100644 (file)
@@ -2,33 +2,29 @@
 // mmu.cpp: Memory management
 //
 // by James Hammons
-// (C) 2013 Underground Software
+// (C) 2013-2018 Underground Software
 //
 // JLH = James Hammons <jlhamm@acm.org>
 //
 // WHO  WHEN        WHAT
 // ---  ----------  -----------------------------------------------------------
 // JLH  09/27/2013  Created this file
-
+//
 
 #include "mmu.h"
 #include "apple2.h"
-#include "firmware.h"
+#include "firmware/firmware.h"
 #include "log.h"
+#include "mockingboard.h"
 #include "sound.h"
 #include "video.h"
 
-
 // Debug defines
 //#define LC_DEBUG
 
 // Address Map enumeration
 enum { AM_RAM, AM_ROM, AM_BANKED, AM_READ, AM_WRITE, AM_READ_WRITE, AM_END_OF_LIST };
 
-// Macros for function pointers
-#define READFUNC(x) uint8_t (* x)(uint16_t)
-#define WRITEFUNC(x) void (* x)(uint16_t, uint8_t)
-
 // Internal vars
 uint8_t ** addrPtrRead[0x10000];
 uint8_t ** addrPtrWrite[0x10000];
@@ -37,6 +33,14 @@ uint16_t addrOffset[0x10000];
 READFUNC(funcMapRead[0x10000]);
 WRITEFUNC(funcMapWrite[0x10000]);
 
+READFUNC(slotHandlerR[8]);
+WRITEFUNC(slotHandlerW[8]);
+
+READFUNC(slotHandler2KR[8]);
+WRITEFUNC(slotHandler2KW[8]);
+
+uint8_t enabledSlot;
+
 struct AddressMap
 {
        uint16_t start;
@@ -67,20 +71,25 @@ uint8_t * mainMemoryTextW = &ram[0x0400];   // $0400 - $07FF (write)
 uint8_t * mainMemoryHGRR  = &ram[0x2000];      // $2000 - $3FFF (read)
 uint8_t * mainMemoryHGRW  = &ram[0x2000];      // $2000 - $3FFF (write)
 
-uint8_t * slotMemory      = &rom[0xC100];      // $C100 - $CFFF
+uint8_t * slotMemory      = &rom[0xC100];      // $C100 - $C7FF
+uint8_t * peripheralMemory= &rom[0xC800];      // $C800 - $CFFF
 uint8_t * slot3Memory     = &rom[0xC300];      // $C300 - $C3FF
+uint8_t * slot4Memory     = &rom[0xC400];      // $C400 - $C4FF
 uint8_t * slot6Memory     = &diskROM[0];       // $C600 - $C6FF
 uint8_t * lcBankMemoryR   = &ram[0xD000];      // $D000 - $DFFF (read)
 uint8_t * lcBankMemoryW   = &ram[0xD000];      // $D000 - $DFFF (write)
 uint8_t * upperMemoryR    = &ram[0xE000];      // $E000 - $FFFF (read)
 uint8_t * upperMemoryW    = &ram[0xE000];      // $E000 - $FFFF (write)
 
-
 // Function prototypes
 uint8_t ReadNOP(uint16_t);
 void WriteNOP(uint16_t, uint8_t);
 uint8_t ReadMemory(uint16_t);
 void WriteMemory(uint16_t, uint8_t);
+uint8_t SlotR(uint16_t address);
+void SlotW(uint16_t address, uint8_t byte);
+uint8_t Slot2KR(uint16_t address);
+void Slot2KW(uint16_t address, uint8_t byte);
 uint8_t ReadKeyboard(uint16_t);
 void Switch80STORE(uint16_t, uint8_t);
 void SwitchRAMRD(uint16_t, uint8_t);
@@ -88,6 +97,8 @@ void SwitchRAMWRT(uint16_t, uint8_t);
 void SwitchSLOTCXROM(uint16_t, uint8_t);
 void SwitchALTZP(uint16_t, uint8_t);
 void SwitchSLOTC3ROM(uint16_t, uint8_t);
+uint8_t SwitchINTC8ROMR(uint16_t);
+void SwitchINTC8ROMW(uint16_t, uint8_t);
 void Switch80COL(uint16_t, uint8_t);
 void SwitchALTCHARSET(uint16_t, uint8_t);
 uint8_t ReadKeyStrobe(uint16_t);
@@ -123,17 +134,12 @@ void SwitchHIRESW(uint16_t, uint8_t);
 uint8_t SwitchDHIRESR(uint16_t);
 void SwitchDHIRESW(uint16_t, uint8_t);
 void SwitchIOUDIS(uint16_t, uint8_t);
-uint8_t Slot6R(uint16_t);
-void Slot6W(uint16_t, uint8_t);
-void HandleSlot6(uint16_t, uint8_t);
+uint8_t ReadCassetteIn(uint16_t);
 uint8_t ReadButton0(uint16_t);
 uint8_t ReadButton1(uint16_t);
 uint8_t ReadPaddle0(uint16_t);
 uint8_t ReadIOUDIS(uint16_t);
 uint8_t ReadDHIRES(uint16_t);
-//uint8_t SwitchR(uint16_t);
-//void SwitchW(uint16_t, uint8_t);
-
 
 // The main Apple //e memory map
 AddressMap memoryMap[] = {
@@ -168,32 +174,61 @@ AddressMap memoryMap[] = {
        { 0xC01D, 0xC01D, AM_READ_WRITE, 0, 0, ReadHIRES, WriteKeyStrobe },
        { 0xC01E, 0xC01E, AM_READ_WRITE, 0, 0, ReadALTCHARSET, WriteKeyStrobe },
        { 0xC01F, 0xC01F, AM_READ_WRITE, 0, 0, Read80COL, WriteKeyStrobe },
+       // $C020 is "Cassette Out (RO)"
+       { 0xC020, 0xC02F, AM_READ, 0, 0, ReadFloatingBus, 0 },
+       // May have to put a "floating bus" read there... :-/
+       // Apparently, video RAM is put on 'non-responding address'. So will
+       // need to time those out.
+       // So... $C020-$C08F, when read, return video data.
+       // $C090-$C7FF do also, as long as the slot the range refers to is empty
+       // and last and least is $CFFF, which is the Expansion ROM disable.
        { 0xC030, 0xC03F, AM_READ_WRITE, 0, 0, ReadSpeaker, WriteSpeaker },
        { 0xC050, 0xC051, AM_READ_WRITE, 0, 0, SwitchTEXTR, SwitchTEXTW },
        { 0xC052, 0xC053, AM_READ_WRITE, 0, 0, SwitchMIXEDR, SwitchMIXEDW },
        { 0xC054, 0xC055, AM_READ_WRITE, 0, 0, SwitchPAGE2R, SwitchPAGE2W },
        { 0xC056, 0xC057, AM_READ_WRITE, 0, 0, SwitchHIRESR, SwitchHIRESW },
        { 0xC05E, 0xC05F, AM_READ_WRITE, 0, 0, SwitchDHIRESR, SwitchDHIRESW },
+       // $C060 is Cassette IN.  No idea what it reads with N/C
+       { 0xC060, 0xC060, AM_READ, 0, 0, ReadCassetteIn, 0 },
        { 0xC061, 0xC061, AM_READ, 0, 0, ReadButton0, 0 },
        { 0xC062, 0xC062, AM_READ, 0, 0, ReadButton1, 0 },
        { 0xC064, 0xC067, AM_READ, 0, 0, ReadPaddle0, 0 },
-//     { 0xC07E, 0xC07F, AM_READ_WRITE, 0, 0, SwitchIOUDISR, SwitchIOUDISW },
        { 0xC07E, 0xC07E, AM_READ_WRITE, 0, 0, ReadIOUDIS, SwitchIOUDIS },
        { 0xC07F, 0xC07F, AM_READ_WRITE, 0, 0, ReadDHIRES, SwitchIOUDIS },
        { 0xC080, 0xC08F, AM_READ_WRITE, 0, 0, SwitchLCR, SwitchLCW },
-       { 0xC0E0, 0xC0EF, AM_READ_WRITE, 0, 0, Slot6R, Slot6W },
-       { 0xC100, 0xCFFF, AM_ROM, &slotMemory, 0, 0, 0 },
 
-       // This will overlay the slotMemory accessors for slot 6 ROM
-       { 0xC300, 0xC3FF, AM_ROM, &slot3Memory, 0, 0, 0 },
-       { 0xC600, 0xC6FF, AM_ROM, &slot6Memory, 0, 0, 0 },
+       { 0xC100, 0xC7FF, AM_READ_WRITE, 0, 0, SlotR, SlotW },
+       { 0xC800, 0xCFFE, AM_READ_WRITE, 0, 0, Slot2KR, Slot2KW },
+       { 0xCFFF, 0xCFFF, AM_READ_WRITE, 0, 0, SwitchINTC8ROMR, SwitchINTC8ROMW },
 
        { 0xD000, 0xDFFF, AM_BANKED, &lcBankMemoryR, &lcBankMemoryW, 0, 0 },
        { 0xE000, 0xFFFF, AM_BANKED, &upperMemoryR, &upperMemoryW, 0, 0 },
-//     { 0x0000, 0x0000, AM_END_OF_LIST, 0, 0, 0, 0 }
        ADDRESS_MAP_END
 };
+/*
+Some stuff that may be useful:
+
+N.B.: Page 5-22 of UTA2E has INTC8ROM ON/OFF backwards
+INTC8ROM is turned OFF by R/W access to $CFFF
+INTC8ROM is turned ON by $C3xx access and SLOTC3ROM' (off)
+WRONG: (INTC8ROM on puts card's slot ROM/RAM(?) access in $C800-$CFFF)
+
+OK, so it's slightly more complex than that.  Basically, when there is an access to $CFFF, all peripheral cards must *stop* responding to  I/O STROBE'.  Only when a card gets an I/O SELECT' signal, can it respond to I/O STROBE'.
+
+INTC8ROM inhibits I/O STROBE' and activates the MB ROM in $C800-$CFFF
+INTC8ROM is 1 by access to $C3xx when SLOTC3ROM is 0
+INTC8ROM is 0 by access to $CFFF
 
+ICX = INTCXROM (aka SLOTCXROM), SC3 = SLOTC3ROM
+
+             ICX=0,SC3=0  ICX=0,SC3=1  ICX=1,SC3=0  ICX=1,SC3=1
+$C100-$C2FF   slot         slot         internal     internal
+$C300-$C3FF   internal     slot         internal     internal
+$C400-$CFFF   slot         slot         internal     internal
+
+Read from $C800-$CFFF causes I/O STROBE to go low (and INTCXROM and INTC8ROM are not set)
+
+*/
 
 void SetupAddressMap(void)
 {
@@ -206,6 +241,14 @@ void SetupAddressMap(void)
                addrOffset[i] = 0;
        }
 
+       for(uint32_t i=0; i<8; i++)
+       {
+               slotHandlerR[i] = ReadNOP;
+               slotHandlerW[i] = WriteNOP;
+               slotHandler2KR[i] = ReadNOP;
+               slotHandler2KW[i] = WriteNOP;
+       }
+
        uint32_t i=0;
 
        while (memoryMap[i].type != AM_END_OF_LIST)
@@ -274,7 +317,6 @@ void SetupAddressMap(void)
        SwitchLC();
 }
 
-
 //
 // Reset the MMU state after a power down event
 //
@@ -284,39 +326,115 @@ void ResetMMUPointers(void)
        {
                mainMemoryTextR = (displayPage2 ? &ram2[0x0400] : &ram[0x0400]);
                mainMemoryTextW = (displayPage2 ? &ram2[0x0400] : &ram[0x0400]);
+               mainMemoryHGRR = (displayPage2 ? &ram2[0x2000] : &ram[0x2000]);
+               mainMemoryHGRW = (displayPage2 ? &ram2[0x2000] : &ram[0x2000]);
        }
        else
        {
-               mainMemoryTextR = (ramwrt ? &ram2[0x0400] : &ram[0x0400]);
+// Shouldn't mainMemoryTextR depend on ramrd???  (I think it should...)
+               mainMemoryTextR = (ramrd ? &ram2[0x0400] : &ram[0x0400]);
                mainMemoryTextW = (ramwrt ? &ram2[0x0400] : &ram[0x0400]);
+               mainMemoryHGRR = (ramrd ? &ram2[0x2000] : &ram[0x2000]);
+               mainMemoryHGRW = (ramwrt ? &ram2[0x2000] : &ram[0x2000]);
        }
 
        mainMemoryR = (ramrd ? &ram2[0x0200] : &ram[0x0200]);
-       mainMemoryHGRR = (ramrd ? &ram2[0x2000] : &ram[0x2000]);
        mainMemoryW = (ramwrt ?  &ram2[0x0200] : &ram[0x0200]);
-       mainMemoryHGRW = (ramwrt ? &ram2[0x2000] : &ram[0x2000]);
+//     mainMemoryHGRR = (ramrd ? &ram2[0x2000] : &ram[0x2000]);
+//     mainMemoryHGRW = (ramwrt ? &ram2[0x2000] : &ram[0x2000]);
 
-       slot6Memory = (slotCXROM ? &diskROM[0] : &rom[0xC600]);
-       slot3Memory = (slotC3ROM ? &rom[0] : &rom[0xC300]);
+//     slot6Memory = (intCXROM ? &rom[0xC600] : &diskROM[0]);
+//     slot3Memory = (slotC3ROM ? &rom[0] : &rom[0xC300]);
        pageZeroMemory = (altzp ? &ram2[0x0000] : &ram[0x0000]);
        SwitchLC();
+#if 1
+WriteLog("RAMWRT = %s\n", (ramwrt ? "ON" : "off"));
+WriteLog("RAMRD = %s\n", (ramrd ? "ON" : "off"));
+WriteLog("SLOTCXROM = %s\n", (intCXROM ? "ON" : "off"));
+WriteLog("SLOTC3ROM = %s\n", (slotC3ROM ? "ON" : "off"));
+WriteLog("ALTZP = %s\n", (altzp ? "ON" : "off"));
+#endif
 }
 
+//
+// Set up slot access
+//
+void InstallSlotHandler(uint8_t slot, SlotData * slotData)
+{
+       // Sanity check
+       if (slot > 7)
+       {
+               WriteLog("InstallSlotHandler: Caller attempted to put device into slot #%u...\n", slot);
+               return;
+       }
+
+       // Set up I/O read & write functions
+       for(uint32_t i=0; i<16; i++)
+       {
+               if (slotData->ioR)
+                       funcMapRead[0xC080 + (slot * 16) + i] = slotData->ioR;
+
+               if (slotData->ioW)
+                       funcMapWrite[0xC080 + (slot * 16) + i] = slotData->ioW;
+       }
+
+       // Set up memory access read/write functions
+       if (slotData->pageR)
+               slotHandlerR[slot] = slotData->pageR;
+
+       if (slotData->pageW)
+               slotHandlerW[slot] = slotData->pageW;
+
+       if (slotData->extraR)
+               slotHandler2KR[slot] = slotData->extraR;
+
+       if (slotData->extraW)
+               slotHandler2KW[slot] = slotData->extraW;
+/*
+Was thinking about how to make these things more self-contained, so that the management overhead would be less.  IOW, you should be able to make an object (struct) that holds everything needed to interface the MMU with itself--InstallSlotHandler *almost* does this, but not quite.  A consequence of this approach is that we would have to add generic slot I/O handlers into the mix, but that shouldn't be too horrible.  So it could be something like so:
+
+struct Card
+{
+       void * object;
+       uint16_t type;  // Probably an enum so we can figure out what 'object' is
+       READFUNC(slotIOR);
+       WRITEFUNC(slotIOW);
+       READFUNC(slotPageR);
+       WRITEFUNC(slotPageW);
+       READFUNC(slot2KR);
+       WRITEFUNC(slot2KW);
+}
+
+So instead of a bunch of crappy shit that sucks in here, we would have a simple thing like:
+
+Card * slots[8];
+
+to encapsulate slots.  This also makes it easier to move them around and makes things less error prone.
+
+So maybe...
+
+
+*/
+}
 
 //
 // Built-in functions
 //
 uint8_t ReadNOP(uint16_t)
 {
-       return 0;
+       // This is for unconnected reads, and some software looks at addresses like
+       // these.  In particular, Mr. Robot and His Robot Factory failed in that it
+       // was looking at the first byte of each slots 256 byte driver space and
+       // failing if it saw a zero there.  Now I have no idea what happens in the
+       // real hardware, but I suspect it would return something that looks like
+       // ReadFloatingBus().
+       return 0xFF;
 }
 
-
 void WriteNOP(uint16_t, uint8_t)
 {
 }
 
-
 uint8_t ReadMemory(uint16_t address)
 {
 //WriteLog("ReadMemory: addr=$%04X, addrPtrRead[addr]=$%X, addrOffset[addr]=$%X, val=$%02X\n", address, addrPtrRead[address], addrOffset[address], addrPtrRead[address][addrOffset[address]]);
@@ -325,7 +443,6 @@ uint8_t ReadMemory(uint16_t address)
        return (*addrPtrRead[address])[addrOffset[address]];
 }
 
-
 void WriteMemory(uint16_t address, uint8_t byte)
 {
        // We can write protect memory this way, but it adds a branch to the mix.
@@ -337,21 +454,128 @@ void WriteMemory(uint16_t address, uint8_t byte)
        (*addrPtrWrite[address])[addrOffset[address]] = byte;
 }
 
-
 //
 // The main memory access functions used by V65C02
 //
 uint8_t AppleReadMem(uint16_t address)
 {
+#if 0
+if (address == 0xD4 || address == 0xAC20)
+       WriteLog("Reading $%X...\n", address);
+#endif
+#if 0
+       uint8_t memRead = (*(funcMapRead[address]))(address);
+static uint16_t lastAddr = 0;
+static uint32_t lastCount = 0;
+if ((address > 0xC000 && address < 0xC100) || address == 0xC601)
+{
+       if (lastAddr == address)
+               lastCount++;
+       else
+       {
+               if (lastCount > 1)
+                       WriteLog("%d times...\n", lastCount);
+
+               WriteLog("Reading $%02X from $%X ($%02X, $%02X)\n", memRead, address, diskROM[1], rom[0xC601]);
+               lastCount = 1;
+               lastAddr = address;
+       }
+}
+       return memRead;
+#else
        return (*(funcMapRead[address]))(address);
+#endif
 }
 
-
 void AppleWriteMem(uint16_t address, uint8_t byte)
 {
+#if 0
+static uint16_t lastAddr = 0;
+static uint32_t lastCount = 0;
+if ((address > 0xC000 && address < 0xC100) || address == 0xC601)
+{
+       if (lastAddr == address)
+               lastCount++;
+       else
+       {
+               if (lastCount > 1)
+                       WriteLog("%d times...\n", lastCount);
+
+               WriteLog("Writing to $%X\n", address);
+               lastCount = 1;
+               lastAddr = address;
+       }
+}
+#endif
+#if 0
+if (address == 0xD4 || address == 0xAC20)
+       WriteLog("Writing $%02X @ $%X...\n", byte, address);
+#endif
+#if 0
+//if (address >= 0x0827 && address <= 0x082A)
+if (address == 0x000D)
+       WriteLog("Writing $%02X @ $%X (PC=$%04X)...\n", byte, address, mainCPU.pc);
+#endif
        (*(funcMapWrite[address]))(address, byte);
 }
 
+//
+// Generic slot handlers.  These are set up here so that we can catch INTCXROM,
+// INTC8ROM & SLOTC3ROM here instead of having to catch them in each slot handler.
+//
+uint8_t SlotR(uint16_t address)
+{
+//WriteLog("SlotR: address=$%04X, intCXROM=%d, slotC3ROM=%d, intC8ROM=%d\n", address, intCXROM, slotC3ROM, intC8ROM);
+       if (intCXROM)
+               return rom[address];
+
+       uint8_t slot = (address & 0xF00) >> 8;
+       enabledSlot = slot;
+
+       if ((slotC3ROM == 0) && (slot == 3))
+       {
+               intC8ROM = 1;
+               return rom[address];
+       }
+
+       return (*(slotHandlerR[slot]))(address & 0xFF);
+}
+
+void SlotW(uint16_t address, uint8_t byte)
+{
+       if (intCXROM)
+               return;
+
+       uint8_t slot = (address & 0xF00) >> 8;
+       enabledSlot = slot;
+
+       if ((slotC3ROM == 0) && (slot == 3))
+       {
+               intC8ROM = 1;
+               return;
+       }
+
+       (*(slotHandlerW[slot]))(address & 0xFF, byte);
+}
+
+//
+// Slot handling for 2K address space at $C800-$CFFF
+//
+uint8_t Slot2KR(uint16_t address)
+{
+       if (intCXROM || intC8ROM)
+               return rom[address];
+
+       return (*(slotHandler2KR[enabledSlot]))(address & 0x7FF);
+}
+
+void Slot2KW(uint16_t address, uint8_t byte)
+{
+       if (intCXROM || intC8ROM)
+               return;
+
+       (*(slotHandler2KW[enabledSlot]))(address & 0x7FF, byte);
+}
 
 //
 // Actual emulated I/O functions follow
@@ -361,61 +585,89 @@ uint8_t ReadKeyboard(uint16_t /*addr*/)
        return lastKeyPressed | ((uint8_t)keyDown << 7);
 }
 
-
 void Switch80STORE(uint16_t address, uint8_t)
 {
        store80Mode = (bool)(address & 0x01);
 WriteLog("Setting 80STORE to %s...\n", (store80Mode ? "ON" : "off"));
 
+       // It seems this affects more than just the text RAM, it also seems to affect the graphics RAM as well...
        if (store80Mode)
        {
                mainMemoryTextR = (displayPage2 ? &ram2[0x0400] : &ram[0x0400]);
                mainMemoryTextW = (displayPage2 ? &ram2[0x0400] : &ram[0x0400]);
+               mainMemoryHGRR = (displayPage2 ? &ram2[0x2000] : &ram[0x2000]);
+               mainMemoryHGRW = (displayPage2 ? &ram2[0x2000] : &ram[0x2000]);
        }
        else
        {
-               mainMemoryTextR = (ramwrt ? &ram2[0x0400] : &ram[0x0400]);
+               mainMemoryTextR = (ramrd ? &ram2[0x0400] : &ram[0x0400]);
                mainMemoryTextW = (ramwrt ? &ram2[0x0400] : &ram[0x0400]);
+               mainMemoryHGRR = (ramrd ? &ram2[0x2000] : &ram[0x2000]);
+               mainMemoryHGRW = (ramwrt ? &ram2[0x2000] : &ram[0x2000]);
        }
 }
 
-
 void SwitchRAMRD(uint16_t address, uint8_t)
 {
        ramrd = (bool)(address & 0x01);
        mainMemoryR = (ramrd ? &ram2[0x0200] : &ram[0x0200]);
-       mainMemoryHGRR = (ramrd ? &ram2[0x2000] : &ram[0x2000]);
+//     mainMemoryHGRR = (ramrd ? &ram2[0x2000] : &ram[0x2000]);
 
        if (store80Mode)
                return;
 
        mainMemoryTextR = (ramrd ? &ram2[0x0400] : &ram[0x0400]);
+       mainMemoryHGRR = (ramrd ? &ram2[0x2000] : &ram[0x2000]);
 }
 
-
 void SwitchRAMWRT(uint16_t address, uint8_t)
 {
        ramwrt = (bool)(address & 0x01);
        mainMemoryW = (ramwrt ?  &ram2[0x0200] : &ram[0x0200]);
-       mainMemoryHGRW = (ramwrt ? &ram2[0x2000] : &ram[0x2000]);
+//     mainMemoryHGRW = (ramwrt ? &ram2[0x2000] : &ram[0x2000]);
 
        if (store80Mode)
                return;
 
        mainMemoryTextW = (ramwrt ? &ram2[0x0400] : &ram[0x0400]);
+       mainMemoryHGRW = (ramwrt ? &ram2[0x2000] : &ram[0x2000]);
 }
 
-
+//
+// Since any slots that aren't populated are set to read from the ROM anyway,
+// we only concern ourselves with switching populated slots here.  (Note that
+// the MB slot is a split ROM / I/O device, and it's taken care of in the
+// MB handler.)
+//
+// N.B.: SLOTCXROM is also INTCXROM
+//
 void SwitchSLOTCXROM(uint16_t address, uint8_t)
 {
-//WriteLog("Setting SLOTCXROM to %s...\n", ((address & 0x01) ^ 0x01 ? "ON" : "off"));
-       // This is the only soft switch that breaks the usual convention.
-       slotCXROM = !((bool)(address & 0x01));
-//     slot3Memory = (slotCXROM ? &rom[0] : &rom[0xC300]);
-       slot6Memory = (slotCXROM ? &diskROM[0] : &rom[0xC600]);
+WriteLog("Setting SLOTCXROM to %s...\n", (address & 0x01 ? "ON" : "off"));
+       intCXROM = (bool)(address & 0x01);
+
+       // INTC8ROM trumps all (only in the $C800--$CFFF range... which we don't account for yet...  :-/)
+//     if (intC8ROM)
+//             return;
+#if 0
+#if 1
+       if (intCXROM)
+       {
+               slot3Memory = &rom[0xC300];
+               slot6Memory = &rom[0xC600];
+       }
+       else
+       {
+               slot3Memory = (slotC3ROM ? &rom[0] : &rom[0xC300]);
+               slot6Memory = &diskROM[0];
+       }
+#else
+//     slot3Memory = (intCXROM ? &rom[0xC300] : &rom[0]);
+       slot6Memory = (intCXROM ? &rom[0xC600] : &diskROM[0]);
+#endif
+#endif
 }
 
-
 void SwitchALTZP(uint16_t address, uint8_t)
 {
        altzp = (bool)(address & 0x01);
@@ -424,47 +676,79 @@ void SwitchALTZP(uint16_t address, uint8_t)
 }
 
 //extern bool dumpDis;
-
+//
+// The interpretation of this name is that if it's set then we access the ROM
+// for the card actually sitting in SLOT 3 (if any)
+//
 void SwitchSLOTC3ROM(uint16_t address, uint8_t)
 {
 //dumpDis = true;
 //WriteLog("Setting SLOTC3ROM to %s...\n", (address & 0x01 ? "ON" : "off"));
        slotC3ROM = (bool)(address & 0x01);
+#if 1
+       if (intCXROM)
+               slot3Memory = &rom[0xC300];
+       else
+               slot3Memory = (slotC3ROM ? &rom[0] : &rom[0xC300]);
+#else
 //     slotC3ROM = false;
 // Seems the h/w forces this with an 80 column card in slot 3...
        slot3Memory = (slotC3ROM ? &rom[0] : &rom[0xC300]);
 //     slot3Memory = &rom[0xC300];
+#endif
+}
+
+/*
+We need to see where this is being switched from; if we know that, we can switch in the appropriate ROM to $C800-$CFFF.  N.B.: Will probably need a custom handler routine, as some cards (like the Apple Hi-Speed SCSI card) split the 2K range into a 1K RAM space and a 1K bank switch ROM space.
+*/
+//
+// This is a problem with split ROM / I/O regions.  Because we can't do that
+// cleanly, we have to have a read handler for this.
+//
+// N.B.: We could add AM_IOREAD_WRITE and AM_READ_IOWRITE to the memory handlers
+//       to take care of split ROM / I/O regions...
+//
+uint8_t SwitchINTC8ROMR(uint16_t)
+{
+WriteLog("Hitting INTC8ROM (read)...\n");
+       intC8ROM = false;
+       return rom[0xCFFF];
 }
 
+//
+// This resets the INTC8ROM switch (RW)
+//
+void SwitchINTC8ROMW(uint16_t, uint8_t)
+{
+WriteLog("Hitting INTC8ROM (write)...\n");
+       intC8ROM = false;
+}
 
 void Switch80COL(uint16_t address, uint8_t)
 {
        col80Mode = (bool)(address & 0x01);
 }
 
-
 void SwitchALTCHARSET(uint16_t address, uint8_t)
 {
        alternateCharset = (bool)(address & 0x01);
+WriteLog("Setting ALTCHARSET to %s...\n", (alternateCharset ? "ON" : "off"));
 }
 
-
 uint8_t ReadKeyStrobe(uint16_t)
 {
-// No character data is read from here, just the 'any key was pressed' signal...
-//     uint8_t byte = lastKeyPressed | ((uint8_t)keyDown << 7);
+       // No character data is read from here, just the 'any key was pressed'
+       // signal...
        uint8_t byte = (uint8_t)keyDown << 7;
        keyDown = false;
        return byte;
 }
 
-
 uint8_t ReadBANK2(uint16_t)
 {
        return (lcState < 0x04 ? 0x80 : 0x00);
 }
 
-
 uint8_t ReadLCRAM(uint16_t)
 {
        // If bits 0 & 1 are set, but not at the same time, then it's ROM
@@ -472,120 +756,104 @@ uint8_t ReadLCRAM(uint16_t)
        return (lcROM ? 0x00 : 0x80);
 }
 
-
 uint8_t ReadRAMRD(uint16_t)
 {
        return (uint8_t)ramrd << 7;
 }
 
-
 uint8_t ReadRAMWRT(uint16_t)
 {
        return (uint8_t)ramwrt << 7;
 }
 
-
 uint8_t ReadSLOTCXROM(uint16_t)
 {
-       return (uint8_t)slotCXROM << 7;
+       return (uint8_t)intCXROM << 7;
 }
 
-
 uint8_t ReadALTZP(uint16_t)
 {
        return (uint8_t)altzp << 7;
 }
 
-
 uint8_t ReadSLOTC3ROM(uint16_t)
 {
-//     return 0;
        return (uint8_t)slotC3ROM << 7;
 }
 
-
 uint8_t Read80STORE(uint16_t)
 {
        return (uint8_t)store80Mode << 7;
 }
 
-
 uint8_t ReadVBL(uint16_t)
 {
        return (uint8_t)vbl << 7;
 }
 
-
 uint8_t ReadTEXT(uint16_t)
 {
        return (uint8_t)textMode << 7;
 }
 
-
 uint8_t ReadMIXED(uint16_t)
 {
        return (uint8_t)mixedMode << 7;
 }
 
-
 uint8_t ReadPAGE2(uint16_t)
 {
        return (uint8_t)displayPage2 << 7;
 }
 
-
 uint8_t ReadHIRES(uint16_t)
 {
        return (uint8_t)hiRes << 7;
 }
 
-
 uint8_t ReadALTCHARSET(uint16_t)
 {
        return (uint8_t)alternateCharset << 7;
 }
 
-
 uint8_t Read80COL(uint16_t)
 {
        return (uint8_t)col80Mode << 7;
 }
 
-
 void WriteKeyStrobe(uint16_t, uint8_t)
 {
        keyDown = false;
 }
 
-
 uint8_t ReadSpeaker(uint16_t)
 {
        ToggleSpeaker();
-       return 0;
+//     return 0;
+       // Seems this is needed for some things...
+       return ReadFloatingBus(0);
 }
 
-
 void WriteSpeaker(uint16_t, uint8_t)
 {
        ToggleSpeaker();
 }
 
-
 uint8_t SwitchLCR(uint16_t address)
 {
        lcState = address & 0x0B;
        SwitchLC();
-       return 0;
+//     return 0;
+       // Seems this is needed for some things...
+       return ReadFloatingBus(0);
 }
 
-
 void SwitchLCW(uint16_t address, uint8_t)
 {
        lcState = address & 0x0B;
        SwitchLC();
 }
 
-
 void SwitchLC(void)
 {
        switch (lcState)
@@ -661,37 +929,39 @@ WriteLog("SwitchLC: Read/write bank 2\n");
        }
 }
 
-
 uint8_t SwitchTEXTR(uint16_t address)
 {
 WriteLog("Setting TEXT to %s...\n", (address & 0x01 ? "ON" : "off"));
        textMode = (bool)(address & 0x01);
-       return 0;
+//     return 0;
+       // Seems this is needed for some things...
+       return ReadFloatingBus(0);
 }
 
-
 void SwitchTEXTW(uint16_t address, uint8_t)
 {
 WriteLog("Setting TEXT to %s...\n", (address & 0x01 ? "ON" : "off"));
        textMode = (bool)(address & 0x01);
 }
 
-
 uint8_t SwitchMIXEDR(uint16_t address)
 {
 WriteLog("Setting MIXED to %s...\n", (address & 0x01 ? "ON" : "off"));
        mixedMode = (bool)(address & 0x01);
-       return 0;
+//     return 0;
+       // Seems this is needed for some things...
+       return ReadFloatingBus(0);
 }
 
-
 void SwitchMIXEDW(uint16_t address, uint8_t)
 {
 WriteLog("Setting MIXED to %s...\n", (address & 0x01 ? "ON" : "off"));
        mixedMode = (bool)(address & 0x01);
 }
 
-
+/*
+80STORE, PAGE2, and HIRES bank switch the primary display pages, $400--$7FF and $2000--$3FFF, between motherboard RAM and auxiliary card RAM.  If 80STORE is set and HIRES is reset, then PAGE2 switches between motherboard RAM and auxiliary card RAM for reading and writing in the $400--$7FF range.  If 80STORE is set and HIRES is set, then PAGE2 switches between motherboard RAM and auxiliary card RAM for reading and writing in the $400--$7FF and $2000--$3FFF ranges.  PAGE2 set selects auxiliary card RAM, and PAGE2 reset selects motherboard RAM.  If 80STORE is reset, then RAMRD and RAMWRT will bank switch the $400-$7FF and $2000--$3FFF ranges along with the rest of the $200--$BFFF range.
+*/
 uint8_t SwitchPAGE2R(uint16_t address)
 {
 WriteLog("Setting PAGE2 to %s...\n", (address & 0x01 ? "ON" : "off"));
@@ -701,12 +971,18 @@ WriteLog("Setting PAGE2 to %s...\n", (address & 0x01 ? "ON" : "off"));
        {
                mainMemoryTextR = (displayPage2 ? &ram2[0x0400] : &ram[0x0400]);
                mainMemoryTextW = (displayPage2 ? &ram2[0x0400] : &ram[0x0400]);
+
+               if (hiRes)
+               {
+                       mainMemoryHGRR = (displayPage2 ? &ram2[0x2000] : &ram[0x2000]);
+                       mainMemoryHGRW = (displayPage2 ? &ram2[0x2000] : &ram[0x2000]);
+               }
        }
 
-       return 0;
+       // Seems this is needed for some things...
+       return ReadFloatingBus(0);
 }
 
-
 void SwitchPAGE2W(uint16_t address, uint8_t)
 {
 WriteLog("Setting PAGE2 to %s...\n", (address & 0x01 ? "ON" : "off"));
@@ -716,25 +992,30 @@ WriteLog("Setting PAGE2 to %s...\n", (address & 0x01 ? "ON" : "off"));
        {
                mainMemoryTextR = (displayPage2 ? &ram2[0x0400] : &ram[0x0400]);
                mainMemoryTextW = (displayPage2 ? &ram2[0x0400] : &ram[0x0400]);
+
+               if (hiRes)
+               {
+                       mainMemoryHGRR = (displayPage2 ? &ram2[0x2000] : &ram[0x2000]);
+                       mainMemoryHGRW = (displayPage2 ? &ram2[0x2000] : &ram[0x2000]);
+               }
        }
 }
 
-
 uint8_t SwitchHIRESR(uint16_t address)
 {
 WriteLog("Setting HIRES to %s...\n", (address & 0x01 ? "ON" : "off"));
        hiRes = (bool)(address & 0x01);
-       return 0;
+//     return 0;
+       // Seems this is needed for some things...
+       return ReadFloatingBus(0);
 }
 
-
 void SwitchHIRESW(uint16_t address, uint8_t)
 {
 WriteLog("Setting HIRES to %s...\n", (address & 0x01 ? "ON" : "off"));
        hiRes = (bool)(address & 0x01);
 }
 
-
 uint8_t SwitchDHIRESR(uint16_t address)
 {
 WriteLog("Setting DHIRES to %s (ioudis = %s)...\n", ((address & 0x01) ^ 0x01 ? "ON" : "off"), (ioudis ? "ON" : "off"));
@@ -742,10 +1023,11 @@ WriteLog("Setting DHIRES to %s (ioudis = %s)...\n", ((address & 0x01) ^ 0x01 ? "
        if (ioudis)
                dhires = !((bool)(address & 0x01));
 
-       return 0;
+//     return 0;
+       // Seems this is needed for some things...
+       return ReadFloatingBus(0);
 }
 
-
 void SwitchDHIRESW(uint16_t address, uint8_t)
 {
 WriteLog("Setting DHIRES to %s (ioudis = %s)...\n", ((address & 0x01) ^ 0x01 ? "ON" : "off"), (ioudis ? "ON" : "off"));
@@ -753,117 +1035,29 @@ WriteLog("Setting DHIRES to %s (ioudis = %s)...\n", ((address & 0x01) ^ 0x01 ? "
                dhires = !((bool)(address & 0x01));
 }
 
-
 void SwitchIOUDIS(uint16_t address, uint8_t)
 {
        ioudis = !((bool)(address & 0x01));
 }
 
-
-uint8_t Slot6R(uint16_t address)
-{
-//WriteLog("Slot6R: address = %X\n", address & 0x0F);
-//     HandleSlot6(address, 0);
-//     return 0;
-       uint8_t state = address & 0x0F;
-
-       switch (state)
-       {
-       case 0x00:
-       case 0x01:
-       case 0x02:
-       case 0x03:
-       case 0x04:
-       case 0x05:
-       case 0x06:
-       case 0x07:
-               floppyDrive.ControlStepper(state);
-               break;
-       case 0x08:
-       case 0x09:
-               floppyDrive.ControlMotor(state & 0x01);
-               break;
-       case 0x0A:
-       case 0x0B:
-               floppyDrive.DriveEnable(state & 0x01);
-               break;
-       case 0x0C:
-               return floppyDrive.ReadWrite();
-               break;
-       case 0x0D:
-               return floppyDrive.GetLatchValue();
-               break;
-       case 0x0E:
-               floppyDrive.SetReadMode();
-               break;
-       case 0x0F:
-               floppyDrive.SetWriteMode();
-               break;
-       }
-
-       return 0;
-}
-
-
-void Slot6W(uint16_t address, uint8_t byte)
-{
-//WriteLog("Slot6W: address = %X, byte= %X\n", address & 0x0F, byte);
-//     HandleSlot6(address, byte);
-       uint8_t state = address & 0x0F;
-
-       switch (state)
-       {
-       case 0x00:
-       case 0x01:
-       case 0x02:
-       case 0x03:
-       case 0x04:
-       case 0x05:
-       case 0x06:
-       case 0x07:
-               floppyDrive.ControlStepper(state);
-               break;
-       case 0x08:
-       case 0x09:
-               floppyDrive.ControlMotor(state & 0x01);
-               break;
-       case 0x0A:
-       case 0x0B:
-               floppyDrive.DriveEnable(state & 0x01);
-               break;
-       case 0x0C:
-               floppyDrive.ReadWrite();
-               break;
-       case 0x0D:
-               floppyDrive.SetLatchValue(byte);
-               break;
-       case 0x0E:
-               floppyDrive.SetReadMode();
-               break;
-       case 0x0F:
-               floppyDrive.SetWriteMode();
-               break;
-       }
-}
-
-
-void HandleSlot6(uint16_t address, uint8_t byte)
+uint8_t ReadCassetteIn(uint16_t)
 {
+       // No idea what it's supposed to return if there's no cassette attached, so let's try this (Serpentine crashes if $FF is returned...)
+       // Serpentine crashes even earlier if $00 is returned...  Now what???
+       // This seems to work for Serpentine, not sure what's supposed to be returned here...  Maybe it's an RNG when N/C?
+       return 0x5A;
 }
 
-
 uint8_t ReadButton0(uint16_t)
 {
        return (uint8_t)openAppleDown << 7;
 }
 
-
 uint8_t ReadButton1(uint16_t)
 {
        return (uint8_t)closedAppleDown << 7;
 }
 
-
 // The way the paddles work is that a strobe is written (or read) to $C070,
 // then software counts down the time that it takes for the paddle outputs
 // to have bit 7 return to 0. If there are no paddles connected, bit 7
@@ -874,16 +1068,55 @@ uint8_t ReadPaddle0(uint16_t)
        return 0xFF;
 }
 
-
 uint8_t ReadIOUDIS(uint16_t)
 {
        return (uint8_t)ioudis << 7;
 }
 
-
 uint8_t ReadDHIRES(uint16_t)
 {
        return (uint8_t)dhires << 7;
 }
 
+// Whenever a read is done to a MMIO location that is unconnected to anything,
+// it actually sees the RAM access done by the video generation hardware. Some
+// programs exploit this, so we emulate it here.
+
+// N.B.: frameCycles will be off by the true amount because this only
+//       increments by the amount of a speaker cycle, not the cycle count when
+//       the access happens... !!! FIX !!!
+uint8_t ReadFloatingBus(uint16_t)
+{
+       // Get the currently elapsed cycle count for this frame
+       uint32_t frameCycles = mainCPU.clock - frameCycleStart;
+
+       // Make counters out of the cycle count. There are 65 cycles per line.
+       uint32_t numLines = frameCycles / 65;
+       uint32_t numHTicks = frameCycles - (numLines * 65);
 
+       // Convert these to H/V counters
+       uint32_t hcount = numHTicks - 1;
+
+       // HC sees zero twice:
+       if (hcount == 0xFFFFFFFF)
+               hcount = 0;
+
+       uint32_t vcount = numLines + 0xFA;
+
+       // Now do the address calculations
+       uint32_t sum = 0xD + ((hcount & 0x38) >> 3)
+               + (((vcount & 0xC0) >> 6) | ((vcount & 0xC0) >> 4));
+       uint32_t address = ((vcount & 0x38) << 4) | ((sum & 0x0F) << 3) | (hcount & 0x07);
+
+       // Add in particulars for the gfx mode we're in...
+       if (textMode || (!textMode && !hiRes))
+               address |= (!(!store80Mode && displayPage2) ? 0x400 : 0)
+                       | (!store80Mode && displayPage2 ? 0x800 : 0);
+       else
+               address |= (!(!store80Mode && displayPage2) ? 0x2000: 0)
+                       | (!store80Mode && displayPage2 ? 0x4000 : 0)
+                       | ((vcount & 0x07) << 10);
+
+       // The address so read is *always* in main RAM, never alt RAM
+       return ram[address];
+}