2 // Apple 2 SDL Portable Apple Emulator
5 // (C) 2005 Underground Software
7 // Loosely based on AppleWin by Tom Charlesworth which was based on AppleWin by
8 // Oliver Schmidt which was based on AppleWin by Michael O'Brien. :-) Parts are
9 // also derived from ApplePC. Too bad it was closed source--it could have been
10 // *the* premier Apple II emulator out there.
12 // JLH = James L. Hammons <jlhamm@acm.org>
15 // --- ---------- ------------------------------------------------------------
16 // JLH 11/12/2005 Initial port to SDL
17 // JLH 11/18/2005 Wired up graphic soft switches
18 // JLH 12/02/2005 Setup timer subsystem for more accurate time keeping
19 // JLH 12/12/2005 Added preliminary state saving support
24 // - Port to SDL [DONE]
26 // - Weed out unneeded functions [DONE]
28 // - 128K IIe related stuff
29 // - State loading/saving
47 #include "applevideo.h"
53 #include "gui/window.h"
54 #include "gui/draggablewindow2.h"
55 #include "gui/textedit.h"
57 // Debug and misc. defines
59 #define THREADED_65C02
60 #define CPU_THREAD_OVERFLOW_COMPENSATION
62 //#define CPU_CLOCK_CHECKING
63 //#define THREAD_DEBUGGING
64 #define SOFT_SWITCH_DEBUGGING
68 uint8_t ram[0x10000], rom[0x10000]; // RAM & ROM spaces
69 uint8_t ram2[0x10000]; // Auxillary RAM
70 uint8_t diskRom[0x100]; // Disk ROM space
71 V65C02REGS mainCPU; // v65C02 execution context
72 uint8_t appleType = APPLE_TYPE_IIE;
73 FloppyDrive floppyDrive;
77 static uint8_t lastKeyPressed = 0;
78 static bool keyDown = false;
79 static bool openAppleDown = false;
80 static bool closedAppleDown = false;
81 static bool store80Mode = false;
82 static bool vbl = false;
83 static bool slotCXROM = false;
84 static bool slotC3ROM = false;
85 static bool ramrd = false;
86 static bool ramwrt = false;
87 static bool altzp = false;
88 static bool ioudis = true;
91 //static FloppyDrive floppyDrive;
93 enum { LC_BANK_1, LC_BANK_2 };
95 static uint8_t visibleBank = LC_BANK_1;
96 static bool readRAM = false;
97 static bool writeRAM = false;
99 static bool running = true; // Machine running state flag...
100 static uint32_t startTicks;
101 static bool pauseMode = false;
103 static GUI * gui = NULL;
105 // Local functions (technically, they're global...)
107 bool LoadImg(char * filename, uint8_t * ram, int size);
108 uint8_t RdMem(uint16_t addr);
109 void WrMem(uint16_t addr, uint8_t b);
110 static void SaveApple2State(const char * filename);
111 static bool LoadApple2State(const char * filename);
113 // Local timer callback functions
115 static void FrameCallback(void);
116 static void BlinkTimer(void);
118 #ifdef THREADED_65C02
119 // Test of threaded execution of 6502
120 static SDL_Thread * cpuThread = NULL;
121 //static SDL_mutex * cpuMutex = NULL;
122 static SDL_cond * cpuCond = NULL;
123 static SDL_sem * mainSem = NULL;
124 static bool cpuFinished = false;
125 static bool cpuSleep = false;
128 // NB: Apple //e Manual sez 6502 is running @ 1,022,727 Hz
130 // Let's try a thread...
132 Here's how it works: Execute 1 frame's worth, then sleep.
133 Other stuff wakes it up
135 int CPUThreadFunc(void * data)
137 // Mutex must be locked for conditional to work...
138 // Also, must be created in the thread that uses it...
139 SDL_mutex * cpuMutex = SDL_CreateMutex();
141 // decrement mainSem...
142 //SDL_SemWait(mainSem);
143 #ifdef CPU_THREAD_OVERFLOW_COMPENSATION
144 float overflow = 0.0;
150 SDL_CondWait(cpuCond, cpuMutex);
152 // decrement mainSem...
153 #ifdef THREAD_DEBUGGING
154 WriteLog("CPU: SDL_SemWait(mainSem);\n");
156 SDL_SemWait(mainSem);
158 // There are exactly 800 slices of 21.333 cycles per frame, so it works out
161 uint32_t cycles = 17066;
162 #ifdef CPU_THREAD_OVERFLOW_COMPENSATION
163 // ODD! It's closer *without* this overflow compensation. ??? WHY ???
164 overflow += 0.666666667;
173 #ifdef THREAD_DEBUGGING
174 WriteLog("CPU: Execute65C02(&mainCPU, cycles);\n");
176 Execute65C02(&mainCPU, cycles); // how much? 1 frame (after 1 s, off by 40 cycles) not any more--it's off by as much as 240 now!
178 // Adjust the sound routine's last cycle toggled time base
179 // Also, since we're finished executing, .clock is now valid
180 #ifdef THREAD_DEBUGGING
181 WriteLog("CPU: AdjustLastToggleCycles(mainCPU.clock);\n");
183 AdjustLastToggleCycles(mainCPU.clock);
185 #ifdef THREAD_DEBUGGING
186 WriteLog("CPU: Execute65C02(&mainCPU, cycles);\n");
188 for(int i=0; i<800; i++)
190 uint32_t cycles = 21;
191 overflow += 0.333333334;
199 Execute65C02(&mainCPU, cycles);
200 WriteSampleToBuffer();
204 //WriteLog("CPUThread: Supposedly end of frame...\n");
206 #ifdef THREAD_DEBUGGING
207 WriteLog("CPU: SDL_mutexP(cpuMutex);\n");
209 SDL_mutexP(cpuMutex);
211 if (SDL_CondWait(cpuCond, cpuMutex) != 0)
213 printf("SDL_CondWait != 0! (Error: '%s')\n", SDL_GetError());
217 // increment mainSem...
218 #ifdef THREAD_DEBUGGING
219 WriteLog("CPU: SDL_SemPost(mainSem);\n");
221 SDL_SemPost(mainSem);
222 // SDL_CondSignal(mainCond); // In case something is waiting on us...
224 #ifdef THREAD_DEBUGGING
225 WriteLog("CPU: SDL_CondWait(cpuCond, cpuMutex);\n");
227 SDL_CondWait(cpuCond, cpuMutex);
230 #ifdef THREAD_DEBUGGING
231 WriteLog("CPU: SDL_mutexV(cpuMutex);\n");
233 SDL_mutexV(cpuMutex);
235 while (!cpuFinished);
237 SDL_DestroyMutex(cpuMutex);
246 Element * TestWindow(void)
248 Element * win = new DraggableWindow2(10, 10, 128, 128);
249 // ((DraggableWindow *)win)->AddElement(new TextEdit(4, 16, 92, 0, "u2prog.dsk", win));
255 Element * QuitEmulator(void)
265 Small Apple II memory map:
267 $C010 - Clear bit 7 of keyboard data ($C000)
268 $C030 - Toggle speaker diaphragm
270 $C054 - Select page 1
271 $C056 - Select lo-res
272 $C058 - Set annuciator-0 output to 0
273 $C05A - Set annuciator-0 output to 0
274 $C05D - Set annuciator-0 output to 1
275 $C05F - Set annuciator-0 output to 1
276 $C0E0 - Disk control stepper ($C0E0-7)
277 $C0E9 - Disk control motor (on)
278 $C0EA - Disk enable (drive 1)
280 $C0EE - Disk set read mode
284 // V65C02 read/write memory functions
287 uint8_t RdMem(uint16_t addr)
292 if (addr >= 0xC000 && addr <= 0xC0FF)
293 WriteLog("\n*** Read at I/O address %04X\n", addr);
296 if (addr >= 0xC080 && addr <= 0xC08F)
297 WriteLog("\n*** Read at I/O address %04X\n", addr);
300 if ((addr & 0xFFF0) == 0xC000) // Read $C000-$C00F
302 return lastKeyPressed | (keyDown ? 0x80 : 0x00);
304 // else if ((addr & 0xFFF8) == 0xC010) // Read $C010-$C01F
305 else if (addr == 0xC010)
307 //This is bogus: keyDown is set to false, so return val NEVER is set...
309 //Also, this is IIe/IIc only...!
310 uint8_t retVal = lastKeyPressed | (keyDown ? 0x80 : 0x00);
314 // These are //e locations
315 else if (addr == 0xC011)
317 #ifdef SOFT_SWITCH_DEBUGGING
318 WriteLog("RDBANK2 (read)\n");
320 return (visibleBank == LC_BANK_2 ? 0x80 : 0x00);
322 else if (addr == 0xC012)
324 #ifdef SOFT_SWITCH_DEBUGGING
325 WriteLog("RDLCRAM (read)\n");
327 return (readRAM ? 0x80 : 0x00);
329 else if (addr == 0xC013)
331 #ifdef SOFT_SWITCH_DEBUGGING
332 WriteLog("RAMRD (read)\n");
334 return (ramrd ? 0x80 : 0x00);
336 else if (addr == 0xC014)
338 #ifdef SOFT_SWITCH_DEBUGGING
339 WriteLog("RAMWRT (read)\n");
341 return (ramwrt ? 0x80 : 0x00);
343 else if (addr == 0xC015)
345 #ifdef SOFT_SWITCH_DEBUGGING
346 WriteLog("SLOTCXROM (read)\n");
348 return (slotCXROM ? 0x80 : 0x00);
350 else if (addr == 0xC016)
352 #ifdef SOFT_SWITCH_DEBUGGING
353 WriteLog("ALTZP (read)\n");
355 return (altzp ? 0x80 : 0x00);
357 else if (addr == 0xC017)
359 #ifdef SOFT_SWITCH_DEBUGGING
360 WriteLog("SLOTC3ROM (read)\n");
362 return (slotC3ROM ? 0x80 : 0x00);
364 else if (addr == 0xC018)
366 #ifdef SOFT_SWITCH_DEBUGGING
367 WriteLog("80STORE (read)\n");
369 return (store80Mode ? 0x80 : 0x00);
371 else if (addr == 0xC019)
373 #ifdef SOFT_SWITCH_DEBUGGING
374 WriteLog("VBL (read)\n");
376 // NB: The doco suggests that this signal goes LOW when in the VBI.
377 // Which means that we need to control this by counting lines somewhere.
378 return (vbl ? 0x80 : 0x00);
380 else if (addr == 0xC01A)
382 #ifdef SOFT_SWITCH_DEBUGGING
383 WriteLog("TEXT (read)\n");
385 return (textMode ? 0x80 : 0x00);
387 else if (addr == 0xC01B)
389 #ifdef SOFT_SWITCH_DEBUGGING
390 WriteLog("MIXED (read)\n");
392 return (mixedMode ? 0x80 : 0x00);
394 else if (addr == 0xC01C)
396 #ifdef SOFT_SWITCH_DEBUGGING
397 WriteLog("PAGE2 (read)\n");
399 return (displayPage2 ? 0x80 : 0x00);
401 else if (addr == 0xC01D)
403 #ifdef SOFT_SWITCH_DEBUGGING
404 WriteLog("HIRES (read)\n");
406 return (hiRes ? 0x80 : 0x00);
408 else if (addr == 0xC01E)
410 #ifdef SOFT_SWITCH_DEBUGGING
411 WriteLog("ALTCHARSET (read)\n");
413 return (alternateCharset ? 0x80 : 0x00);
415 else if (addr == 0xC01F)
417 #ifdef SOFT_SWITCH_DEBUGGING
418 WriteLog("80COL (read)\n");
420 return (col80Mode ? 0x80 : 0x00);
422 else if ((addr & 0xFFF0) == 0xC030) // Read $C030-$C03F
425 This is problematic, mainly because the v65C02 removes actual cycles run after each call.
426 Therefore, we don't really have a reliable method of sending a timestamp to the sound routine.
429 What we need to send is a delta T value but related to the IRQ buffer routine. E.g., if the buffer
430 hasn't had any changes in it then we just fill it with the last sample value and are done. Then
431 we need to adjust our delta T accordingly. What we could do is keep a running total of time since the
432 last change and adjust it accordingly, i.e., whenever a sound IRQ happens.
435 Have deltaT somewhere. Then, whenever there's a toggle, backfill buffer with last spkr state and reset
436 deltaT to zero. In the sound IRQ, if deltaT > buffer size, then subtract buffer size from deltaT. (?)
441 ToggleSpeaker(GetCurrentV65C02Clock());
442 //should it return something else here???
445 else if (addr == 0xC050) // Read $C050
447 #ifdef SOFT_SWITCH_DEBUGGING
448 WriteLog("TEXT off (read)\n");
452 else if (addr == 0xC051) // Read $C051
454 #ifdef SOFT_SWITCH_DEBUGGING
455 WriteLog("TEXT on (read)\n");
459 else if (addr == 0xC052) // Read $C052
461 #ifdef SOFT_SWITCH_DEBUGGING
462 WriteLog("MIXED off (read)\n");
466 else if (addr == 0xC053) // Read $C053
468 #ifdef SOFT_SWITCH_DEBUGGING
469 WriteLog("MIXED on (read)\n");
473 else if (addr == 0xC054) // Read $C054
475 #ifdef SOFT_SWITCH_DEBUGGING
476 WriteLog("PAGE2 off (read)\n");
478 displayPage2 = false;
480 else if (addr == 0xC055) // Read $C055
482 #ifdef SOFT_SWITCH_DEBUGGING
483 WriteLog("PAGE2 on (read)\n");
487 else if (addr == 0xC056) // Read $C056
489 #ifdef SOFT_SWITCH_DEBUGGING
490 WriteLog("HIRES off (read)\n");
494 else if (addr == 0xC057) // Read $C057
496 #ifdef SOFT_SWITCH_DEBUGGING
497 WriteLog("HIRES on (read)\n");
501 else if (addr == 0xC05E)
503 #ifdef SOFT_SWITCH_DEBUGGING
504 WriteLog("DHIRES on (read)\n");
509 else if (addr == 0xC05F)
511 #ifdef SOFT_SWITCH_DEBUGGING
512 WriteLog("DHIRES off (read)\n");
517 else if (addr == 0xC061) // Read $C061
519 // Open Apple key (or push button 0)
520 return (openAppleDown ? 0x80 : 0x00);
522 else if (addr == 0xC062) // Read $C062
524 // Open Apple key (or push button 0)
525 return (closedAppleDown ? 0x80 : 0x00);
527 // The way the paddles work is that a strobe is written (or read) to $C070,
528 // then software counts down the time that it takes for the paddle outputs
529 // to have bit 7 return to 0. If there are no paddles connected, bit 7
531 else if (addr == 0xC064) // Paddles 0-3
535 else if (addr == 0xC065)
539 else if (addr == 0xC066)
543 else if (addr == 0xC067)
547 else if (addr == 0xC07E)
549 #ifdef SOFT_SWITCH_DEBUGGING
550 WriteLog("IOUDIS (read)\n");
552 return (ioudis ? 0x80 : 0x00);
554 else if (addr == 0xC07F)
556 #ifdef SOFT_SWITCH_DEBUGGING
557 WriteLog("DHIRES (read)\n");
559 return (dhires ? 0x80 : 0x00);
562 //Note that this is a kludge: The $D000-$DFFF 4K space is shared (since $C000-$CFFF is
563 //memory mapped) between TWO banks, and that that $E000-$FFFF RAM space is a single bank.
564 //[SHOULD BE FIXED NOW]
565 //OK! This switch selects bank 2 of the 4K bank at $D000-$DFFF. One access makes it
566 //visible, two makes it R/W.
601 else if ((addr & 0xFFFB) == 0xC080)
604 WriteLog("LC(R): $C080 49280 OECG R Read RAM bank 2; no write\n");
606 //$C080 49280 OECG R Read RAM bank 2; no write
607 visibleBank = LC_BANK_2;
611 else if ((addr & 0xFFFB) == 0xC081)
614 WriteLog("LC(R): $C081 49281 OECG RR Read ROM; write RAM bank 2\n");
616 //$C081 49281 ROMIN OECG RR Read ROM; write RAM bank 2
617 visibleBank = LC_BANK_2;
621 else if ((addr & 0xFFFB) == 0xC082)
624 WriteLog("LC(R): $C082 49282 OECG R Read ROM; no write\n");
626 //$C082 49282 OECG R Read ROM; no write
627 visibleBank = LC_BANK_2;
631 else if ((addr & 0xFFFB) == 0xC083)
634 WriteLog("LC(R): $C083 49283 OECG RR Read/Write RAM bank 2\n");
636 //$C083 49283 LCBANK2 OECG RR Read/write RAM bank 2
637 visibleBank = LC_BANK_2;
641 else if ((addr & 0xFFFB) == 0xC088)
644 WriteLog("LC(R): $%04X 49288 OECG R Read RAM bank 1; no write\n", addr);
646 //$C088 49288 OECG R Read RAM bank 1; no write
647 visibleBank = LC_BANK_1;
650 //Hm. Some stuff seems to want this.
651 //nope, was looking at $C0E8... return 0xFF;
653 else if ((addr & 0xFFFB) == 0xC089)
656 WriteLog("LC(R): $C089 49289 OECG RR Read ROM; write RAM bank 1\n");
658 //$C089 49289 OECG RR Read ROM; write RAM bank 1
659 visibleBank = LC_BANK_1;
663 else if ((addr & 0xFFFB) == 0xC08A)
666 WriteLog("LC(R): $C08A 49290 OECG R Read ROM; no write\n");
668 //$C08A 49290 OECG R Read ROM; no write
669 visibleBank = LC_BANK_1;
673 else if ((addr & 0xFFFB) == 0xC08B)
676 WriteLog("LC(R): $C08B 49291 OECG RR Read/Write RAM bank 1\n");
678 //$C08B 49291 OECG RR Read/write RAM bank 1
679 visibleBank = LC_BANK_1;
683 else if ((addr & 0xFFF8) == 0xC0E0)
685 floppyDrive.ControlStepper(addr & 0x07);
687 else if ((addr & 0xFFFE) == 0xC0E8)
689 floppyDrive.ControlMotor(addr & 0x01);
691 else if ((addr & 0xFFFE) == 0xC0EA)
693 floppyDrive.DriveEnable(addr & 0x01);
695 else if (addr == 0xC0EC)
697 return floppyDrive.ReadWrite();
699 else if (addr == 0xC0ED)
701 return floppyDrive.GetLatchValue();
703 else if (addr == 0xC0EE)
705 floppyDrive.SetReadMode();
707 else if (addr == 0xC0EF)
709 floppyDrive.SetWriteMode();
712 //#define LC_DEBUGGING
714 bool showpath = false;
715 if (addr >= 0xD000 && addr <= 0xD00F)
719 if (addr >= 0xC100 && addr <= 0xC7FF) // The $C000-$CFFF block is *never* RAM
721 // Looks like the ][e ref manual got this one wrong: slotCXROM set should mean
722 // use internal ROM, NOT slot ROM. :-/
723 // (fixed now, by setting the switch correctly in the write mem section :-P)
729 if (addr >= 0xC100 && addr <= 0xC1FF)
730 b = parallelROM[addr & 0xFF];
731 else if (addr >= 0xC600 && addr <= 0xC6FF)
732 b = diskROM[addr & 0xFF];
733 else if (addr >= 0xC300 && addr <= 0xC3FF && !slotC3ROM)
741 WriteLog("b is from $C100-$CFFF block...\n");
744 else if (addr >= 0xC800 && addr <= 0xCFFF) // 2K peripheral or OS ROM
748 else if (addr >= 0xD000)
752 if (addr <= 0xDFFF && visibleBank == LC_BANK_1)
756 // b = ram[addr - 0x1000];
757 b = (altzp ? ram2[addr - 0x1000] : ram[addr - 0x1000]);
760 WriteLog("b is from LC bank #1 (ram[addr - 0x1000])...\n");
768 b = (altzp ? ram2[addr] : ram[addr]);
771 WriteLog("b is from LC bank #2 (ram[addr])...\n");
782 WriteLog("b is from LC ROM (rom[addr])...\n");
788 // 80STORE only works for WRITING, not READING!
790 // Check for 80STORE mode (STORE80 takes precedence over RAMRD/WRT)...
791 if ((((addr >= 0x0400) && (addr <= 0x07FF)) || ((addr >= 0x2000) && (addr <= 0x3FFF))) && store80Mode)
802 // Finally, check for auxillary/altzp write switches
804 b = (altzp ? ram2[addr] : ram[addr]);
806 b = (ramrd ? ram2[addr] : ram[addr]);
809 WriteLog("b is from ram[addr]...\n");
814 if (addr >= 0xD000 && addr <= 0xD00F)
816 WriteLog("*** Read from $%04X: $%02X (readRAM=%s, PC=%04X, ram$D000=%02X)\n", addr, b, (readRAM ? "T" : "F"), mainCPU.pc, ram[0xC000]);
825 APPENDIX F Assembly Language Program Listings
831 5 * ;ADDRESSES FOR FIRST 6522
832 6 ORB EQU $C400 ;PORT B
833 7 ORA EQU $C401 ;PORT A
834 8 DDRB EQU $C402 ;DATA DIRECTION REGISTER (A)
835 9 DDRA EQU $C403 ;DATA DIRECTION REGISTER (B)
836 10 * ;ADDRESSES FOR SECOND 6522
837 11 ORB2 EQU $C480 ;PORT B
838 12 ORA2 EQU $C481 ;PORT A
839 13 DDRB2 EQU $C482 ;DATA DIRECTION REGISTER (B)
840 14 DDRA2 EQU $C483 ;DATA DIRECTION REGISTER (A)
842 void WrMem(uint16_t addr, uint8_t b)
845 //extern V6809REGS regs;
846 //if (addr >= 0xC800 && addr <= 0xCBFE)
847 //if (addr == 0xC80F || addr == 0xC80D)
848 // WriteLog("WrMem: Writing address %04X with %02X [PC=%04X, $CB00=%02X]\n", addr, b, regs.pc, gram[0xCB00]);//*/
851 if (addr >= 0xC000 && addr <= 0xC0FF)
852 WriteLog("\n*** Write at I/O address %04X\n", addr);
855 Check the BIKO version on Asimov to see if it's been cracked or not...
857 7F3D: 29 07 AND #$07 [PC=7F3F, SP=01EA, CC=---B-I--, A=01, X=4B, Y=00]
858 7F3F: C9 06 CMP #$06 [PC=7F41, SP=01EA, CC=N--B-I--, A=01, X=4B, Y=00]
859 7F41: 90 03 BCC $7F46 [PC=7F46, SP=01EA, CC=N--B-I--, A=01, X=4B, Y=00]
860 [7F43: 4C 83 7E JMP $7E83] <- Skipped over... (Prints "THANK YOU VERY MUCH!")
861 7F46: AA TAX [PC=7F47, SP=01EA, CC=---B-I--, A=01, X=01, Y=00]
863 ; INX here *ensures* 1 - 6!!! BUG!!!
864 ; Or is it? Could this be part of a braindead copy protection scheme? It's
865 ; awfully close to NOP ($EA)...
866 ; Nothing else touches it once it's been written... Hmm...
868 7F47: E8 INX [PC=7F48, SP=01EA, CC=---B-I--, A=01, X=02, Y=00]
869 7F48: F8 SED [PC=7F49, SP=01EA, CC=---BDI--, A=01, X=02, Y=00]
870 7F49: 18 CLC [PC=7F4A, SP=01EA, CC=---BDI--, A=01, X=02, Y=00]
871 7F4A: BD 15 4E LDA $4E15,X [PC=7F4D, SP=01EA, CC=---BDI--, A=15, X=02, Y=00]
874 ; 4E15: 25 25 15 15 10 20
875 ; 4E1B: 03 41 99 99 01 00 12
878 7F4D: 65 FC ADC $FC [PC=7F4F, SP=01EA, CC=---BDI--, A=16, X=02, Y=00]
879 7F4F: 65 FC ADC $FC [PC=7F51, SP=01EA, CC=---BDI--, A=17, X=02, Y=00]
880 7F51: 65 FC ADC $FC [PC=7F53, SP=01EA, CC=---BDI--, A=18, X=02, Y=00]
881 7F53: 65 FC ADC $FC [PC=7F55, SP=01EA, CC=---BDI--, A=19, X=02, Y=00]
883 ; NO checking is done on the raised stat! Aarrrgggghhhhh!
885 7F55: 9D 15 4E STA $4E15,X [PC=7F58, SP=01EA, CC=---BDI--, A=19, X=02, Y=00]
886 7F58: D8 CLD [PC=7F59, SP=01EA, CC=---B-I--, A=19, X=02, Y=00]
888 ; Print "ALAKAZAM!" and so on...
890 7F59: 20 2C 40 JSR $402C [PC=402C, SP=01E8, CC=---B-I--, A=19, X=02, Y=00]
894 WriteLog("\n*** Byte %02X written at address %04X\n", b, addr);
897 I think this is IIc/IIe only...
899 CLR80STORE=$C000 ;80STORE Off- disable 80-column memory mapping (Write)
900 SET80STORE=$C001 ;80STORE On- enable 80-column memory mapping (WR-only)
902 CLRAUXRD = $C002 ;read from main 48K (WR-only)
903 SETAUXRD = $C003 ;read from aux/alt 48K (WR-only)
905 CLRAUXWR = $C004 ;write to main 48K (WR-only)
906 SETAUXWR = $C005 ;write to aux/alt 48K (WR-only)
908 CLRCXROM = $C006 ;use ROM on cards (WR-only)
909 SETCXROM = $C007 ;use internal ROM (WR-only)
911 CLRAUXZP = $C008 ;use main zero page, stack, & LC (WR-only)
912 SETAUXZP = $C009 ;use alt zero page, stack, & LC (WR-only)
914 CLRC3ROM = $C00A ;use internal Slot 3 ROM (WR-only)
915 SETC3ROM = $C00B ;use external Slot 3 ROM (WR-only)
917 CLR80VID = $C00C ;disable 80-column display mode (WR-only)
918 SET80VID = $C00D ;enable 80-column display mode (WR-only)
920 CLRALTCH = $C00E ;use main char set- norm LC, Flash UC (WR-only)
921 SETALTCH = $C00F ;use alt char set- norm inverse, LC; no Flash (WR-only)
925 #ifdef SOFT_SWITCH_DEBUGGING
926 WriteLog("80STORE off (write)\n");
930 else if (addr == 0xC001)
932 #ifdef SOFT_SWITCH_DEBUGGING
933 WriteLog("80STORE on (write)\n");
937 else if (addr == 0xC002)
939 #ifdef SOFT_SWITCH_DEBUGGING
940 WriteLog("RAMRD off (write)\n");
944 else if (addr == 0xC003)
946 #ifdef SOFT_SWITCH_DEBUGGING
947 WriteLog("RAMRD on (write)\n");
951 else if (addr == 0xC004)
953 #ifdef SOFT_SWITCH_DEBUGGING
954 WriteLog("RAMWRT off (write)\n");
958 else if (addr == 0xC005)
960 #ifdef SOFT_SWITCH_DEBUGGING
961 WriteLog("RAMWRT on (write)\n");
965 else if (addr == 0xC006)
967 // This is the only soft switch that breaks the usual convention.
968 #ifdef SOFT_SWITCH_DEBUGGING
969 WriteLog("SLOTCXROM on (write)\n");
973 else if (addr == 0xC007)
975 #ifdef SOFT_SWITCH_DEBUGGING
976 WriteLog("SLOTCXROM off (write)\n");
980 else if (addr == 0xC008)
982 #ifdef SOFT_SWITCH_DEBUGGING
983 WriteLog("ALTZP off (write)\n");
987 else if (addr == 0xC009)
989 #ifdef SOFT_SWITCH_DEBUGGING
990 WriteLog("ALTZP on (write)\n");
994 else if (addr == 0xC00A)
996 #ifdef SOFT_SWITCH_DEBUGGING
997 WriteLog("SLOTC3ROM off (write)\n");
1001 else if (addr == 0xC00B)
1003 #ifdef SOFT_SWITCH_DEBUGGING
1004 WriteLog("SLOTC3ROM on (write)\n");
1008 else if (addr == 0xC00C)
1010 #ifdef SOFT_SWITCH_DEBUGGING
1011 WriteLog("80COL off (write)\n");
1015 else if (addr == 0xC00D)
1017 #ifdef SOFT_SWITCH_DEBUGGING
1018 WriteLog("80COL on (write)\n");
1022 else if (addr == 0xC00E)
1024 #ifdef SOFT_SWITCH_DEBUGGING
1025 WriteLog("ALTCHARSET off (write)\n");
1027 alternateCharset = false;
1029 else if (addr == 0xC00F)
1031 #ifdef SOFT_SWITCH_DEBUGGING
1032 WriteLog("ALTCHARSET on (write)\n");
1034 alternateCharset = true;
1036 else if ((addr & 0xFFF0) == 0xC010) // Keyboard strobe
1038 //Actually, according to the A2 ref, this should do nothing since a write
1039 //is immediately preceded by a read leaving it in the same state it was...
1040 //But leaving this out seems to fuck up the key handling of some games...
1043 else if (addr == 0xC050)
1045 #ifdef SOFT_SWITCH_DEBUGGING
1046 WriteLog("TEXT off (write)\n");
1050 else if (addr == 0xC051)
1052 #ifdef SOFT_SWITCH_DEBUGGING
1053 WriteLog("TEXT on (write)\n");
1057 else if (addr == 0xC052)
1059 #ifdef SOFT_SWITCH_DEBUGGING
1060 WriteLog("MIXED off (write)\n");
1064 else if (addr == 0xC053)
1066 #ifdef SOFT_SWITCH_DEBUGGING
1067 WriteLog("MIXED on (write)\n");
1071 else if (addr == 0xC054)
1073 #ifdef SOFT_SWITCH_DEBUGGING
1074 WriteLog("PAGE2 off (write)\n");
1076 displayPage2 = false;
1078 else if (addr == 0xC055)
1080 #ifdef SOFT_SWITCH_DEBUGGING
1081 WriteLog("PAGE2 on (write)\n");
1083 displayPage2 = true;
1085 else if (addr == 0xC056)
1087 #ifdef SOFT_SWITCH_DEBUGGING
1088 WriteLog("HIRES off (write)\n");
1092 else if (addr == 0xC057)
1094 #ifdef SOFT_SWITCH_DEBUGGING
1095 WriteLog("HIRES on (write)\n");
1099 else if (addr == 0xC05E)
1101 #ifdef SOFT_SWITCH_DEBUGGING
1102 WriteLog("DHIRES on (write)\n");
1107 //static int goDumpDis = 0;
1109 //if (goDumpDis == 2)
1112 else if (addr == 0xC05F)
1114 #ifdef SOFT_SWITCH_DEBUGGING
1115 WriteLog("DHIRES off (write)\n");
1120 else if (addr == 0xC07E)
1122 #ifdef SOFT_SWITCH_DEBUGGING
1123 WriteLog("IOUDIS on (write)\n");
1127 else if (addr == 0xC07F)
1129 #ifdef SOFT_SWITCH_DEBUGGING
1130 WriteLog("IOUDIS off (write)\n");
1134 else if ((addr & 0xFFFB) == 0xC080)
1137 WriteLog("LC(R): $C080 49280 OECG R Read RAM bank 2; no write\n");
1139 //$C080 49280 OECG R Read RAM bank 2; no write
1140 visibleBank = LC_BANK_2;
1144 else if ((addr & 0xFFFB) == 0xC081)
1147 WriteLog("LC(R): $C081 49281 OECG RR Read ROM; write RAM bank 2\n");
1149 //$C081 49281 ROMIN OECG RR Read ROM; write RAM bank 2
1150 visibleBank = LC_BANK_2;
1154 else if ((addr & 0xFFFB) == 0xC082)
1157 WriteLog("LC(R): $C082 49282 OECG R Read ROM; no write\n");
1159 //$C082 49282 OECG R Read ROM; no write
1160 visibleBank = LC_BANK_2;
1164 else if ((addr & 0xFFFB) == 0xC083)
1167 WriteLog("LC(R): $C083 49283 OECG RR Read/Write RAM bank 2\n");
1169 //$C083 49283 LCBANK2 OECG RR Read/write RAM bank 2
1170 visibleBank = LC_BANK_2;
1174 else if ((addr & 0xFFFB) == 0xC088)
1177 WriteLog("LC(R): $C088 49288 OECG R Read RAM bank 1; no write\n");
1179 //$C088 49288 OECG R Read RAM bank 1; no write
1180 visibleBank = LC_BANK_1;
1184 else if ((addr & 0xFFFB) == 0xC089)
1187 WriteLog("LC(R): $C089 49289 OECG RR Read ROM; write RAM bank 1\n");
1189 //$C089 49289 OECG RR Read ROM; write RAM bank 1
1190 visibleBank = LC_BANK_1;
1194 else if ((addr & 0xFFFB) == 0xC08A)
1197 WriteLog("LC(R): $C08A 49290 OECG R Read ROM; no write\n");
1199 //$C08A 49290 OECG R Read ROM; no write
1200 visibleBank = LC_BANK_1;
1204 else if ((addr & 0xFFFB) == 0xC08B)
1207 WriteLog("LC(R): $C08B 49291 OECG RR Read/Write RAM bank 1\n");
1209 //$C08B 49291 OECG RR Read/write RAM bank 1
1210 visibleBank = LC_BANK_1;
1214 //This is determined by which slot it is in--this assumes slot 6. !!! FIX !!!
1215 else if ((addr & 0xFFF8) == 0xC0E0)
1217 floppyDrive.ControlStepper(addr & 0x07);
1219 else if ((addr & 0xFFFE) == 0xC0E8)
1221 floppyDrive.ControlMotor(addr & 0x01);
1223 else if ((addr & 0xFFFE) == 0xC0EA)
1225 floppyDrive.DriveEnable(addr & 0x01);
1227 else if (addr == 0xC0EC)
1229 //change this to Write()? (and the other to Read()?) Dunno. Seems to work OK, but still...
1231 floppyDrive.ReadWrite();
1233 else if (addr == 0xC0ED)
1235 floppyDrive.SetLatchValue(b);
1237 else if (addr == 0xC0EE)
1239 floppyDrive.SetReadMode();
1241 else if (addr == 0xC0EF)
1243 floppyDrive.SetWriteMode();
1245 //Still need to add missing I/O switches here...
1247 //DEEE: BD 10 BF LDA $BF10,X [PC=DEF1, SP=01F4, CC=--.B-IZ-, A=00, X=0C, Y=07]
1249 if (addr >= 0xD000 && addr <= 0xD00F)
1251 WriteLog("*** Write to $%04X: $%02X (writeRAM=%s, PC=%04X, ram$D000=%02X)\n", addr, b, (writeRAM ? "T" : "F"), mainCPU.pc, ram[0xC000]);
1254 if (addr >= 0xC000 && addr <= 0xCFFF)
1255 return; // Protect LC bank #1 from being written to!
1262 if (addr <= 0xDFFF && visibleBank == LC_BANK_1)
1263 ram[addr - 0x1000] = b;
1267 if (addr <= 0xDFFF && visibleBank == LC_BANK_1)
1270 ram2[addr - 0x1000] = b;
1272 ram[addr - 0x1000] = b;
1287 // Check for 80STORE mode (STORE80 takes precedence over RAMRD/WRT)...
1288 if ((((addr >= 0x0400) && (addr <= 0x07FF)) || ((addr >= 0x2000) && (addr <= 0x3FFF))) && store80Mode)
1298 // Finally, check for auxillary/altzp write switches
1311 // if (addr < 0x0200 || addr >= 0xD000)
1315 WriteLog("Write $38: $%02X\n", b);
1316 else if (addr == 0x39)
1317 WriteLog("Write $39: $%02X\n", b);
1336 // Load a file into RAM/ROM image space
1338 bool LoadImg(char * filename, uint8_t * ram, int size)
1340 FILE * fp = fopen(filename, "rb");
1345 fread(ram, 1, size, fp);
1352 static void SaveApple2State(const char * filename)
1357 static bool LoadApple2State(const char * filename)
1363 #ifdef CPU_CLOCK_CHECKING
1364 uint8_t counter = 0;
1365 uint32_t totalCPU = 0;
1366 uint64_t lastClock = 0;
1371 int main(int /*argc*/, char * /*argv*/[])
1373 InitLog("./apple2.log");
1375 srand(time(NULL)); // Initialize RNG
1378 memset(ram, 0, 0x10000);
1379 memset(rom, 0, 0x10000);
1380 memset(ram2, 0, 0x10000);
1382 // Set up V65C02 execution context
1383 memset(&mainCPU, 0, sizeof(V65C02REGS));
1384 mainCPU.RdMem = RdMem;
1385 mainCPU.WrMem = WrMem;
1386 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
1388 // alternateCharset = true;
1389 // if (!LoadImg(settings.BIOSPath, rom + 0xD000, 0x3000))
1390 if (!LoadImg(settings.BIOSPath, rom + 0xC000, 0x4000))
1392 WriteLog("Could not open file '%s'!\n", settings.BIOSPath);
1396 //This is now included...
1397 /* if (!LoadImg(settings.diskPath, diskRom, 0x100))
1399 WriteLog("Could not open file '%s'!\nDisk II will be unavailable!\n", settings.diskPath);
1403 //Load up disk image from config file (for now)...
1404 floppyDrive.LoadImage(settings.diskImagePath1, 0);
1405 floppyDrive.LoadImage(settings.diskImagePath2, 1);
1406 // floppyDrive.LoadImage("./disks/temp.nib", 1); // Load temp .nib file into second drive...
1408 //Kill the DOS ROM in slot 6 for now...
1410 // memcpy(rom + 0xC600, diskROM, 0x100);
1411 // memcpy(rom + 0xC700, diskROM, 0x100); // Slot 7???
1413 WriteLog("About to initialize video...\n");
1417 std::cout << "Aborting!" << std::endl;
1421 // Have to do this *after* video init but *before* sound init...!
1422 //Shouldn't be necessary since we're not doing emulation in the ISR...
1423 if (settings.autoStateSaving)
1425 // Load last state from file...
1426 if (!LoadApple2State(settings.autoStatePath))
1427 WriteLog("Unable to use Apple2 state file \"%s\"!\n", settings.autoStatePath);
1433 if (!LoadImg("./BT1_6502_RAM_SPACE.bin", ram, 0x10000))
1435 cout << "Couldn't load state file!" << endl;
1436 cout << "Aborting!!" << endl;
1441 //-- -- -- -- ----- -----
1442 //00 75 3B 53 FD 01 41 44
1444 mainCPU.cpuFlags = 0;
1450 mainCPU.pc = 0x4441;
1454 displayPage2 = false;
1461 memcpy(ram + 0xD000, ram + 0xC000, 0x1000);
1464 WriteLog("About to initialize audio...\n");
1466 //nope SDL_EnableUNICODE(1); // Needed to do key translation shit
1468 // gui = new GUI(surface); // Set up the GUI system object...
1469 // gui = new GUI(mainSurface); // Set up the GUI system object...
1470 // SDL 2... this will likely cause Apple 2 to crash
1471 // gui = new GUI(NULL); // Set up the GUI system object...
1473 gui->AddMenuTitle("Apple2");
1474 gui->AddMenuItem("Test!", TestWindow/*, hotkey*/);
1475 gui->AddMenuItem("");
1476 gui->AddMenuItem("Quit", QuitEmulator, SDLK_q);
1477 gui->CommitItemsToMenu();
1480 SetupBlurTable(); // Set up the color TV emulation blur table
1481 running = true; // Set running status...
1483 InitializeEventList(); // Clear the event list before we use it...
1484 SetCallbackTime(FrameCallback, 16666.66666667); // Set frame to fire at 1/60 s interval
1485 SetCallbackTime(BlinkTimer, 250000); // Set up blinking at 1/4 s intervals
1486 startTicks = SDL_GetTicks();
1488 #ifdef THREADED_65C02
1489 cpuCond = SDL_CreateCond();
1490 mainSem = SDL_CreateSemaphore(1);
1491 cpuThread = SDL_CreateThread(CPUThreadFunc, NULL, NULL);
1492 //Hmm... CPU does POST (+1), wait, then WAIT (-1)
1493 // SDL_sem * mainMutex = SDL_CreateMutex();
1496 WriteLog("Entering main loop...\n");
1499 double timeToNextEvent = GetTimeToNextEvent();
1500 #ifndef THREADED_65C02
1501 Execute65C02(&mainCPU, USEC_TO_M6502_CYCLES(timeToNextEvent));
1503 //We MUST remove a frame's worth of time in order for the CPU to function... !!! FIX !!!
1504 //(Fix so that this is not a requirement!)
1505 //Fixed, but mainCPU.clock is destroyed in the bargain. Oh well.
1506 // mainCPU.clock -= USEC_TO_M6502_CYCLES(timeToNextEvent);
1508 #ifdef CPU_CLOCK_CHECKING
1509 #ifndef THREADED_65C02
1510 totalCPU += USEC_TO_M6502_CYCLES(timeToNextEvent);
1513 // Handle CPU time delta for sound...
1514 //Don't need this anymore now that we use absolute time...
1515 // AddToSoundTimeBase(USEC_TO_M6502_CYCLES(timeToNextEvent));
1519 #ifdef THREADED_65C02
1520 WriteLog("Main: cpuFinished = true;\n");
1522 //#warning "If sound thread is behind, CPU thread will never wake up... !!! FIX !!!" [DONE]
1523 //What to do? How do you know when the CPU is sleeping???
1524 //USE A CONDITIONAL!!! OF COURSE!!!!!!11!11!11!!!1!
1526 SDL_mutexP(mainMutex);
1527 SDL_CondWait(mainCond, mainMutex); // Wait for CPU thread to get to signal point...
1528 SDL_mutexV(mainMutex);
1530 //Nope, use a semaphore...
1531 WriteLog("Main: SDL_SemWait(mainSem);\n");
1532 SDL_SemWait(mainSem);//should lock until CPU thread is waiting...
1535 WriteLog("Main: SDL_CondSignal(cpuCond);//thread is probably asleep, wake it up\n");
1536 SDL_CondSignal(cpuCond);//thread is probably asleep, wake it up
1537 WriteLog("Main: SDL_WaitThread(cpuThread, NULL);\n");
1538 SDL_WaitThread(cpuThread, NULL);
1539 //nowok:SDL_WaitThread(CPUThreadFunc, NULL);
1540 WriteLog("Main: SDL_DestroyCond(cpuCond);\n");
1541 SDL_DestroyCond(cpuCond);
1543 //SDL_DestroyMutex(mainMutex);
1544 SDL_DestroySemaphore(mainSem);
1547 if (settings.autoStateSaving)
1549 // Save state here...
1550 SaveApple2State(settings.autoStatePath);
1552 floppyDrive.SaveImage();
1568 -----------------------
1569 space $A0 $A0 $A0 $A0 No xlation
1570 RETURN $8D $8D $8D $8D No xlation
1571 0 $B0 $B0 $B0 $B0 Need to screen shift+0 (?)
1572 1! $B1 $B1 $A1 $A1 No xlation
1573 2" $B2 $B2 $A2 $A2 No xlation
1574 3# $B3 $B3 $A3 $A3 No xlation
1575 4$ $B4 $B4 $A4 $A4 No xlation
1576 5% $B5 $B5 $A5 $A5 No xlation
1577 6& $B6 $B6 $A6 $A6 No xlation
1578 7' $B7 $B7 $A7 $A7 No xlation
1579 8( $B8 $B8 $A8 $A8 No xlation
1580 9) $B9 $B9 $A9 $A9 No xlation
1581 :* $BA $BA $AA $AA No xlation
1582 ;+ $BB $BB $AB $AB No xlation
1583 ,< $AC $AC $BC $BC No xlation
1584 -= $AD $AD $BD $BD No xlation
1585 .> $AE $AE $BE $BE No xlation
1586 /? $AF $AF $BF $BF No xlation
1599 M $CD $8D $DD $9D -> ODD
1600 N^ $CE $8E $DE $9E -> ODD
1602 P@ $D0 $90 $C0 $80 Need to xlate CTL+SHFT+P & SHFT+P (?)
1615 ESC $9B $9B $9B $9B No xlation
1618 //static uint64_t lastCPUCycles = 0;
1619 static uint32_t frameCount = 0;
1620 static void FrameCallback(void)
1624 while (SDL_PollEvent(&event))
1629 //Need to do some key translation here, and screen out non-apple keys as well...
1630 //(really, could do it all in SDL_KEYDOWN, would just have to get symbols &
1631 // everything else done separately. this is slightly easier. :-P)
1632 // if (event.key.keysym.sym == SDLK_TAB) // Prelim key screening...
1633 if (event.edit.text[0] == '\t') // Prelim key screening...
1636 lastKeyPressed = event.edit.text[0];
1639 //kludge: should have a caps lock thingy here...
1640 //or all uppercase for ][+...
1641 // if (lastKeyPressed >= 'a' && lastKeyPressed <='z')
1642 // lastKeyPressed &= 0xDF; // Convert to upper case...
1646 // CTRL+RESET key emulation (mapped to CTRL+`)
1647 // This doesn't work...
1648 // if (event.key.keysym.sym == SDLK_BREAK && (event.key.keysym.mod & KMOD_CTRL))
1649 // if (event.key.keysym.sym == SDLK_PAUSE && (event.key.keysym.mod & KMOD_CTRL))
1650 if (event.key.keysym.sym == SDLK_BACKQUOTE && (event.key.keysym.mod & KMOD_CTRL))
1651 //NOTE that this shouldn't take place until the key is lifted... !!! FIX !!!
1652 //ALSO it seems to leave the machine in an inconsistent state vis-a-vis the language card...
1653 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
1655 if (event.key.keysym.sym == SDLK_RIGHT)
1656 lastKeyPressed = 0x15, keyDown = true;
1657 else if (event.key.keysym.sym == SDLK_LEFT)
1658 lastKeyPressed = 0x08, keyDown = true;
1659 else if (event.key.keysym.sym == SDLK_UP)
1660 lastKeyPressed = 0x0B, keyDown = true;
1661 else if (event.key.keysym.sym == SDLK_DOWN)
1662 lastKeyPressed = 0x0A, keyDown = true;
1663 else if (event.key.keysym.sym == SDLK_RETURN)
1664 lastKeyPressed = 0x0D, keyDown = true;
1665 else if (event.key.keysym.sym == SDLK_ESCAPE)
1666 lastKeyPressed = 0x1B, keyDown = true;
1667 else if (event.key.keysym.sym == SDLK_BACKSPACE)
1668 lastKeyPressed = 0x7F, keyDown = true;
1670 // Fix CTRL+key combo...
1671 if (event.key.keysym.mod & KMOD_CTRL)
1673 if (event.key.keysym.sym >= SDLK_a && event.key.keysym.sym <= SDLK_z)
1675 lastKeyPressed = (event.key.keysym.sym - SDLK_a) + 1;
1677 //printf("Key combo pressed: CTRL+%c\n", lastKeyPressed + 0x40);
1681 // Use ALT+Q to exit, as well as the usual window decoration method
1682 if (event.key.keysym.sym == SDLK_q && (event.key.keysym.mod & KMOD_ALT))
1685 if (event.key.keysym.sym == SDLK_PAUSE)
1687 pauseMode = !pauseMode;
1692 SpawnMessage("*** PAUSED ***");
1697 SpawnMessage("*** RESUME ***");
1701 // Paddle buttons 0 & 1
1702 if (event.key.keysym.sym == SDLK_INSERT)
1703 openAppleDown = true;
1704 if (event.key.keysym.sym == SDLK_PAGEUP)
1705 closedAppleDown = true;
1707 if (event.key.keysym.sym == SDLK_F11)
1708 dumpDis = !dumpDis; // Toggle the disassembly process
1709 // else if (event.key.keysym.sym == SDLK_F11)
1710 // floppyDrive.LoadImage("./disks/bt1_char.dsk");//Kludge to load char disk...
1711 else if (event.key.keysym.sym == SDLK_F9)
1713 floppyDrive.CreateBlankImage();
1714 // SpawnMessage("Image cleared...");
1716 else if (event.key.keysym.sym == SDLK_F10)
1718 floppyDrive.SwapImages();
1719 // SpawnMessage("Image swapped...");
1722 if (event.key.keysym.sym == SDLK_F2)// Toggle the palette
1724 else if (event.key.keysym.sym == SDLK_F3)// Cycle through screen types
1727 // if (event.key.keysym.sym == SDLK_F5) // Temp GUI launch key
1728 if (event.key.keysym.sym == SDLK_F1) // GUI launch key
1729 //NOTE: Should parse the output to determine whether or not the user requested
1730 // to quit completely... !!! FIX !!!
1733 if (event.key.keysym.sym == SDLK_F5)
1736 char volStr[19] = "[****************]";
1737 // volStr[GetVolume()] = 0;
1738 for(int i=GetVolume(); i<16; i++)
1739 volStr[1 + i] = '-';
1740 SpawnMessage("Volume: %s", volStr);
1742 else if (event.key.keysym.sym == SDLK_F6)
1745 char volStr[19] = "[****************]";
1746 // volStr[GetVolume()] = 0;
1747 for(int i=GetVolume(); i<16; i++)
1748 volStr[1 + i] = '-';
1749 SpawnMessage("Volume: %s", volStr);
1752 static bool fullscreenDebounce = false;
1754 if (event.key.keysym.sym == SDLK_F12)
1756 if (!fullscreenDebounce)
1759 fullscreenDebounce = true;
1766 if (event.key.keysym.sym == SDLK_F12)
1767 fullscreenDebounce = false;
1769 // Paddle buttons 0 & 1
1770 if (event.key.keysym.sym == SDLK_INSERT)
1771 openAppleDown = false;
1772 if (event.key.keysym.sym == SDLK_PAGEUP)
1773 closedAppleDown = false;
1775 // if (event.key.keysym.sym >= SDLK_a && event.key.keysym.sym <= SDLK_z)
1784 //#warning "!!! Taking MAJOR time hit with the video frame rendering !!!"
1786 SetCallbackTime(FrameCallback, 16666.66666667);
1788 #ifdef CPU_CLOCK_CHECKING
1789 //We know it's stopped, so we can get away with this...
1793 uint64_t clock = GetCurrentV65C02Clock();
1794 //totalCPU += (uint32_t)(clock - lastClock);
1796 printf("Executed %u cycles...\n", (uint32_t)(clock - lastClock));
1802 //Instead of this, we should yield remaining time to other processes... !!! FIX !!! [DONE]
1805 //Actually, slows things down too much...
1807 // while (SDL_GetTicks() - startTicks < 16); // Wait for next frame...
1809 // This is the problem: If you set the interval to 16, it runs faster than
1810 // 1/60s per frame. If you set it to 17, it runs slower. What we need is to
1811 // have it do 16 for one frame, then 17 for two others. Then it should average
1812 // out to 1/60s per frame every 3 frames.
1813 frameCount = (frameCount + 1) % 3;
1815 uint32_t waitFrameTime = 17 - (frameCount == 0 ? 1 : 0);
1817 while (SDL_GetTicks() - startTicks < waitFrameTime)
1818 SDL_Delay(1); // Wait for next frame...
1820 startTicks = SDL_GetTicks();
1822 uint64_t cpuCycles = GetCurrentV65C02Clock();
1823 uint32_t cyclesBurned = (uint32_t)(cpuCycles - lastCPUCycles);
1824 WriteLog("FrameCallback: used %i cycles\n", cyclesBurned);
1825 lastCPUCycles = cpuCycles;
1828 //let's wait, then signal...
1829 //works longer, but then still falls behind...
1830 #ifdef THREADED_65C02
1832 SDL_CondSignal(cpuCond);//OK, let the CPU go another frame...
1837 static void BlinkTimer(void)
1840 SetCallbackTime(BlinkTimer, 250000); // Set up blinking at 1/4 sec intervals
1845 Next problem is this: How to have events occur and synchronize with the rest
1848 o Have the CPU thread manage the timer mechanism? (need to have a method of carrying
1849 remainder CPU cycles over...)
1851 One way would be to use a fractional accumulator, then subtract 1 every
1852 time it overflows. Like so:
1854 double overflow = 0;
1858 Execute6808(&soundCPU, time);
1859 overflow += 0.289115646;