]> Shamusworld >> Repos - apple2/blob - src/mmu.cpp
Miscellaneous bugfixes.
[apple2] / src / mmu.cpp
1 //
2 // mmu.cpp: Memory management
3 //
4 // by James Hammons
5 // (C) 2013-2018 Underground Software
6 //
7 // JLH = James Hammons <jlhamm@acm.org>
8 //
9 // WHO  WHEN        WHAT
10 // ---  ----------  -----------------------------------------------------------
11 // JLH  09/27/2013  Created this file
12 //
13
14 #include "mmu.h"
15 #include "apple2.h"
16 #include "firmware/firmware.h"
17 #include "log.h"
18 #include "mockingboard.h"
19 #include "sound.h"
20 #include "video.h"
21
22 // Debug defines
23 //#define LC_DEBUG
24
25 // Address Map enumeration
26 enum { AM_RAM, AM_ROM, AM_BANKED, AM_READ, AM_WRITE, AM_READ_WRITE, AM_END_OF_LIST };
27
28 // Internal vars
29 uint8_t ** addrPtrRead[0x10000];
30 uint8_t ** addrPtrWrite[0x10000];
31 uint16_t addrOffset[0x10000];
32
33 READFUNC(funcMapRead[0x10000]);
34 WRITEFUNC(funcMapWrite[0x10000]);
35
36 READFUNC(slotHandlerR[8]);
37 WRITEFUNC(slotHandlerW[8]);
38
39 READFUNC(slotHandler2KR[8]);
40 WRITEFUNC(slotHandler2KW[8]);
41
42 uint8_t enabledSlot;
43
44 struct AddressMap
45 {
46         uint16_t start;
47         uint16_t end;
48         int type;
49         uint8_t ** memory;
50         uint8_t ** altMemory;
51         READFUNC(read);
52         WRITEFUNC(write);
53 };
54
55 #define ADDRESS_MAP_END         { 0x0000, 0x0000, AM_END_OF_LIST, 0, 0, 0, 0 }
56
57 // Dunno if I like this approach or not...
58 //ADDRESS_MAP_START()
59 //      AM_RANGE(0x0000, 0xBFFF) AM_RAM AM_BASE(ram) AM_SHARE(1)
60 //      AM_RANGE(0xC000, 0xC001) AM_READWRITE(readFunc, writeFunc)
61 //ADDRESS_MAP_END
62
63 // Would need a pointer for 80STORE as well...
64
65 uint8_t * pageZeroMemory  = &ram[0x0000];       // $0000 - $01FF
66 uint8_t * mainMemoryR     = &ram[0x0200];       // $0200 - $BFFF (read)
67 uint8_t * mainMemoryW     = &ram[0x0200];       // $0200 - $BFFF (write)
68
69 uint8_t * mainMemoryTextR = &ram[0x0400];       // $0400 - $07FF (read)
70 uint8_t * mainMemoryTextW = &ram[0x0400];       // $0400 - $07FF (write)
71 uint8_t * mainMemoryHGRR  = &ram[0x2000];       // $2000 - $3FFF (read)
72 uint8_t * mainMemoryHGRW  = &ram[0x2000];       // $2000 - $3FFF (write)
73
74 uint8_t * slotMemory      = &rom[0xC100];       // $C100 - $C7FF
75 uint8_t * peripheralMemory= &rom[0xC800];       // $C800 - $CFFF
76 uint8_t * slot3Memory     = &rom[0xC300];       // $C300 - $C3FF
77 uint8_t * slot4Memory     = &rom[0xC400];       // $C400 - $C4FF
78 uint8_t * slot6Memory     = &diskROM[0];        // $C600 - $C6FF
79 uint8_t * lcBankMemoryR   = &ram[0xD000];       // $D000 - $DFFF (read)
80 uint8_t * lcBankMemoryW   = &ram[0xD000];       // $D000 - $DFFF (write)
81 uint8_t * upperMemoryR    = &ram[0xE000];       // $E000 - $FFFF (read)
82 uint8_t * upperMemoryW    = &ram[0xE000];       // $E000 - $FFFF (write)
83
84 // Function prototypes
85 uint8_t ReadNOP(uint16_t);
86 void WriteNOP(uint16_t, uint8_t);
87 uint8_t ReadMemory(uint16_t);
88 void WriteMemory(uint16_t, uint8_t);
89 uint8_t SlotR(uint16_t address);
90 void SlotW(uint16_t address, uint8_t byte);
91 uint8_t Slot2KR(uint16_t address);
92 void Slot2KW(uint16_t address, uint8_t byte);
93 uint8_t ReadKeyboard(uint16_t);
94 void Switch80STORE(uint16_t, uint8_t);
95 void SwitchRAMRD(uint16_t, uint8_t);
96 void SwitchRAMWRT(uint16_t, uint8_t);
97 void SwitchSLOTCXROM(uint16_t, uint8_t);
98 void SwitchALTZP(uint16_t, uint8_t);
99 void SwitchSLOTC3ROM(uint16_t, uint8_t);
100 uint8_t SwitchINTC8ROMR(uint16_t);
101 void SwitchINTC8ROMW(uint16_t, uint8_t);
102 void Switch80COL(uint16_t, uint8_t);
103 void SwitchALTCHARSET(uint16_t, uint8_t);
104 uint8_t ReadKeyStrobe(uint16_t);
105 uint8_t ReadBANK2(uint16_t);
106 uint8_t ReadLCRAM(uint16_t);
107 uint8_t ReadRAMRD(uint16_t);
108 uint8_t ReadRAMWRT(uint16_t);
109 uint8_t ReadSLOTCXROM(uint16_t);
110 uint8_t ReadALTZP(uint16_t);
111 uint8_t ReadSLOTC3ROM(uint16_t);
112 uint8_t Read80STORE(uint16_t);
113 uint8_t ReadVBL(uint16_t);
114 uint8_t ReadTEXT(uint16_t);
115 uint8_t ReadMIXED(uint16_t);
116 uint8_t ReadPAGE2(uint16_t);
117 uint8_t ReadHIRES(uint16_t);
118 uint8_t ReadALTCHARSET(uint16_t);
119 uint8_t Read80COL(uint16_t);
120 void WriteKeyStrobe(uint16_t, uint8_t);
121 uint8_t ReadSpeaker(uint16_t);
122 void WriteSpeaker(uint16_t, uint8_t);
123 uint8_t SwitchLCR(uint16_t);
124 void SwitchLCW(uint16_t, uint8_t);
125 void SwitchLC(void);
126 uint8_t SwitchTEXTR(uint16_t);
127 void SwitchTEXTW(uint16_t, uint8_t);
128 uint8_t SwitchMIXEDR(uint16_t);
129 void SwitchMIXEDW(uint16_t, uint8_t);
130 uint8_t SwitchPAGE2R(uint16_t);
131 void SwitchPAGE2W(uint16_t, uint8_t);
132 uint8_t SwitchHIRESR(uint16_t);
133 void SwitchHIRESW(uint16_t, uint8_t);
134 uint8_t SwitchDHIRESR(uint16_t);
135 void SwitchDHIRESW(uint16_t, uint8_t);
136 void SwitchIOUDIS(uint16_t, uint8_t);
137 uint8_t ReadCassetteIn(uint16_t);
138 uint8_t ReadButton0(uint16_t);
139 uint8_t ReadButton1(uint16_t);
140 uint8_t ReadPaddle0(uint16_t);
141 uint8_t ReadIOUDIS(uint16_t);
142 uint8_t ReadDHIRES(uint16_t);
143
144 // The main Apple //e memory map
145 AddressMap memoryMap[] = {
146         { 0x0000, 0x01FF, AM_RAM, &pageZeroMemory, 0, 0, 0 },
147         { 0x0200, 0xBFFF, AM_BANKED, &mainMemoryR, &mainMemoryW, 0, 0 },
148
149         // These will overlay over the previously written memory accessors
150         { 0x0400, 0x07FF, AM_BANKED, &mainMemoryTextR, &mainMemoryTextW, 0, 0 },
151         { 0x2000, 0x3FFF, AM_BANKED, &mainMemoryHGRR, &mainMemoryHGRW, 0, 0 },
152
153         { 0xC000, 0xC001, AM_READ_WRITE, 0, 0, ReadKeyboard, Switch80STORE },
154         { 0xC002, 0xC003, AM_READ_WRITE, 0, 0, ReadKeyboard, SwitchRAMRD },
155         { 0xC004, 0xC005, AM_READ_WRITE, 0, 0, ReadKeyboard, SwitchRAMWRT },
156         { 0xC006, 0xC007, AM_READ_WRITE, 0, 0, ReadKeyboard, SwitchSLOTCXROM },
157         { 0xC008, 0xC009, AM_READ_WRITE, 0, 0, ReadKeyboard, SwitchALTZP },
158         { 0xC00A, 0xC00B, AM_READ_WRITE, 0, 0, ReadKeyboard, SwitchSLOTC3ROM },
159         { 0xC00C, 0xC00D, AM_READ_WRITE, 0, 0, ReadKeyboard, Switch80COL },
160         { 0xC00E, 0xC00F, AM_READ_WRITE, 0, 0, ReadKeyboard, SwitchALTCHARSET },
161         { 0xC010, 0xC010, AM_READ_WRITE, 0, 0, ReadKeyStrobe, WriteKeyStrobe },
162         { 0xC011, 0xC011, AM_READ_WRITE, 0, 0, ReadBANK2, WriteKeyStrobe },
163         { 0xC012, 0xC012, AM_READ_WRITE, 0, 0, ReadLCRAM, WriteKeyStrobe },
164         { 0xC013, 0xC013, AM_READ_WRITE, 0, 0, ReadRAMRD, WriteKeyStrobe },
165         { 0xC014, 0xC014, AM_READ_WRITE, 0, 0, ReadRAMWRT, WriteKeyStrobe },
166         { 0xC015, 0xC015, AM_READ_WRITE, 0, 0, ReadSLOTCXROM, WriteKeyStrobe },
167         { 0xC016, 0xC016, AM_READ_WRITE, 0, 0, ReadALTZP, WriteKeyStrobe },
168         { 0xC017, 0xC017, AM_READ_WRITE, 0, 0, ReadSLOTC3ROM, WriteKeyStrobe },
169         { 0xC018, 0xC018, AM_READ_WRITE, 0, 0, Read80STORE, WriteKeyStrobe },
170         { 0xC019, 0xC019, AM_READ_WRITE, 0, 0, ReadVBL, WriteKeyStrobe },
171         { 0xC01A, 0xC01A, AM_READ_WRITE, 0, 0, ReadTEXT, WriteKeyStrobe },
172         { 0xC01B, 0xC01B, AM_READ_WRITE, 0, 0, ReadMIXED, WriteKeyStrobe },
173         { 0xC01C, 0xC01C, AM_READ_WRITE, 0, 0, ReadPAGE2, WriteKeyStrobe },
174         { 0xC01D, 0xC01D, AM_READ_WRITE, 0, 0, ReadHIRES, WriteKeyStrobe },
175         { 0xC01E, 0xC01E, AM_READ_WRITE, 0, 0, ReadALTCHARSET, WriteKeyStrobe },
176         { 0xC01F, 0xC01F, AM_READ_WRITE, 0, 0, Read80COL, WriteKeyStrobe },
177         // $C020 is "Cassette Out (RO)"
178         { 0xC020, 0xC02F, AM_READ, 0, 0, ReadFloatingBus, 0 },
179         // May have to put a "floating bus" read there... :-/
180         // Apparently, video RAM is put on 'non-responding address'. So will
181         // need to time those out.
182         // So... $C020-$C08F, when read, return video data.
183         // $C090-$C7FF do also, as long as the slot the range refers to is empty
184         // and last and least is $CFFF, which is the Expansion ROM disable.
185         { 0xC030, 0xC03F, AM_READ_WRITE, 0, 0, ReadSpeaker, WriteSpeaker },
186         { 0xC050, 0xC051, AM_READ_WRITE, 0, 0, SwitchTEXTR, SwitchTEXTW },
187         { 0xC052, 0xC053, AM_READ_WRITE, 0, 0, SwitchMIXEDR, SwitchMIXEDW },
188         { 0xC054, 0xC055, AM_READ_WRITE, 0, 0, SwitchPAGE2R, SwitchPAGE2W },
189         { 0xC056, 0xC057, AM_READ_WRITE, 0, 0, SwitchHIRESR, SwitchHIRESW },
190         { 0xC05E, 0xC05F, AM_READ_WRITE, 0, 0, SwitchDHIRESR, SwitchDHIRESW },
191         // $C060 is Cassette IN.  No idea what it reads with N/C
192         { 0xC060, 0xC060, AM_READ, 0, 0, ReadCassetteIn, 0 },
193         { 0xC061, 0xC061, AM_READ, 0, 0, ReadButton0, 0 },
194         { 0xC062, 0xC062, AM_READ, 0, 0, ReadButton1, 0 },
195         { 0xC064, 0xC067, AM_READ, 0, 0, ReadPaddle0, 0 },
196         { 0xC07E, 0xC07E, AM_READ_WRITE, 0, 0, ReadIOUDIS, SwitchIOUDIS },
197         { 0xC07F, 0xC07F, AM_READ_WRITE, 0, 0, ReadDHIRES, SwitchIOUDIS },
198         { 0xC080, 0xC08F, AM_READ_WRITE, 0, 0, SwitchLCR, SwitchLCW },
199
200         { 0xC100, 0xC7FF, AM_READ_WRITE, 0, 0, SlotR, SlotW },
201         { 0xC800, 0xCFFE, AM_READ_WRITE, 0, 0, Slot2KR, Slot2KW },
202         { 0xCFFF, 0xCFFF, AM_READ_WRITE, 0, 0, SwitchINTC8ROMR, SwitchINTC8ROMW },
203
204         { 0xD000, 0xDFFF, AM_BANKED, &lcBankMemoryR, &lcBankMemoryW, 0, 0 },
205         { 0xE000, 0xFFFF, AM_BANKED, &upperMemoryR, &upperMemoryW, 0, 0 },
206         ADDRESS_MAP_END
207 };
208 /*
209 Some stuff that may be useful:
210
211 N.B.: Page 5-22 of UTA2E has INTC8ROM ON/OFF backwards
212 INTC8ROM is turned OFF by R/W access to $CFFF
213 INTC8ROM is turned ON by $C3xx access and SLOTC3ROM' (off)
214 WRONG: (INTC8ROM on puts card's slot ROM/RAM(?) access in $C800-$CFFF)
215
216 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'.
217
218 INTC8ROM inhibits I/O STROBE' and activates the MB ROM in $C800-$CFFF
219 INTC8ROM is 1 by access to $C3xx when SLOTC3ROM is 0
220 INTC8ROM is 0 by access to $CFFF
221
222 ICX = INTCXROM (aka SLOTCXROM), SC3 = SLOTC3ROM
223
224              ICX=0,SC3=0  ICX=0,SC3=1  ICX=1,SC3=0  ICX=1,SC3=1
225 $C100-$C2FF   slot         slot         internal     internal
226 $C300-$C3FF   internal     slot         internal     internal
227 $C400-$CFFF   slot         slot         internal     internal
228
229 Read from $C800-$CFFF causes I/O STROBE to go low (and INTCXROM and INTC8ROM are not set)
230
231 */
232
233 void SetupAddressMap(void)
234 {
235         for(uint32_t i=0; i<0x10000; i++)
236         {
237                 funcMapRead[i] = ReadNOP;
238                 funcMapWrite[i] = WriteNOP;
239                 addrPtrRead[i] = 0;
240                 addrPtrWrite[i] = 0;
241                 addrOffset[i] = 0;
242         }
243
244         for(uint32_t i=0; i<8; i++)
245         {
246                 slotHandlerR[i] = ReadNOP;
247                 slotHandlerW[i] = WriteNOP;
248                 slotHandler2KR[i] = ReadNOP;
249                 slotHandler2KW[i] = WriteNOP;
250         }
251
252         uint32_t i=0;
253
254         while (memoryMap[i].type != AM_END_OF_LIST)
255         {
256                 switch (memoryMap[i].type)
257                 {
258                 case AM_RAM:
259                         for(uint32_t j=memoryMap[i].start; j<=memoryMap[i].end; j++)
260                         {
261                                 funcMapRead[j] = ReadMemory;
262                                 funcMapWrite[j] = WriteMemory;
263                                 addrPtrRead[j] = memoryMap[i].memory;
264                                 addrPtrWrite[j] = memoryMap[i].memory;
265                                 addrOffset[j] = j - memoryMap[i].start;
266 //WriteLog("SetupAddressMap: j=$%04X, addrOffset[j]=$%04X\n", j, addrOffset[j]);
267                         }
268
269                         break;
270                 case AM_ROM:
271                         for(uint32_t j=memoryMap[i].start; j<=memoryMap[i].end; j++)
272                         {
273                                 funcMapRead[j] = ReadMemory;
274                                 addrPtrRead[j] = memoryMap[i].memory;
275                                 addrOffset[j] = j - memoryMap[i].start;
276                         }
277
278                         break;
279                 case AM_BANKED:
280                         for(uint32_t j=memoryMap[i].start; j<=memoryMap[i].end; j++)
281                         {
282                                 funcMapRead[j] = ReadMemory;
283                                 funcMapWrite[j] = WriteMemory;
284                                 addrPtrRead[j] = memoryMap[i].memory;
285                                 addrPtrWrite[j] = memoryMap[i].altMemory;
286                                 addrOffset[j] = j - memoryMap[i].start;
287                         }
288
289                         break;
290                 case AM_READ:
291                         for(uint32_t j=memoryMap[i].start; j<=memoryMap[i].end; j++)
292                                 funcMapRead[j] = memoryMap[i].read;
293
294                         break;
295                 case AM_WRITE:
296                         for(uint32_t j=memoryMap[i].start; j<=memoryMap[i].end; j++)
297                                 funcMapWrite[j] = memoryMap[i].write;
298
299                         break;
300                 case AM_READ_WRITE:
301                         for(uint32_t j=memoryMap[i].start; j<=memoryMap[i].end; j++)
302                         {
303                                 funcMapRead[j] = memoryMap[i].read;
304                                 funcMapWrite[j] = memoryMap[i].write;
305                         }
306
307                         break;
308                 }
309
310                 i++;
311         };
312
313         // This should correctly set up the LC pointers, but it doesn't
314         // for some reason... :-/
315         // It's because we were storing pointers directly, instead of pointers
316         // to the pointer... It's complicated. :-)
317         SwitchLC();
318 }
319
320 //
321 // Reset the MMU state after a power down event
322 //
323 void ResetMMUPointers(void)
324 {
325         if (store80Mode)
326         {
327                 mainMemoryTextR = (displayPage2 ? &ram2[0x0400] : &ram[0x0400]);
328                 mainMemoryTextW = (displayPage2 ? &ram2[0x0400] : &ram[0x0400]);
329                 mainMemoryHGRR = (displayPage2 ? &ram2[0x2000] : &ram[0x2000]);
330                 mainMemoryHGRW = (displayPage2 ? &ram2[0x2000] : &ram[0x2000]);
331         }
332         else
333         {
334 // Shouldn't mainMemoryTextR depend on ramrd???  (I think it should...)
335                 mainMemoryTextR = (ramrd ? &ram2[0x0400] : &ram[0x0400]);
336                 mainMemoryTextW = (ramwrt ? &ram2[0x0400] : &ram[0x0400]);
337                 mainMemoryHGRR = (ramrd ? &ram2[0x2000] : &ram[0x2000]);
338                 mainMemoryHGRW = (ramwrt ? &ram2[0x2000] : &ram[0x2000]);
339         }
340
341         mainMemoryR = (ramrd ? &ram2[0x0200] : &ram[0x0200]);
342         mainMemoryW = (ramwrt ?  &ram2[0x0200] : &ram[0x0200]);
343 //      mainMemoryHGRR = (ramrd ? &ram2[0x2000] : &ram[0x2000]);
344 //      mainMemoryHGRW = (ramwrt ? &ram2[0x2000] : &ram[0x2000]);
345
346 //      slot6Memory = (intCXROM ? &rom[0xC600] : &diskROM[0]);
347 //      slot3Memory = (slotC3ROM ? &rom[0] : &rom[0xC300]);
348         pageZeroMemory = (altzp ? &ram2[0x0000] : &ram[0x0000]);
349         SwitchLC();
350 #if 1
351 WriteLog("RAMWRT = %s\n", (ramwrt ? "ON" : "off"));
352 WriteLog("RAMRD = %s\n", (ramrd ? "ON" : "off"));
353 WriteLog("SLOTCXROM = %s\n", (intCXROM ? "ON" : "off"));
354 WriteLog("SLOTC3ROM = %s\n", (slotC3ROM ? "ON" : "off"));
355 WriteLog("ALTZP = %s\n", (altzp ? "ON" : "off"));
356 #endif
357 }
358
359 //
360 // Set up slot access
361 //
362 void InstallSlotHandler(uint8_t slot, SlotData * slotData)
363 {
364         // Sanity check
365         if (slot > 7)
366         {
367                 WriteLog("InstallSlotHandler: Caller attempted to put device into slot #%u...\n", slot);
368                 return;
369         }
370
371         // Set up I/O read & write functions
372         for(uint32_t i=0; i<16; i++)
373         {
374                 if (slotData->ioR)
375                         funcMapRead[0xC080 + (slot * 16) + i] = slotData->ioR;
376
377                 if (slotData->ioW)
378                         funcMapWrite[0xC080 + (slot * 16) + i] = slotData->ioW;
379         }
380
381         // Set up memory access read/write functions
382         if (slotData->pageR)
383                 slotHandlerR[slot] = slotData->pageR;
384
385         if (slotData->pageW)
386                 slotHandlerW[slot] = slotData->pageW;
387
388         if (slotData->extraR)
389                 slotHandler2KR[slot] = slotData->extraR;
390
391         if (slotData->extraW)
392                 slotHandler2KW[slot] = slotData->extraW;
393 /*
394 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:
395
396 struct Card
397 {
398         void * object;
399         uint16_t type;  // Probably an enum so we can figure out what 'object' is
400         READFUNC(slotIOR);
401         WRITEFUNC(slotIOW);
402         READFUNC(slotPageR);
403         WRITEFUNC(slotPageW);
404         READFUNC(slot2KR);
405         WRITEFUNC(slot2KW);
406 }
407
408 So instead of a bunch of crappy shit that sucks in here, we would have a simple thing like:
409
410 Card * slots[8];
411
412 to encapsulate slots.  This also makes it easier to move them around and makes things less error prone.
413
414 So maybe...
415
416
417 */
418 }
419
420 //
421 // Built-in functions
422 //
423 uint8_t ReadNOP(uint16_t)
424 {
425         // This is for unconnected reads, and some software looks at addresses like
426         // these.  In particular, Mr. Robot and His Robot Factory failed in that it
427         // was looking at the first byte of each slots 256 byte driver space and
428         // failing if it saw a zero there.  Now I have no idea what happens in the
429         // real hardware, but I suspect it would return something that looks like
430         // ReadFloatingBus().
431         return 0xFF;
432 }
433
434 void WriteNOP(uint16_t, uint8_t)
435 {
436 }
437
438 uint8_t ReadMemory(uint16_t address)
439 {
440 //WriteLog("ReadMemory: addr=$%04X, addrPtrRead[addr]=$%X, addrOffset[addr]=$%X, val=$%02X\n", address, addrPtrRead[address], addrOffset[address], addrPtrRead[address][addrOffset[address]]);
441         // We are guaranteed a valid address here by the setup function, so there's
442         // no need to do any checking here.
443         return (*addrPtrRead[address])[addrOffset[address]];
444 }
445
446 void WriteMemory(uint16_t address, uint8_t byte)
447 {
448         // We can write protect memory this way, but it adds a branch to the mix.
449         // :-/ (this can be avoided by setting up another bank of memory which we
450         //  ignore... hmm...)
451         if ((*addrPtrWrite[address]) == 0)
452                 return;
453
454         (*addrPtrWrite[address])[addrOffset[address]] = byte;
455 }
456
457 //
458 // The main memory access functions used by V65C02
459 //
460 uint8_t AppleReadMem(uint16_t address)
461 {
462 #if 0
463 if (address == 0xD4 || address == 0xAC20)
464         WriteLog("Reading $%X...\n", address);
465 #endif
466 #if 0
467         uint8_t memRead = (*(funcMapRead[address]))(address);
468 static uint16_t lastAddr = 0;
469 static uint32_t lastCount = 0;
470 if ((address > 0xC000 && address < 0xC100) || address == 0xC601)
471 {
472         if (lastAddr == address)
473                 lastCount++;
474         else
475         {
476                 if (lastCount > 1)
477                         WriteLog("%d times...\n", lastCount);
478
479                 WriteLog("Reading $%02X from $%X ($%02X, $%02X)\n", memRead, address, diskROM[1], rom[0xC601]);
480                 lastCount = 1;
481                 lastAddr = address;
482         }
483 }
484         return memRead;
485 #else
486         return (*(funcMapRead[address]))(address);
487 #endif
488 }
489
490 void AppleWriteMem(uint16_t address, uint8_t byte)
491 {
492 #if 0
493 static uint16_t lastAddr = 0;
494 static uint32_t lastCount = 0;
495 if ((address > 0xC000 && address < 0xC100) || address == 0xC601)
496 {
497         if (lastAddr == address)
498                 lastCount++;
499         else
500         {
501                 if (lastCount > 1)
502                         WriteLog("%d times...\n", lastCount);
503
504                 WriteLog("Writing to $%X\n", address);
505                 lastCount = 1;
506                 lastAddr = address;
507         }
508 }
509 #endif
510 #if 0
511 if (address == 0xD4 || address == 0xAC20)
512         WriteLog("Writing $%02X @ $%X...\n", byte, address);
513 #endif
514 #if 0
515 //if (address >= 0x0827 && address <= 0x082A)
516 if (address == 0x000D)
517         WriteLog("Writing $%02X @ $%X (PC=$%04X)...\n", byte, address, mainCPU.pc);
518 #endif
519         (*(funcMapWrite[address]))(address, byte);
520 }
521
522 //
523 // Generic slot handlers.  These are set up here so that we can catch INTCXROM,
524 // INTC8ROM & SLOTC3ROM here instead of having to catch them in each slot handler.
525 //
526 uint8_t SlotR(uint16_t address)
527 {
528 //WriteLog("SlotR: address=$%04X, intCXROM=%d, slotC3ROM=%d, intC8ROM=%d\n", address, intCXROM, slotC3ROM, intC8ROM);
529         if (intCXROM)
530                 return rom[address];
531
532         uint8_t slot = (address & 0xF00) >> 8;
533         enabledSlot = slot;
534
535         if ((slotC3ROM == 0) && (slot == 3))
536         {
537                 intC8ROM = 1;
538                 return rom[address];
539         }
540
541         return (*(slotHandlerR[slot]))(address & 0xFF);
542 }
543
544 void SlotW(uint16_t address, uint8_t byte)
545 {
546         if (intCXROM)
547                 return;
548
549         uint8_t slot = (address & 0xF00) >> 8;
550         enabledSlot = slot;
551
552         if ((slotC3ROM == 0) && (slot == 3))
553         {
554                 intC8ROM = 1;
555                 return;
556         }
557
558         (*(slotHandlerW[slot]))(address & 0xFF, byte);
559 }
560
561 //
562 // Slot handling for 2K address space at $C800-$CFFF
563 //
564 uint8_t Slot2KR(uint16_t address)
565 {
566         if (intCXROM || intC8ROM)
567                 return rom[address];
568
569         return (*(slotHandler2KR[enabledSlot]))(address & 0x7FF);
570 }
571
572 void Slot2KW(uint16_t address, uint8_t byte)
573 {
574         if (intCXROM || intC8ROM)
575                 return;
576
577         (*(slotHandler2KW[enabledSlot]))(address & 0x7FF, byte);
578 }
579
580 //
581 // Actual emulated I/O functions follow
582 //
583 uint8_t ReadKeyboard(uint16_t /*addr*/)
584 {
585         return lastKeyPressed | ((uint8_t)keyDown << 7);
586 }
587
588 void Switch80STORE(uint16_t address, uint8_t)
589 {
590         store80Mode = (bool)(address & 0x01);
591 WriteLog("Setting 80STORE to %s...\n", (store80Mode ? "ON" : "off"));
592
593         // It seems this affects more than just the text RAM, it also seems to affect the graphics RAM as well...
594         if (store80Mode)
595         {
596                 mainMemoryTextR = (displayPage2 ? &ram2[0x0400] : &ram[0x0400]);
597                 mainMemoryTextW = (displayPage2 ? &ram2[0x0400] : &ram[0x0400]);
598                 mainMemoryHGRR = (displayPage2 ? &ram2[0x2000] : &ram[0x2000]);
599                 mainMemoryHGRW = (displayPage2 ? &ram2[0x2000] : &ram[0x2000]);
600         }
601         else
602         {
603                 mainMemoryTextR = (ramrd ? &ram2[0x0400] : &ram[0x0400]);
604                 mainMemoryTextW = (ramwrt ? &ram2[0x0400] : &ram[0x0400]);
605                 mainMemoryHGRR = (ramrd ? &ram2[0x2000] : &ram[0x2000]);
606                 mainMemoryHGRW = (ramwrt ? &ram2[0x2000] : &ram[0x2000]);
607         }
608 }
609
610 void SwitchRAMRD(uint16_t address, uint8_t)
611 {
612         ramrd = (bool)(address & 0x01);
613         mainMemoryR = (ramrd ? &ram2[0x0200] : &ram[0x0200]);
614 //      mainMemoryHGRR = (ramrd ? &ram2[0x2000] : &ram[0x2000]);
615
616         if (store80Mode)
617                 return;
618
619         mainMemoryTextR = (ramrd ? &ram2[0x0400] : &ram[0x0400]);
620         mainMemoryHGRR = (ramrd ? &ram2[0x2000] : &ram[0x2000]);
621 }
622
623 void SwitchRAMWRT(uint16_t address, uint8_t)
624 {
625         ramwrt = (bool)(address & 0x01);
626         mainMemoryW = (ramwrt ?  &ram2[0x0200] : &ram[0x0200]);
627 //      mainMemoryHGRW = (ramwrt ? &ram2[0x2000] : &ram[0x2000]);
628
629         if (store80Mode)
630                 return;
631
632         mainMemoryTextW = (ramwrt ? &ram2[0x0400] : &ram[0x0400]);
633         mainMemoryHGRW = (ramwrt ? &ram2[0x2000] : &ram[0x2000]);
634 }
635
636 //
637 // Since any slots that aren't populated are set to read from the ROM anyway,
638 // we only concern ourselves with switching populated slots here.  (Note that
639 // the MB slot is a split ROM / I/O device, and it's taken care of in the
640 // MB handler.)
641 //
642 // N.B.: SLOTCXROM is also INTCXROM
643 //
644 void SwitchSLOTCXROM(uint16_t address, uint8_t)
645 {
646 WriteLog("Setting SLOTCXROM to %s...\n", (address & 0x01 ? "ON" : "off"));
647         intCXROM = (bool)(address & 0x01);
648
649         // INTC8ROM trumps all (only in the $C800--$CFFF range... which we don't account for yet...  :-/)
650 //      if (intC8ROM)
651 //              return;
652 #if 0
653 #if 1
654         if (intCXROM)
655         {
656                 slot3Memory = &rom[0xC300];
657                 slot6Memory = &rom[0xC600];
658         }
659         else
660         {
661                 slot3Memory = (slotC3ROM ? &rom[0] : &rom[0xC300]);
662                 slot6Memory = &diskROM[0];
663         }
664 #else
665 //      slot3Memory = (intCXROM ? &rom[0xC300] : &rom[0]);
666         slot6Memory = (intCXROM ? &rom[0xC600] : &diskROM[0]);
667 #endif
668 #endif
669 }
670
671 void SwitchALTZP(uint16_t address, uint8_t)
672 {
673         altzp = (bool)(address & 0x01);
674         pageZeroMemory = (altzp ? &ram2[0x0000] : &ram[0x0000]);
675         SwitchLC();
676 }
677
678 //extern bool dumpDis;
679 //
680 // The interpretation of this name is that if it's set then we access the ROM
681 // for the card actually sitting in SLOT 3 (if any)
682 //
683 void SwitchSLOTC3ROM(uint16_t address, uint8_t)
684 {
685 //dumpDis = true;
686 //WriteLog("Setting SLOTC3ROM to %s...\n", (address & 0x01 ? "ON" : "off"));
687         slotC3ROM = (bool)(address & 0x01);
688 #if 1
689         if (intCXROM)
690                 slot3Memory = &rom[0xC300];
691         else
692                 slot3Memory = (slotC3ROM ? &rom[0] : &rom[0xC300]);
693 #else
694 //      slotC3ROM = false;
695 // Seems the h/w forces this with an 80 column card in slot 3...
696         slot3Memory = (slotC3ROM ? &rom[0] : &rom[0xC300]);
697 //      slot3Memory = &rom[0xC300];
698 #endif
699 }
700
701 /*
702 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.
703 */
704 //
705 // This is a problem with split ROM / I/O regions.  Because we can't do that
706 // cleanly, we have to have a read handler for this.
707 //
708 // N.B.: We could add AM_IOREAD_WRITE and AM_READ_IOWRITE to the memory handlers
709 //       to take care of split ROM / I/O regions...
710 //
711 uint8_t SwitchINTC8ROMR(uint16_t)
712 {
713 WriteLog("Hitting INTC8ROM (read)...\n");
714         intC8ROM = false;
715         return rom[0xCFFF];
716 }
717
718 //
719 // This resets the INTC8ROM switch (RW)
720 //
721 void SwitchINTC8ROMW(uint16_t, uint8_t)
722 {
723 WriteLog("Hitting INTC8ROM (write)...\n");
724         intC8ROM = false;
725 }
726
727 void Switch80COL(uint16_t address, uint8_t)
728 {
729         col80Mode = (bool)(address & 0x01);
730 }
731
732 void SwitchALTCHARSET(uint16_t address, uint8_t)
733 {
734         alternateCharset = (bool)(address & 0x01);
735 WriteLog("Setting ALTCHARSET to %s...\n", (alternateCharset ? "ON" : "off"));
736 }
737
738 uint8_t ReadKeyStrobe(uint16_t)
739 {
740         // No character data is read from here, just the 'any key was pressed'
741         // signal...
742         uint8_t byte = (uint8_t)keyDown << 7;
743         keyDown = false;
744         return byte;
745 }
746
747 uint8_t ReadBANK2(uint16_t)
748 {
749         return (lcState < 0x04 ? 0x80 : 0x00);
750 }
751
752 uint8_t ReadLCRAM(uint16_t)
753 {
754         // If bits 0 & 1 are set, but not at the same time, then it's ROM
755         uint8_t lcROM = (lcState & 0x1) ^ ((lcState & 0x02) >> 1);
756         return (lcROM ? 0x00 : 0x80);
757 }
758
759 uint8_t ReadRAMRD(uint16_t)
760 {
761         return (uint8_t)ramrd << 7;
762 }
763
764 uint8_t ReadRAMWRT(uint16_t)
765 {
766         return (uint8_t)ramwrt << 7;
767 }
768
769 uint8_t ReadSLOTCXROM(uint16_t)
770 {
771         return (uint8_t)intCXROM << 7;
772 }
773
774 uint8_t ReadALTZP(uint16_t)
775 {
776         return (uint8_t)altzp << 7;
777 }
778
779 uint8_t ReadSLOTC3ROM(uint16_t)
780 {
781         return (uint8_t)slotC3ROM << 7;
782 }
783
784 uint8_t Read80STORE(uint16_t)
785 {
786         return (uint8_t)store80Mode << 7;
787 }
788
789 uint8_t ReadVBL(uint16_t)
790 {
791         return (uint8_t)vbl << 7;
792 }
793
794 uint8_t ReadTEXT(uint16_t)
795 {
796         return (uint8_t)textMode << 7;
797 }
798
799 uint8_t ReadMIXED(uint16_t)
800 {
801         return (uint8_t)mixedMode << 7;
802 }
803
804 uint8_t ReadPAGE2(uint16_t)
805 {
806         return (uint8_t)displayPage2 << 7;
807 }
808
809 uint8_t ReadHIRES(uint16_t)
810 {
811         return (uint8_t)hiRes << 7;
812 }
813
814 uint8_t ReadALTCHARSET(uint16_t)
815 {
816         return (uint8_t)alternateCharset << 7;
817 }
818
819 uint8_t Read80COL(uint16_t)
820 {
821         return (uint8_t)col80Mode << 7;
822 }
823
824 void WriteKeyStrobe(uint16_t, uint8_t)
825 {
826         keyDown = false;
827 }
828
829 uint8_t ReadSpeaker(uint16_t)
830 {
831         ToggleSpeaker();
832 //      return 0;
833         // Seems this is needed for some things...
834         return ReadFloatingBus(0);
835 }
836
837 void WriteSpeaker(uint16_t, uint8_t)
838 {
839         ToggleSpeaker();
840 }
841
842 uint8_t SwitchLCR(uint16_t address)
843 {
844         lcState = address & 0x0B;
845         SwitchLC();
846 //      return 0;
847         // Seems this is needed for some things...
848         return ReadFloatingBus(0);
849 }
850
851 void SwitchLCW(uint16_t address, uint8_t)
852 {
853         lcState = address & 0x0B;
854         SwitchLC();
855 }
856
857 void SwitchLC(void)
858 {
859         switch (lcState)
860         {
861         case 0x00:
862 #ifdef LC_DEBUG
863 WriteLog("SwitchLC: Read RAM bank 2, no write\n");
864 #endif
865                 // [R ] Read RAM bank 2; no write
866                 lcBankMemoryR = (altzp ? &ram2[0xD000] : &ram[0xD000]);
867                 lcBankMemoryW = 0;
868                 upperMemoryR = (altzp ? &ram2[0xE000] : &ram[0xE000]);
869                 upperMemoryW = 0;
870                 break;
871         case 0x01:
872 #ifdef LC_DEBUG
873 WriteLog("SwitchLC: Read ROM, write bank 2\n");
874 #endif
875                 // [RR] Read ROM; write RAM bank 2
876                 lcBankMemoryR = &rom[0xD000];
877                 lcBankMemoryW = (altzp ? &ram2[0xD000] : &ram[0xD000]);
878                 upperMemoryR = &rom[0xE000];
879                 upperMemoryW = (altzp ? &ram2[0xE000] : &ram[0xE000]);
880                 break;
881         case 0x02:
882 #ifdef LC_DEBUG
883 WriteLog("SwitchLC: Read ROM, no write\n");
884 #endif
885                 // [R ] Read ROM; no write
886                 lcBankMemoryR = &rom[0xD000];
887                 lcBankMemoryW = 0;
888                 upperMemoryR = &rom[0xE000];
889                 upperMemoryW = 0;
890                 break;
891         case 0x03:
892 #ifdef LC_DEBUG
893 WriteLog("SwitchLC: Read/write bank 2\n");
894 #endif
895                 // [RR] Read RAM bank 2; write RAM bank 2
896                 lcBankMemoryR = (altzp ? &ram2[0xD000] : &ram[0xD000]);
897                 lcBankMemoryW = (altzp ? &ram2[0xD000] : &ram[0xD000]);
898                 upperMemoryR = (altzp ? &ram2[0xE000] : &ram[0xE000]);
899                 upperMemoryW = (altzp ? &ram2[0xE000] : &ram[0xE000]);
900                 break;
901         case 0x08:
902                 // [R ] Read RAM bank 1; no write
903                 lcBankMemoryR = (altzp ? &ram2[0xC000] : &ram[0xC000]);
904                 lcBankMemoryW = 0;
905                 upperMemoryR = (altzp ? &ram2[0xE000] : &ram[0xE000]);
906                 upperMemoryW = 0;
907                 break;
908         case 0x09:
909                 // [RR] Read ROM; write RAM bank 1
910                 lcBankMemoryR = &rom[0xD000];
911                 lcBankMemoryW = (altzp ? &ram2[0xC000] : &ram[0xC000]);
912                 upperMemoryR = &rom[0xE000];
913                 upperMemoryW = (altzp ? &ram2[0xE000] : &ram[0xE000]);
914                 break;
915         case 0x0A:
916                 // [R ] Read ROM; no write
917                 lcBankMemoryR = &rom[0xD000];
918                 lcBankMemoryW = 0;
919                 upperMemoryR = &rom[0xE000];
920                 upperMemoryW = 0;
921                 break;
922         case 0x0B:
923                 // [RR] Read RAM bank 1; write RAM bank 1
924                 lcBankMemoryR = (altzp ? &ram2[0xC000] : &ram[0xC000]);
925                 lcBankMemoryW = (altzp ? &ram2[0xC000] : &ram[0xC000]);
926                 upperMemoryR = (altzp ? &ram2[0xE000] : &ram[0xE000]);
927                 upperMemoryW = (altzp ? &ram2[0xE000] : &ram[0xE000]);
928                 break;
929         }
930 }
931
932 uint8_t SwitchTEXTR(uint16_t address)
933 {
934 WriteLog("Setting TEXT to %s...\n", (address & 0x01 ? "ON" : "off"));
935         textMode = (bool)(address & 0x01);
936 //      return 0;
937         // Seems this is needed for some things...
938         return ReadFloatingBus(0);
939 }
940
941 void SwitchTEXTW(uint16_t address, uint8_t)
942 {
943 WriteLog("Setting TEXT to %s...\n", (address & 0x01 ? "ON" : "off"));
944         textMode = (bool)(address & 0x01);
945 }
946
947 uint8_t SwitchMIXEDR(uint16_t address)
948 {
949 WriteLog("Setting MIXED to %s...\n", (address & 0x01 ? "ON" : "off"));
950         mixedMode = (bool)(address & 0x01);
951 //      return 0;
952         // Seems this is needed for some things...
953         return ReadFloatingBus(0);
954 }
955
956 void SwitchMIXEDW(uint16_t address, uint8_t)
957 {
958 WriteLog("Setting MIXED to %s...\n", (address & 0x01 ? "ON" : "off"));
959         mixedMode = (bool)(address & 0x01);
960 }
961
962 /*
963 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.
964 */
965 uint8_t SwitchPAGE2R(uint16_t address)
966 {
967 WriteLog("Setting PAGE2 to %s...\n", (address & 0x01 ? "ON" : "off"));
968         displayPage2 = (bool)(address & 0x01);
969
970         if (store80Mode)
971         {
972                 mainMemoryTextR = (displayPage2 ? &ram2[0x0400] : &ram[0x0400]);
973                 mainMemoryTextW = (displayPage2 ? &ram2[0x0400] : &ram[0x0400]);
974
975                 if (hiRes)
976                 {
977                         mainMemoryHGRR = (displayPage2 ? &ram2[0x2000] : &ram[0x2000]);
978                         mainMemoryHGRW = (displayPage2 ? &ram2[0x2000] : &ram[0x2000]);
979                 }
980         }
981
982         // Seems this is needed for some things...
983         return ReadFloatingBus(0);
984 }
985
986 void SwitchPAGE2W(uint16_t address, uint8_t)
987 {
988 WriteLog("Setting PAGE2 to %s...\n", (address & 0x01 ? "ON" : "off"));
989         displayPage2 = (bool)(address & 0x01);
990
991         if (store80Mode)
992         {
993                 mainMemoryTextR = (displayPage2 ? &ram2[0x0400] : &ram[0x0400]);
994                 mainMemoryTextW = (displayPage2 ? &ram2[0x0400] : &ram[0x0400]);
995
996                 if (hiRes)
997                 {
998                         mainMemoryHGRR = (displayPage2 ? &ram2[0x2000] : &ram[0x2000]);
999                         mainMemoryHGRW = (displayPage2 ? &ram2[0x2000] : &ram[0x2000]);
1000                 }
1001         }
1002 }
1003
1004 uint8_t SwitchHIRESR(uint16_t address)
1005 {
1006 WriteLog("Setting HIRES to %s...\n", (address & 0x01 ? "ON" : "off"));
1007         hiRes = (bool)(address & 0x01);
1008 //      return 0;
1009         // Seems this is needed for some things...
1010         return ReadFloatingBus(0);
1011 }
1012
1013 void SwitchHIRESW(uint16_t address, uint8_t)
1014 {
1015 WriteLog("Setting HIRES to %s...\n", (address & 0x01 ? "ON" : "off"));
1016         hiRes = (bool)(address & 0x01);
1017 }
1018
1019 uint8_t SwitchDHIRESR(uint16_t address)
1020 {
1021 WriteLog("Setting DHIRES to %s (ioudis = %s)...\n", ((address & 0x01) ^ 0x01 ? "ON" : "off"), (ioudis ? "ON" : "off"));
1022         // Hmm, this breaks convention too, like SLOTCXROM
1023         if (ioudis)
1024                 dhires = !((bool)(address & 0x01));
1025
1026 //      return 0;
1027         // Seems this is needed for some things...
1028         return ReadFloatingBus(0);
1029 }
1030
1031 void SwitchDHIRESW(uint16_t address, uint8_t)
1032 {
1033 WriteLog("Setting DHIRES to %s (ioudis = %s)...\n", ((address & 0x01) ^ 0x01 ? "ON" : "off"), (ioudis ? "ON" : "off"));
1034         if (ioudis)
1035                 dhires = !((bool)(address & 0x01));
1036 }
1037
1038 void SwitchIOUDIS(uint16_t address, uint8_t)
1039 {
1040         ioudis = !((bool)(address & 0x01));
1041 }
1042
1043 uint8_t ReadCassetteIn(uint16_t)
1044 {
1045         // 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...)
1046         // Serpentine crashes even earlier if $00 is returned...  Now what???
1047         // This seems to work for Serpentine, not sure what's supposed to be returned here...  Maybe it's an RNG when N/C?
1048         return 0x5A;
1049 }
1050
1051 uint8_t ReadButton0(uint16_t)
1052 {
1053         return (uint8_t)openAppleDown << 7;
1054 }
1055
1056 uint8_t ReadButton1(uint16_t)
1057 {
1058         return (uint8_t)closedAppleDown << 7;
1059 }
1060
1061 // The way the paddles work is that a strobe is written (or read) to $C070,
1062 // then software counts down the time that it takes for the paddle outputs
1063 // to have bit 7 return to 0. If there are no paddles connected, bit 7
1064 // stays at 1.
1065 // NB: This is really paddles 0-3, not just 0 :-P
1066 uint8_t ReadPaddle0(uint16_t)
1067 {
1068         return 0xFF;
1069 }
1070
1071 uint8_t ReadIOUDIS(uint16_t)
1072 {
1073         return (uint8_t)ioudis << 7;
1074 }
1075
1076 uint8_t ReadDHIRES(uint16_t)
1077 {
1078         return (uint8_t)dhires << 7;
1079 }
1080
1081 // Whenever a read is done to a MMIO location that is unconnected to anything,
1082 // it actually sees the RAM access done by the video generation hardware. Some
1083 // programs exploit this, so we emulate it here.
1084
1085 // N.B.: frameCycles will be off by the true amount because this only
1086 //       increments by the amount of a speaker cycle, not the cycle count when
1087 //       the access happens... !!! FIX !!!
1088 uint8_t ReadFloatingBus(uint16_t)
1089 {
1090         // Get the currently elapsed cycle count for this frame
1091         uint32_t frameCycles = mainCPU.clock - frameCycleStart;
1092
1093         // Make counters out of the cycle count. There are 65 cycles per line.
1094         uint32_t numLines = frameCycles / 65;
1095         uint32_t numHTicks = frameCycles - (numLines * 65);
1096
1097         // Convert these to H/V counters
1098         uint32_t hcount = numHTicks - 1;
1099
1100         // HC sees zero twice:
1101         if (hcount == 0xFFFFFFFF)
1102                 hcount = 0;
1103
1104         uint32_t vcount = numLines + 0xFA;
1105
1106         // Now do the address calculations
1107         uint32_t sum = 0xD + ((hcount & 0x38) >> 3)
1108                 + (((vcount & 0xC0) >> 6) | ((vcount & 0xC0) >> 4));
1109         uint32_t address = ((vcount & 0x38) << 4) | ((sum & 0x0F) << 3) | (hcount & 0x07);
1110
1111         // Add in particulars for the gfx mode we're in...
1112         if (textMode || (!textMode && !hiRes))
1113                 address |= (!(!store80Mode && displayPage2) ? 0x400 : 0)
1114                         | (!store80Mode && displayPage2 ? 0x800 : 0);
1115         else
1116                 address |= (!(!store80Mode && displayPage2) ? 0x2000: 0)
1117                         | (!store80Mode && displayPage2 ? 0x4000 : 0)
1118                         | ((vcount & 0x07) << 10);
1119
1120         // The address so read is *always* in main RAM, never alt RAM
1121         return ram[address];
1122 }