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;
102 static GUI * gui = NULL;
104 // Local functions (technically, they're global...)
106 bool LoadImg(char * filename, uint8_t * ram, int size);
107 uint8_t RdMem(uint16_t addr);
108 void WrMem(uint16_t addr, uint8_t b);
109 static void SaveApple2State(const char * filename);
110 static bool LoadApple2State(const char * filename);
112 // Local timer callback functions
114 static void FrameCallback(void);
115 static void BlinkTimer(void);
117 #ifdef THREADED_65C02
118 // Test of threaded execution of 6502
119 static SDL_Thread * cpuThread = NULL;
120 //static SDL_mutex * cpuMutex = NULL;
121 static SDL_cond * cpuCond = NULL;
122 static SDL_sem * mainSem = NULL;
123 static bool cpuFinished = false;
124 static bool cpuSleep = false;
127 // NB: Apple //e Manual sez 6502 is running @ 1,022,727 Hz
129 // Let's try a thread...
131 Here's how it works: Execute 1 frame's worth, then sleep.
132 Other stuff wakes it up
134 int CPUThreadFunc(void * data)
136 // Mutex must be locked for conditional to work...
137 // Also, must be created in the thread that uses it...
138 SDL_mutex * cpuMutex = SDL_CreateMutex();
140 // decrement mainSem...
141 //SDL_SemWait(mainSem);
142 #ifdef CPU_THREAD_OVERFLOW_COMPENSATION
143 float overflow = 0.0;
149 SDL_CondWait(cpuCond, cpuMutex);
151 // decrement mainSem...
152 #ifdef THREAD_DEBUGGING
153 WriteLog("CPU: SDL_SemWait(mainSem);\n");
155 SDL_SemWait(mainSem);
157 // There are exactly 800 slices of 21.333 cycles per frame, so it works out
160 uint32_t cycles = 17066;
161 #ifdef CPU_THREAD_OVERFLOW_COMPENSATION
162 // ODD! It's closer *without* this overflow compensation. ??? WHY ???
163 overflow += 0.666666667;
172 #ifdef THREAD_DEBUGGING
173 WriteLog("CPU: Execute65C02(&mainCPU, cycles);\n");
175 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!
177 // Adjust the sound routine's last cycle toggled time base
178 // Also, since we're finished executing, .clock is now valid
179 #ifdef THREAD_DEBUGGING
180 WriteLog("CPU: AdjustLastToggleCycles(mainCPU.clock);\n");
182 AdjustLastToggleCycles(mainCPU.clock);
184 #ifdef THREAD_DEBUGGING
185 WriteLog("CPU: Execute65C02(&mainCPU, cycles);\n");
187 for(int i=0; i<800; i++)
189 uint32_t cycles = 21;
190 overflow += 0.333333334;
198 Execute65C02(&mainCPU, cycles);
199 WriteSampleToBuffer();
203 //WriteLog("CPUThread: Supposedly end of frame...\n");
205 #ifdef THREAD_DEBUGGING
206 WriteLog("CPU: SDL_mutexP(cpuMutex);\n");
208 SDL_mutexP(cpuMutex);
210 if (SDL_CondWait(cpuCond, cpuMutex) != 0)
212 printf("SDL_CondWait != 0! (Error: '%s')\n", SDL_GetError());
216 // increment mainSem...
217 #ifdef THREAD_DEBUGGING
218 WriteLog("CPU: SDL_SemPost(mainSem);\n");
220 SDL_SemPost(mainSem);
221 // SDL_CondSignal(mainCond); // In case something is waiting on us...
223 #ifdef THREAD_DEBUGGING
224 WriteLog("CPU: SDL_CondWait(cpuCond, cpuMutex);\n");
226 SDL_CondWait(cpuCond, cpuMutex);
229 #ifdef THREAD_DEBUGGING
230 WriteLog("CPU: SDL_mutexV(cpuMutex);\n");
232 SDL_mutexV(cpuMutex);
234 while (!cpuFinished);
236 SDL_DestroyMutex(cpuMutex);
245 Element * TestWindow(void)
247 Element * win = new DraggableWindow2(10, 10, 128, 128);
248 // ((DraggableWindow *)win)->AddElement(new TextEdit(4, 16, 92, 0, "u2prog.dsk", win));
254 Element * QuitEmulator(void)
264 Small Apple II memory map:
266 $C010 - Clear bit 7 of keyboard data ($C000)
267 $C030 - Toggle speaker diaphragm
269 $C054 - Select page 1
270 $C056 - Select lo-res
271 $C058 - Set annuciator-0 output to 0
272 $C05A - Set annuciator-0 output to 0
273 $C05D - Set annuciator-0 output to 1
274 $C05F - Set annuciator-0 output to 1
275 $C0E0 - Disk control stepper ($C0E0-7)
276 $C0E9 - Disk control motor (on)
277 $C0EA - Disk enable (drive 1)
279 $C0EE - Disk set read mode
283 // V65C02 read/write memory functions
286 uint8_t RdMem(uint16_t addr)
291 if (addr >= 0xC000 && addr <= 0xC0FF)
292 WriteLog("\n*** Read at I/O address %04X\n", addr);
295 if (addr >= 0xC080 && addr <= 0xC08F)
296 WriteLog("\n*** Read at I/O address %04X\n", addr);
299 if ((addr & 0xFFF0) == 0xC000) // Read $C000-$C00F
301 return lastKeyPressed | (keyDown ? 0x80 : 0x00);
303 // else if ((addr & 0xFFF8) == 0xC010) // Read $C010-$C01F
304 else if (addr == 0xC010)
306 //This is bogus: keyDown is set to false, so return val NEVER is set...
308 //Also, this is IIe/IIc only...!
309 uint8_t retVal = lastKeyPressed | (keyDown ? 0x80 : 0x00);
313 // These are //e locations
314 else if (addr == 0xC011)
316 #ifdef SOFT_SWITCH_DEBUGGING
317 WriteLog("RDBANK2 (read)\n");
319 return (visibleBank == LC_BANK_2 ? 0x80 : 0x00);
321 else if (addr == 0xC012)
323 #ifdef SOFT_SWITCH_DEBUGGING
324 WriteLog("RDLCRAM (read)\n");
326 return (readRAM ? 0x80 : 0x00);
328 else if (addr == 0xC013)
330 #ifdef SOFT_SWITCH_DEBUGGING
331 WriteLog("RAMRD (read)\n");
333 return (ramrd ? 0x80 : 0x00);
335 else if (addr == 0xC014)
337 #ifdef SOFT_SWITCH_DEBUGGING
338 WriteLog("RAMWRT (read)\n");
340 return (ramwrt ? 0x80 : 0x00);
342 else if (addr == 0xC015)
344 #ifdef SOFT_SWITCH_DEBUGGING
345 WriteLog("SLOTCXROM (read)\n");
347 return (slotCXROM ? 0x80 : 0x00);
349 else if (addr == 0xC016)
351 #ifdef SOFT_SWITCH_DEBUGGING
352 WriteLog("ALTZP (read)\n");
354 return (altzp ? 0x80 : 0x00);
356 else if (addr == 0xC017)
358 #ifdef SOFT_SWITCH_DEBUGGING
359 WriteLog("SLOTC3ROM (read)\n");
361 return (slotC3ROM ? 0x80 : 0x00);
363 else if (addr == 0xC018)
365 #ifdef SOFT_SWITCH_DEBUGGING
366 WriteLog("80STORE (read)\n");
368 return (store80Mode ? 0x80 : 0x00);
370 else if (addr == 0xC019)
372 #ifdef SOFT_SWITCH_DEBUGGING
373 WriteLog("VBL (read)\n");
375 return (vbl ? 0x80 : 0x00);
377 else if (addr == 0xC01A)
379 #ifdef SOFT_SWITCH_DEBUGGING
380 WriteLog("TEXT (read)\n");
382 return (textMode ? 0x80 : 0x00);
384 else if (addr == 0xC01B)
386 #ifdef SOFT_SWITCH_DEBUGGING
387 WriteLog("MIXED (read)\n");
389 return (mixedMode ? 0x80 : 0x00);
391 else if (addr == 0xC01C)
393 #ifdef SOFT_SWITCH_DEBUGGING
394 WriteLog("PAGE2 (read)\n");
396 return (displayPage2 ? 0x80 : 0x00);
398 else if (addr == 0xC01D)
400 #ifdef SOFT_SWITCH_DEBUGGING
401 WriteLog("HIRES (read)\n");
403 return (hiRes ? 0x80 : 0x00);
405 else if (addr == 0xC01E)
407 #ifdef SOFT_SWITCH_DEBUGGING
408 WriteLog("ALTCHARSET (read)\n");
410 return (alternateCharset ? 0x80 : 0x00);
412 else if (addr == 0xC01F)
414 #ifdef SOFT_SWITCH_DEBUGGING
415 WriteLog("80COL (read)\n");
417 return (col80Mode ? 0x80 : 0x00);
419 else if ((addr & 0xFFF0) == 0xC030) // Read $C030-$C03F
422 This is problematic, mainly because the v65C02 removes actual cycles run after each call.
423 Therefore, we don't really have a reliable method of sending a timestamp to the sound routine.
426 What we need to send is a delta T value but related to the IRQ buffer routine. E.g., if the buffer
427 hasn't had any changes in it then we just fill it with the last sample value and are done. Then
428 we need to adjust our delta T accordingly. What we could do is keep a running total of time since the
429 last change and adjust it accordingly, i.e., whenever a sound IRQ happens.
432 Have deltaT somewhere. Then, whenever there's a toggle, backfill buffer with last spkr state and reset
433 deltaT to zero. In the sound IRQ, if deltaT > buffer size, then subtract buffer size from deltaT. (?)
438 ToggleSpeaker(GetCurrentV65C02Clock());
439 //should it return something else here???
442 else if (addr == 0xC050) // Read $C050
444 #ifdef SOFT_SWITCH_DEBUGGING
445 WriteLog("TEXT off (read)\n");
449 else if (addr == 0xC051) // Read $C051
451 #ifdef SOFT_SWITCH_DEBUGGING
452 WriteLog("TEXT on (read)\n");
456 else if (addr == 0xC052) // Read $C052
458 #ifdef SOFT_SWITCH_DEBUGGING
459 WriteLog("MIXED off (read)\n");
463 else if (addr == 0xC053) // Read $C053
465 #ifdef SOFT_SWITCH_DEBUGGING
466 WriteLog("MIXED on (read)\n");
470 else if (addr == 0xC054) // Read $C054
472 #ifdef SOFT_SWITCH_DEBUGGING
473 WriteLog("PAGE2 off (read)\n");
475 displayPage2 = false;
477 else if (addr == 0xC055) // Read $C055
479 #ifdef SOFT_SWITCH_DEBUGGING
480 WriteLog("PAGE2 on (read)\n");
484 else if (addr == 0xC056) // Read $C056
486 #ifdef SOFT_SWITCH_DEBUGGING
487 WriteLog("HIRES off (read)\n");
491 else if (addr == 0xC057) // Read $C057
493 #ifdef SOFT_SWITCH_DEBUGGING
494 WriteLog("HIRES on (read)\n");
498 else if (addr == 0xC05E)
500 #ifdef SOFT_SWITCH_DEBUGGING
501 WriteLog("DHIRES on (read)\n");
506 else if (addr == 0xC05F)
508 #ifdef SOFT_SWITCH_DEBUGGING
509 WriteLog("DHIRES off (read)\n");
514 else if (addr == 0xC061) // Read $C061
516 // Open Apple key (or push button 0)
517 return (openAppleDown ? 0x80 : 0x00);
519 else if (addr == 0xC062) // Read $C062
521 // Open Apple key (or push button 0)
522 return (closedAppleDown ? 0x80 : 0x00);
524 // The way the paddles work is that a strobe is written (or read) to $C070,
525 // then software counts down the time that it takes for the paddle outputs
526 // to have bit 7 return to 0. If there are no paddles connected, bit 7
528 else if (addr == 0xC064) // Paddles 0-3
532 else if (addr == 0xC065)
536 else if (addr == 0xC066)
540 else if (addr == 0xC067)
544 else if (addr == 0xC07E)
546 #ifdef SOFT_SWITCH_DEBUGGING
547 WriteLog("IOUDIS (read)\n");
549 return (ioudis ? 0x80 : 0x00);
551 else if (addr == 0xC07F)
553 #ifdef SOFT_SWITCH_DEBUGGING
554 WriteLog("DHIRES (read)\n");
556 return (dhires ? 0x80 : 0x00);
559 //Note that this is a kludge: The $D000-$DFFF 4K space is shared (since $C000-$CFFF is
560 //memory mapped) between TWO banks, and that that $E000-$FFFF RAM space is a single bank.
561 //[SHOULD BE FIXED NOW]
562 //OK! This switch selects bank 2 of the 4K bank at $D000-$DFFF. One access makes it
563 //visible, two makes it R/W.
598 else if ((addr & 0xFFFB) == 0xC080)
601 WriteLog("LC(R): $C080 49280 OECG R Read RAM bank 2; no write\n");
603 //$C080 49280 OECG R Read RAM bank 2; no write
604 visibleBank = LC_BANK_2;
608 else if ((addr & 0xFFFB) == 0xC081)
611 WriteLog("LC(R): $C081 49281 OECG RR Read ROM; write RAM bank 2\n");
613 //$C081 49281 ROMIN OECG RR Read ROM; write RAM bank 2
614 visibleBank = LC_BANK_2;
618 else if ((addr & 0xFFFB) == 0xC082)
621 WriteLog("LC(R): $C082 49282 OECG R Read ROM; no write\n");
623 //$C082 49282 OECG R Read ROM; no write
624 visibleBank = LC_BANK_2;
628 else if ((addr & 0xFFFB) == 0xC083)
631 WriteLog("LC(R): $C083 49283 OECG RR Read/Write RAM bank 2\n");
633 //$C083 49283 LCBANK2 OECG RR Read/write RAM bank 2
634 visibleBank = LC_BANK_2;
638 else if ((addr & 0xFFFB) == 0xC088)
641 WriteLog("LC(R): $%04X 49288 OECG R Read RAM bank 1; no write\n", addr);
643 //$C088 49288 OECG R Read RAM bank 1; no write
644 visibleBank = LC_BANK_1;
647 //Hm. Some stuff seems to want this.
648 //nope, was looking at $C0E8... return 0xFF;
650 else if ((addr & 0xFFFB) == 0xC089)
653 WriteLog("LC(R): $C089 49289 OECG RR Read ROM; write RAM bank 1\n");
655 //$C089 49289 OECG RR Read ROM; write RAM bank 1
656 visibleBank = LC_BANK_1;
660 else if ((addr & 0xFFFB) == 0xC08A)
663 WriteLog("LC(R): $C08A 49290 OECG R Read ROM; no write\n");
665 //$C08A 49290 OECG R Read ROM; no write
666 visibleBank = LC_BANK_1;
670 else if ((addr & 0xFFFB) == 0xC08B)
673 WriteLog("LC(R): $C08B 49291 OECG RR Read/Write RAM bank 1\n");
675 //$C08B 49291 OECG RR Read/write RAM bank 1
676 visibleBank = LC_BANK_1;
680 else if ((addr & 0xFFF8) == 0xC0E0)
682 floppyDrive.ControlStepper(addr & 0x07);
684 else if ((addr & 0xFFFE) == 0xC0E8)
686 floppyDrive.ControlMotor(addr & 0x01);
688 else if ((addr & 0xFFFE) == 0xC0EA)
690 floppyDrive.DriveEnable(addr & 0x01);
692 else if (addr == 0xC0EC)
694 return floppyDrive.ReadWrite();
696 else if (addr == 0xC0ED)
698 return floppyDrive.GetLatchValue();
700 else if (addr == 0xC0EE)
702 floppyDrive.SetReadMode();
704 else if (addr == 0xC0EF)
706 floppyDrive.SetWriteMode();
709 //#define LC_DEBUGGING
711 bool showpath = false;
712 if (addr >= 0xD000 && addr <= 0xD00F)
716 if (addr >= 0xC100 && addr <= 0xC7FF) // The $C000-$CFFF block is *never* RAM
718 // Looks like the ][e ref manual got this one wrong: slotCXROM set should mean
719 // use internal ROM, NOT slot ROM. :-/
720 // (fixed now, by setting the switch correctly in the write mem section :-P)
726 if (addr >= 0xC100 && addr <= 0xC1FF)
727 b = parallelROM[addr & 0xFF];
728 else if (addr >= 0xC600 && addr <= 0xC6FF)
729 b = diskROM[addr & 0xFF];
730 else if (addr >= 0xC300 && addr <= 0xC3FF && !slotC3ROM)
738 WriteLog("b is from $C100-$CFFF block...\n");
741 else if (addr >= 0xC800 && addr <= 0xCFFF) // 2K peripheral or OS ROM
745 else if (addr >= 0xD000)
749 if (addr <= 0xDFFF && visibleBank == LC_BANK_1)
753 // b = ram[addr - 0x1000];
754 b = (altzp ? ram2[addr - 0x1000] : ram[addr - 0x1000]);
757 WriteLog("b is from LC bank #1 (ram[addr - 0x1000])...\n");
765 b = (altzp ? ram2[addr] : ram[addr]);
768 WriteLog("b is from LC bank #2 (ram[addr])...\n");
779 WriteLog("b is from LC ROM (rom[addr])...\n");
785 // Check for 80STORE mode (STORE80 takes precedence over RAMRD/WRT)...
786 if ((((addr >= 0x0400) && (addr <= 0x07FF)) || ((addr >= 0x2000) && (addr <= 0x3FFF))) && store80Mode)
796 // Finally, check for auxillary/altzp write switches
798 b = (altzp ? ram2[addr] : ram[addr]);
800 b = (ramrd ? ram2[addr] : ram[addr]);
803 WriteLog("b is from ram[addr]...\n");
808 if (addr >= 0xD000 && addr <= 0xD00F)
810 WriteLog("*** Read from $%04X: $%02X (readRAM=%s, PC=%04X, ram$D000=%02X)\n", addr, b, (readRAM ? "T" : "F"), mainCPU.pc, ram[0xC000]);
819 APPENDIX F Assembly Language Program Listings
825 5 * ;ADDRESSES FOR FIRST 6522
826 6 ORB EQU $C400 ;PORT B
827 7 ORA EQU $C401 ;PORT A
828 8 DDRB EQU $C402 ;DATA DIRECTION REGISTER (A)
829 9 DDRA EQU $C403 ;DATA DIRECTION REGISTER (B)
830 10 * ;ADDRESSES FOR SECOND 6522
831 11 ORB2 EQU $C480 ;PORT B
832 12 ORA2 EQU $C481 ;PORT A
833 13 DDRB2 EQU $C482 ;DATA DIRECTION REGISTER (B)
834 14 DDRA2 EQU $C483 ;DATA DIRECTION REGISTER (A)
836 void WrMem(uint16_t addr, uint8_t b)
839 //extern V6809REGS regs;
840 //if (addr >= 0xC800 && addr <= 0xCBFE)
841 //if (addr == 0xC80F || addr == 0xC80D)
842 // WriteLog("WrMem: Writing address %04X with %02X [PC=%04X, $CB00=%02X]\n", addr, b, regs.pc, gram[0xCB00]);//*/
845 if (addr >= 0xC000 && addr <= 0xC0FF)
846 WriteLog("\n*** Write at I/O address %04X\n", addr);
849 Check the BIKO version on Asimov to see if it's been cracked or not...
851 7F3D: 29 07 AND #$07 [PC=7F3F, SP=01EA, CC=---B-I--, A=01, X=4B, Y=00]
852 7F3F: C9 06 CMP #$06 [PC=7F41, SP=01EA, CC=N--B-I--, A=01, X=4B, Y=00]
853 7F41: 90 03 BCC $7F46 [PC=7F46, SP=01EA, CC=N--B-I--, A=01, X=4B, Y=00]
854 [7F43: 4C 83 7E JMP $7E83] <- Skipped over... (Prints "THANK YOU VERY MUCH!")
855 7F46: AA TAX [PC=7F47, SP=01EA, CC=---B-I--, A=01, X=01, Y=00]
857 ; INX here *ensures* 1 - 6!!! BUG!!!
858 ; Or is it? Could this be part of a braindead copy protection scheme? It's
859 ; awfully close to NOP ($EA)...
860 ; Nothing else touches it once it's been written... Hmm...
862 7F47: E8 INX [PC=7F48, SP=01EA, CC=---B-I--, A=01, X=02, Y=00]
863 7F48: F8 SED [PC=7F49, SP=01EA, CC=---BDI--, A=01, X=02, Y=00]
864 7F49: 18 CLC [PC=7F4A, SP=01EA, CC=---BDI--, A=01, X=02, Y=00]
865 7F4A: BD 15 4E LDA $4E15,X [PC=7F4D, SP=01EA, CC=---BDI--, A=15, X=02, Y=00]
868 ; 4E15: 25 25 15 15 10 20
869 ; 4E1B: 03 41 99 99 01 00 12
872 7F4D: 65 FC ADC $FC [PC=7F4F, SP=01EA, CC=---BDI--, A=16, X=02, Y=00]
873 7F4F: 65 FC ADC $FC [PC=7F51, SP=01EA, CC=---BDI--, A=17, X=02, Y=00]
874 7F51: 65 FC ADC $FC [PC=7F53, SP=01EA, CC=---BDI--, A=18, X=02, Y=00]
875 7F53: 65 FC ADC $FC [PC=7F55, SP=01EA, CC=---BDI--, A=19, X=02, Y=00]
877 ; NO checking is done on the raised stat! Aarrrgggghhhhh!
879 7F55: 9D 15 4E STA $4E15,X [PC=7F58, SP=01EA, CC=---BDI--, A=19, X=02, Y=00]
880 7F58: D8 CLD [PC=7F59, SP=01EA, CC=---B-I--, A=19, X=02, Y=00]
882 ; Print "ALAKAZAM!" and so on...
884 7F59: 20 2C 40 JSR $402C [PC=402C, SP=01E8, CC=---B-I--, A=19, X=02, Y=00]
888 WriteLog("\n*** Byte %02X written at address %04X\n", b, addr);
891 I think this is IIc/IIe only...
893 CLR80STORE=$C000 ;80STORE Off- disable 80-column memory mapping (Write)
894 SET80STORE=$C001 ;80STORE On- enable 80-column memory mapping (WR-only)
896 CLRAUXRD = $C002 ;read from main 48K (WR-only)
897 SETAUXRD = $C003 ;read from aux/alt 48K (WR-only)
899 CLRAUXWR = $C004 ;write to main 48K (WR-only)
900 SETAUXWR = $C005 ;write to aux/alt 48K (WR-only)
902 CLRCXROM = $C006 ;use ROM on cards (WR-only)
903 SETCXROM = $C007 ;use internal ROM (WR-only)
905 CLRAUXZP = $C008 ;use main zero page, stack, & LC (WR-only)
906 SETAUXZP = $C009 ;use alt zero page, stack, & LC (WR-only)
908 CLRC3ROM = $C00A ;use internal Slot 3 ROM (WR-only)
909 SETC3ROM = $C00B ;use external Slot 3 ROM (WR-only)
911 CLR80VID = $C00C ;disable 80-column display mode (WR-only)
912 SET80VID = $C00D ;enable 80-column display mode (WR-only)
914 CLRALTCH = $C00E ;use main char set- norm LC, Flash UC (WR-only)
915 SETALTCH = $C00F ;use alt char set- norm inverse, LC; no Flash (WR-only)
919 #ifdef SOFT_SWITCH_DEBUGGING
920 WriteLog("80STORE off (write)\n");
924 else if (addr == 0xC001)
926 #ifdef SOFT_SWITCH_DEBUGGING
927 WriteLog("80STORE on (write)\n");
931 else if (addr == 0xC002)
933 #ifdef SOFT_SWITCH_DEBUGGING
934 WriteLog("RAMRD off (write)\n");
938 else if (addr == 0xC003)
940 #ifdef SOFT_SWITCH_DEBUGGING
941 WriteLog("RAMRD on (write)\n");
945 else if (addr == 0xC004)
947 #ifdef SOFT_SWITCH_DEBUGGING
948 WriteLog("RAMWRT off (write)\n");
952 else if (addr == 0xC005)
954 #ifdef SOFT_SWITCH_DEBUGGING
955 WriteLog("RAMWRT on (write)\n");
959 else if (addr == 0xC006)
961 // This is the only soft switch that breaks the usual convention.
962 #ifdef SOFT_SWITCH_DEBUGGING
963 WriteLog("SLOTCXROM on (write)\n");
967 else if (addr == 0xC007)
969 #ifdef SOFT_SWITCH_DEBUGGING
970 WriteLog("SLOTCXROM off (write)\n");
974 else if (addr == 0xC008)
976 #ifdef SOFT_SWITCH_DEBUGGING
977 WriteLog("ALTZP off (write)\n");
981 else if (addr == 0xC009)
983 #ifdef SOFT_SWITCH_DEBUGGING
984 WriteLog("ALTZP on (write)\n");
988 else if (addr == 0xC00A)
990 #ifdef SOFT_SWITCH_DEBUGGING
991 WriteLog("SLOTC3ROM off (write)\n");
995 else if (addr == 0xC00B)
997 #ifdef SOFT_SWITCH_DEBUGGING
998 WriteLog("SLOTC3ROM on (write)\n");
1002 else if (addr == 0xC00C)
1004 #ifdef SOFT_SWITCH_DEBUGGING
1005 WriteLog("80COL off (write)\n");
1009 else if (addr == 0xC00D)
1011 #ifdef SOFT_SWITCH_DEBUGGING
1012 WriteLog("80COL on (write)\n");
1016 else if (addr == 0xC00E)
1018 #ifdef SOFT_SWITCH_DEBUGGING
1019 WriteLog("ALTCHARSET off (write)\n");
1021 alternateCharset = false;
1023 else if (addr == 0xC00F)
1025 #ifdef SOFT_SWITCH_DEBUGGING
1026 WriteLog("ALTCHARSET on (write)\n");
1028 alternateCharset = true;
1030 else if ((addr & 0xFFF0) == 0xC010) // Keyboard strobe
1032 //Actually, according to the A2 ref, this should do nothing since a write
1033 //is immediately preceded by a read leaving it in the same state it was...
1034 //But leaving this out seems to fuck up the key handling of some games...
1037 else if (addr == 0xC050)
1039 #ifdef SOFT_SWITCH_DEBUGGING
1040 WriteLog("TEXT off (write)\n");
1044 else if (addr == 0xC051)
1046 #ifdef SOFT_SWITCH_DEBUGGING
1047 WriteLog("TEXT on (write)\n");
1051 else if (addr == 0xC052)
1053 #ifdef SOFT_SWITCH_DEBUGGING
1054 WriteLog("MIXED off (write)\n");
1058 else if (addr == 0xC053)
1060 #ifdef SOFT_SWITCH_DEBUGGING
1061 WriteLog("MIXED on (write)\n");
1065 else if (addr == 0xC054)
1067 #ifdef SOFT_SWITCH_DEBUGGING
1068 WriteLog("PAGE2 off (write)\n");
1070 displayPage2 = false;
1072 else if (addr == 0xC055)
1074 #ifdef SOFT_SWITCH_DEBUGGING
1075 WriteLog("PAGE2 on (write)\n");
1077 displayPage2 = true;
1079 else if (addr == 0xC056)
1081 #ifdef SOFT_SWITCH_DEBUGGING
1082 WriteLog("HIRES off (write)\n");
1086 else if (addr == 0xC057)
1088 #ifdef SOFT_SWITCH_DEBUGGING
1089 WriteLog("HIRES on (write)\n");
1093 else if (addr == 0xC05E)
1095 #ifdef SOFT_SWITCH_DEBUGGING
1096 WriteLog("DHIRES on (write)\n");
1101 //static int goDumpDis = 0;
1103 //if (goDumpDis == 2)
1106 else if (addr == 0xC05F)
1108 #ifdef SOFT_SWITCH_DEBUGGING
1109 WriteLog("DHIRES off (write)\n");
1114 else if (addr == 0xC07E)
1116 #ifdef SOFT_SWITCH_DEBUGGING
1117 WriteLog("IOUDIS on (write)\n");
1121 else if (addr == 0xC07F)
1123 #ifdef SOFT_SWITCH_DEBUGGING
1124 WriteLog("IOUDIS off (write)\n");
1128 else if ((addr & 0xFFFB) == 0xC080)
1131 WriteLog("LC(R): $C080 49280 OECG R Read RAM bank 2; no write\n");
1133 //$C080 49280 OECG R Read RAM bank 2; no write
1134 visibleBank = LC_BANK_2;
1138 else if ((addr & 0xFFFB) == 0xC081)
1141 WriteLog("LC(R): $C081 49281 OECG RR Read ROM; write RAM bank 2\n");
1143 //$C081 49281 ROMIN OECG RR Read ROM; write RAM bank 2
1144 visibleBank = LC_BANK_2;
1148 else if ((addr & 0xFFFB) == 0xC082)
1151 WriteLog("LC(R): $C082 49282 OECG R Read ROM; no write\n");
1153 //$C082 49282 OECG R Read ROM; no write
1154 visibleBank = LC_BANK_2;
1158 else if ((addr & 0xFFFB) == 0xC083)
1161 WriteLog("LC(R): $C083 49283 OECG RR Read/Write RAM bank 2\n");
1163 //$C083 49283 LCBANK2 OECG RR Read/write RAM bank 2
1164 visibleBank = LC_BANK_2;
1168 else if ((addr & 0xFFFB) == 0xC088)
1171 WriteLog("LC(R): $C088 49288 OECG R Read RAM bank 1; no write\n");
1173 //$C088 49288 OECG R Read RAM bank 1; no write
1174 visibleBank = LC_BANK_1;
1178 else if ((addr & 0xFFFB) == 0xC089)
1181 WriteLog("LC(R): $C089 49289 OECG RR Read ROM; write RAM bank 1\n");
1183 //$C089 49289 OECG RR Read ROM; write RAM bank 1
1184 visibleBank = LC_BANK_1;
1188 else if ((addr & 0xFFFB) == 0xC08A)
1191 WriteLog("LC(R): $C08A 49290 OECG R Read ROM; no write\n");
1193 //$C08A 49290 OECG R Read ROM; no write
1194 visibleBank = LC_BANK_1;
1198 else if ((addr & 0xFFFB) == 0xC08B)
1201 WriteLog("LC(R): $C08B 49291 OECG RR Read/Write RAM bank 1\n");
1203 //$C08B 49291 OECG RR Read/write RAM bank 1
1204 visibleBank = LC_BANK_1;
1208 //This is determined by which slot it is in--this assumes slot 6. !!! FIX !!!
1209 else if ((addr & 0xFFF8) == 0xC0E0)
1211 floppyDrive.ControlStepper(addr & 0x07);
1213 else if ((addr & 0xFFFE) == 0xC0E8)
1215 floppyDrive.ControlMotor(addr & 0x01);
1217 else if ((addr & 0xFFFE) == 0xC0EA)
1219 floppyDrive.DriveEnable(addr & 0x01);
1221 else if (addr == 0xC0EC)
1223 //change this to Write()? (and the other to Read()?) Dunno. Seems to work OK, but still...
1225 floppyDrive.ReadWrite();
1227 else if (addr == 0xC0ED)
1229 floppyDrive.SetLatchValue(b);
1231 else if (addr == 0xC0EE)
1233 floppyDrive.SetReadMode();
1235 else if (addr == 0xC0EF)
1237 floppyDrive.SetWriteMode();
1239 //Still need to add missing I/O switches here...
1241 //DEEE: BD 10 BF LDA $BF10,X [PC=DEF1, SP=01F4, CC=--.B-IZ-, A=00, X=0C, Y=07]
1243 if (addr >= 0xD000 && addr <= 0xD00F)
1245 WriteLog("*** Write to $%04X: $%02X (writeRAM=%s, PC=%04X, ram$D000=%02X)\n", addr, b, (writeRAM ? "T" : "F"), mainCPU.pc, ram[0xC000]);
1248 if (addr >= 0xC000 && addr <= 0xCFFF)
1249 return; // Protect LC bank #1 from being written to!
1256 if (addr <= 0xDFFF && visibleBank == LC_BANK_1)
1257 ram[addr - 0x1000] = b;
1261 if (addr <= 0xDFFF && visibleBank == LC_BANK_1)
1264 ram2[addr - 0x1000] = b;
1266 ram[addr - 0x1000] = b;
1281 // Check for 80STORE mode (STORE80 takes precedence over RAMRD/WRT)...
1282 if ((((addr >= 0x0400) && (addr <= 0x07FF)) || ((addr >= 0x2000) && (addr <= 0x3FFF))) && store80Mode)
1292 // Finally, check for auxillary/altzp write switches
1305 // if (addr < 0x0200 || addr >= 0xD000)
1324 // Load a file into RAM/ROM image space
1326 bool LoadImg(char * filename, uint8_t * ram, int size)
1328 FILE * fp = fopen(filename, "rb");
1333 fread(ram, 1, size, fp);
1340 static void SaveApple2State(const char * filename)
1345 static bool LoadApple2State(const char * filename)
1351 #ifdef CPU_CLOCK_CHECKING
1352 uint8_t counter = 0;
1353 uint32_t totalCPU = 0;
1354 uint64_t lastClock = 0;
1359 int main(int /*argc*/, char * /*argv*/[])
1361 InitLog("./apple2.log");
1363 srand(time(NULL)); // Initialize RNG
1366 //Need to bankify this stuff for the IIe emulation...
1367 memset(ram, 0, 0x10000);
1368 memset(rom, 0, 0x10000);
1369 memset(ram2, 0, 0x10000);
1371 // Set up V65C02 execution context
1372 memset(&mainCPU, 0, sizeof(V65C02REGS));
1373 mainCPU.RdMem = RdMem;
1374 mainCPU.WrMem = WrMem;
1375 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
1377 // alternateCharset = true;
1378 // if (!LoadImg(settings.BIOSPath, rom + 0xD000, 0x3000))
1379 if (!LoadImg(settings.BIOSPath, rom + 0xC000, 0x4000))
1381 WriteLog("Could not open file '%s'!\n", settings.BIOSPath);
1385 //This is now included...
1386 /* if (!LoadImg(settings.diskPath, diskRom, 0x100))
1388 WriteLog("Could not open file '%s'!\nDisk II will be unavailable!\n", settings.diskPath);
1392 //Load up disk image from config file (for now)...
1393 floppyDrive.LoadImage(settings.diskImagePath1, 0);
1394 floppyDrive.LoadImage(settings.diskImagePath2, 1);
1395 // floppyDrive.LoadImage("./disks/temp.nib", 1); // Load temp .nib file into second drive...
1397 //Kill the DOS ROM in slot 6 for now...
1399 // memcpy(rom + 0xC600, diskROM, 0x100);
1400 // memcpy(rom + 0xC700, diskROM, 0x100); // Slot 7???
1402 WriteLog("About to initialize video...\n");
1406 std::cout << "Aborting!" << std::endl;
1410 // Have to do this *after* video init but *before* sound init...!
1411 //Shouldn't be necessary since we're not doing emulation in the ISR...
1412 if (settings.autoStateSaving)
1414 // Load last state from file...
1415 if (!LoadApple2State(settings.autoStatePath))
1416 WriteLog("Unable to use Apple2 state file \"%s\"!\n", settings.autoStatePath);
1422 if (!LoadImg("./BT1_6502_RAM_SPACE.bin", ram, 0x10000))
1424 cout << "Couldn't load state file!" << endl;
1425 cout << "Aborting!!" << endl;
1430 //-- -- -- -- ----- -----
1431 //00 75 3B 53 FD 01 41 44
1433 mainCPU.cpuFlags = 0;
1439 mainCPU.pc = 0x4441;
1443 displayPage2 = false;
1450 memcpy(ram + 0xD000, ram + 0xC000, 0x1000);
1453 WriteLog("About to initialize audio...\n");
1455 //nope SDL_EnableUNICODE(1); // Needed to do key translation shit
1457 // gui = new GUI(surface); // Set up the GUI system object...
1458 // gui = new GUI(mainSurface); // Set up the GUI system object...
1459 // SDL 2... this will likely cause Apple 2 to crash
1460 // gui = new GUI(NULL); // Set up the GUI system object...
1462 gui->AddMenuTitle("Apple2");
1463 gui->AddMenuItem("Test!", TestWindow/*, hotkey*/);
1464 gui->AddMenuItem("");
1465 gui->AddMenuItem("Quit", QuitEmulator, SDLK_q);
1466 gui->CommitItemsToMenu();
1469 SetupBlurTable(); // Set up the color TV emulation blur table
1470 running = true; // Set running status...
1472 InitializeEventList(); // Clear the event list before we use it...
1473 SetCallbackTime(FrameCallback, 16666.66666667); // Set frame to fire at 1/60 s interval
1474 SetCallbackTime(BlinkTimer, 250000); // Set up blinking at 1/4 s intervals
1475 startTicks = SDL_GetTicks();
1477 #ifdef THREADED_65C02
1478 cpuCond = SDL_CreateCond();
1479 mainSem = SDL_CreateSemaphore(1);
1480 cpuThread = SDL_CreateThread(CPUThreadFunc, NULL, NULL);
1481 //Hmm... CPU does POST (+1), wait, then WAIT (-1)
1482 // SDL_sem * mainMutex = SDL_CreateMutex();
1485 WriteLog("Entering main loop...\n");
1488 double timeToNextEvent = GetTimeToNextEvent();
1489 #ifndef THREADED_65C02
1490 Execute65C02(&mainCPU, USEC_TO_M6502_CYCLES(timeToNextEvent));
1492 //We MUST remove a frame's worth of time in order for the CPU to function... !!! FIX !!!
1493 //(Fix so that this is not a requirement!)
1494 //Fixed, but mainCPU.clock is destroyed in the bargain. Oh well.
1495 // mainCPU.clock -= USEC_TO_M6502_CYCLES(timeToNextEvent);
1497 #ifdef CPU_CLOCK_CHECKING
1498 #ifndef THREADED_65C02
1499 totalCPU += USEC_TO_M6502_CYCLES(timeToNextEvent);
1502 // Handle CPU time delta for sound...
1503 //Don't need this anymore now that we use absolute time...
1504 // AddToSoundTimeBase(USEC_TO_M6502_CYCLES(timeToNextEvent));
1508 #ifdef THREADED_65C02
1509 WriteLog("Main: cpuFinished = true;\n");
1511 //#warning "If sound thread is behind, CPU thread will never wake up... !!! FIX !!!" [DONE]
1512 //What to do? How do you know when the CPU is sleeping???
1513 //USE A CONDITIONAL!!! OF COURSE!!!!!!11!11!11!!!1!
1515 SDL_mutexP(mainMutex);
1516 SDL_CondWait(mainCond, mainMutex); // Wait for CPU thread to get to signal point...
1517 SDL_mutexV(mainMutex);
1519 //Nope, use a semaphore...
1520 WriteLog("Main: SDL_SemWait(mainSem);\n");
1521 SDL_SemWait(mainSem);//should lock until CPU thread is waiting...
1524 WriteLog("Main: SDL_CondSignal(cpuCond);//thread is probably asleep, wake it up\n");
1525 SDL_CondSignal(cpuCond);//thread is probably asleep, wake it up
1526 WriteLog("Main: SDL_WaitThread(cpuThread, NULL);\n");
1527 SDL_WaitThread(cpuThread, NULL);
1528 //nowok:SDL_WaitThread(CPUThreadFunc, NULL);
1529 WriteLog("Main: SDL_DestroyCond(cpuCond);\n");
1530 SDL_DestroyCond(cpuCond);
1532 //SDL_DestroyMutex(mainMutex);
1533 SDL_DestroySemaphore(mainSem);
1536 if (settings.autoStateSaving)
1538 // Save state here...
1539 SaveApple2State(settings.autoStatePath);
1541 floppyDrive.SaveImage();
1557 -----------------------
1558 space $A0 $A0 $A0 $A0 No xlation
1559 RETURN $8D $8D $8D $8D No xlation
1560 0 $B0 $B0 $B0 $B0 Need to screen shift+0 (?)
1561 1! $B1 $B1 $A1 $A1 No xlation
1562 2" $B2 $B2 $A2 $A2 No xlation
1563 3# $B3 $B3 $A3 $A3 No xlation
1564 4$ $B4 $B4 $A4 $A4 No xlation
1565 5% $B5 $B5 $A5 $A5 No xlation
1566 6& $B6 $B6 $A6 $A6 No xlation
1567 7' $B7 $B7 $A7 $A7 No xlation
1568 8( $B8 $B8 $A8 $A8 No xlation
1569 9) $B9 $B9 $A9 $A9 No xlation
1570 :* $BA $BA $AA $AA No xlation
1571 ;+ $BB $BB $AB $AB No xlation
1572 ,< $AC $AC $BC $BC No xlation
1573 -= $AD $AD $BD $BD No xlation
1574 .> $AE $AE $BE $BE No xlation
1575 /? $AF $AF $BF $BF No xlation
1588 M $CD $8D $DD $9D -> ODD
1589 N^ $CE $8E $DE $9E -> ODD
1591 P@ $D0 $90 $C0 $80 Need to xlate CTL+SHFT+P & SHFT+P (?)
1604 ESC $9B $9B $9B $9B No xlation
1607 //static uint64_t lastCPUCycles = 0;
1608 static uint32_t frameCount = 0;
1609 static void FrameCallback(void)
1613 while (SDL_PollEvent(&event))
1618 //Need to do some key translation here, and screen out non-apple keys as well...
1619 //(really, could do it all in SDL_KEYDOWN, would just have to get symbols &
1620 // everything else done separately. this is slightly easier. :-P)
1621 // if (event.key.keysym.sym == SDLK_TAB) // Prelim key screening...
1622 if (event.edit.text[0] == '\t') // Prelim key screening...
1625 lastKeyPressed = event.edit.text[0];
1628 //kludge: should have a caps lock thingy here...
1629 //or all uppercase for ][+...
1630 // if (lastKeyPressed >= 'a' && lastKeyPressed <='z')
1631 // lastKeyPressed &= 0xDF; // Convert to upper case...
1635 // CTRL+RESET key emulation (mapped to CTRL+`)
1636 // This doesn't work...
1637 // if (event.key.keysym.sym == SDLK_BREAK && (event.key.keysym.mod & KMOD_CTRL))
1638 // if (event.key.keysym.sym == SDLK_PAUSE && (event.key.keysym.mod & KMOD_CTRL))
1639 if (event.key.keysym.sym == SDLK_BACKQUOTE && (event.key.keysym.mod & KMOD_CTRL))
1640 //NOTE that this shouldn't take place until the key is lifted... !!! FIX !!!
1641 //ALSO it seems to leave the machine in an inconsistent state vis-a-vis the language card...
1642 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
1644 if (event.key.keysym.sym == SDLK_RIGHT)
1645 lastKeyPressed = 0x15, keyDown = true;
1646 else if (event.key.keysym.sym == SDLK_LEFT)
1647 lastKeyPressed = 0x08, keyDown = true;
1648 else if (event.key.keysym.sym == SDLK_UP)
1649 lastKeyPressed = 0x0B, keyDown = true;
1650 else if (event.key.keysym.sym == SDLK_DOWN)
1651 lastKeyPressed = 0x0A, keyDown = true;
1652 else if (event.key.keysym.sym == SDLK_RETURN)
1653 lastKeyPressed = 0x0D, keyDown = true;
1654 else if (event.key.keysym.sym == SDLK_ESCAPE)
1655 lastKeyPressed = 0x1B, keyDown = true;
1656 else if (event.key.keysym.sym == SDLK_BACKSPACE)
1657 lastKeyPressed = 0x7F, keyDown = true;
1659 // Fix CTRL+key combo...
1660 if (event.key.keysym.mod & KMOD_CTRL)
1662 if (event.key.keysym.sym >= SDLK_a && event.key.keysym.sym <= SDLK_z)
1664 lastKeyPressed = (event.key.keysym.sym - SDLK_a) + 1;
1666 //printf("Key combo pressed: CTRL+%c\n", lastKeyPressed + 0x40);
1670 // Use ALT+Q to exit, as well as the usual window decoration method
1671 if (event.key.keysym.sym == SDLK_q && (event.key.keysym.mod & KMOD_ALT))
1674 // Paddle buttons 0 & 1
1675 if (event.key.keysym.sym == SDLK_INSERT)
1676 openAppleDown = true;
1677 if (event.key.keysym.sym == SDLK_PAGEUP)
1678 closedAppleDown = true;
1680 if (event.key.keysym.sym == SDLK_F11)
1681 dumpDis = !dumpDis; // Toggle the disassembly process
1682 // else if (event.key.keysym.sym == SDLK_F11)
1683 // floppyDrive.LoadImage("./disks/bt1_char.dsk");//Kludge to load char disk...
1684 else if (event.key.keysym.sym == SDLK_F9)
1686 floppyDrive.CreateBlankImage();
1687 // SpawnMessage("Image cleared...");
1689 else if (event.key.keysym.sym == SDLK_F10)
1691 floppyDrive.SwapImages();
1692 // SpawnMessage("Image swapped...");
1695 if (event.key.keysym.sym == SDLK_F2)// Toggle the palette
1697 else if (event.key.keysym.sym == SDLK_F3)// Cycle through screen types
1700 // if (event.key.keysym.sym == SDLK_F5) // Temp GUI launch key
1701 if (event.key.keysym.sym == SDLK_F1) // GUI launch key
1702 //NOTE: Should parse the output to determine whether or not the user requested
1703 // to quit completely... !!! FIX !!!
1706 if (event.key.keysym.sym == SDLK_F5)
1709 char volStr[19] = "[****************]";
1710 // volStr[GetVolume()] = 0;
1711 for(int i=GetVolume(); i<16; i++)
1712 volStr[1 + i] = '-';
1713 SpawnMessage("Volume: %s", volStr);
1715 else if (event.key.keysym.sym == SDLK_F6)
1718 char volStr[19] = "[****************]";
1719 // volStr[GetVolume()] = 0;
1720 for(int i=GetVolume(); i<16; i++)
1721 volStr[1 + i] = '-';
1722 SpawnMessage("Volume: %s", volStr);
1725 static bool fullscreenDebounce = false;
1727 if (event.key.keysym.sym == SDLK_F12)
1729 if (!fullscreenDebounce)
1732 fullscreenDebounce = true;
1739 if (event.key.keysym.sym == SDLK_F12)
1740 fullscreenDebounce = false;
1742 // Paddle buttons 0 & 1
1743 if (event.key.keysym.sym == SDLK_INSERT)
1744 openAppleDown = false;
1745 if (event.key.keysym.sym == SDLK_PAGEUP)
1746 closedAppleDown = false;
1748 // if (event.key.keysym.sym >= SDLK_a && event.key.keysym.sym <= SDLK_z)
1757 //#warning "!!! Taking MAJOR time hit with the video frame rendering !!!"
1759 SetCallbackTime(FrameCallback, 16666.66666667);
1761 #ifdef CPU_CLOCK_CHECKING
1762 //We know it's stopped, so we can get away with this...
1766 uint64_t clock = GetCurrentV65C02Clock();
1767 //totalCPU += (uint32_t)(clock - lastClock);
1769 printf("Executed %u cycles...\n", (uint32_t)(clock - lastClock));
1775 //Instead of this, we should yield remaining time to other processes... !!! FIX !!! [DONE]
1778 //Actually, slows things down too much...
1780 // while (SDL_GetTicks() - startTicks < 16); // Wait for next frame...
1782 // This is the problem: If you set the interval to 16, it runs faster than
1783 // 1/60s per frame. If you set it to 17, it runs slower. What we need is to
1784 // have it do 16 for one frame, then 17 for two others. Then it should average
1785 // out to 1/60s per frame every 3 frames.
1786 frameCount = (frameCount + 1) % 3;
1788 uint32_t waitFrameTime = 17 - (frameCount == 0 ? 1 : 0);
1790 while (SDL_GetTicks() - startTicks < waitFrameTime)
1791 SDL_Delay(1); // Wait for next frame...
1793 startTicks = SDL_GetTicks();
1795 uint64_t cpuCycles = GetCurrentV65C02Clock();
1796 uint32_t cyclesBurned = (uint32_t)(cpuCycles - lastCPUCycles);
1797 WriteLog("FrameCallback: used %i cycles\n", cyclesBurned);
1798 lastCPUCycles = cpuCycles;
1801 //let's wait, then signal...
1802 //works longer, but then still falls behind...
1803 #ifdef THREADED_65C02
1804 SDL_CondSignal(cpuCond);//OK, let the CPU go another frame...
1809 static void BlinkTimer(void)
1812 SetCallbackTime(BlinkTimer, 250000); // Set up blinking at 1/4 sec intervals
1817 Next problem is this: How to have events occur and synchronize with the rest
1820 o Have the CPU thread manage the timer mechanism? (need to have a method of carrying
1821 remainder CPU cycles over...)
1823 One way would be to use a fractional accumulator, then subtract 1 every
1824 time it overflows. Like so:
1826 double overflow = 0;
1830 Execute6808(&soundCPU, time);
1831 overflow += 0.289115646;