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 = (ramrd ? ram2[addr - 0x1000] : ram[addr - 0x1000]);
757 WriteLog("b is from LC bank #1 (ram[addr - 0x1000])...\n");
765 // b = (ramrd ? 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");
786 // Check for 80STORE mode (STORE80 takes precedence over RAMRD/WRT)...
787 if ((((addr >= 0x0400) && (addr <= 0x07FF)) || ((addr >= 0x2000) && (addr <= 0x3FFF))) && store80Mode)
797 // Finally, check for auxillary/altzp write switches
800 b = (altzp ? ram2[addr] : ram[addr]);
802 b = (ramrd ? ram2[addr] : ram[addr]);
814 WriteLog("b is from ram[addr]...\n");
819 if (addr >= 0xD000 && addr <= 0xD00F)
821 WriteLog("*** Read from $%04X: $%02X (readRAM=%s, PC=%04X, ram$D000=%02X)\n", addr, b, (readRAM ? "T" : "F"), mainCPU.pc, ram[0xC000]);
830 APPENDIX F Assembly Language Program Listings
836 5 * ;ADDRESSES FOR FIRST 6522
837 6 ORB EQU $C400 ;PORT B
838 7 ORA EQU $C401 ;PORT A
839 8 DDRB EQU $C402 ;DATA DIRECTION REGISTER (A)
840 9 DDRA EQU $C403 ;DATA DIRECTION REGISTER (B)
841 10 * ;ADDRESSES FOR SECOND 6522
842 11 ORB2 EQU $C480 ;PORT B
843 12 ORA2 EQU $C481 ;PORT A
844 13 DDRB2 EQU $C482 ;DATA DIRECTION REGISTER (B)
845 14 DDRA2 EQU $C483 ;DATA DIRECTION REGISTER (A)
847 void WrMem(uint16_t addr, uint8_t b)
850 //extern V6809REGS regs;
851 //if (addr >= 0xC800 && addr <= 0xCBFE)
852 //if (addr == 0xC80F || addr == 0xC80D)
853 // WriteLog("WrMem: Writing address %04X with %02X [PC=%04X, $CB00=%02X]\n", addr, b, regs.pc, gram[0xCB00]);//*/
856 if (addr >= 0xC000 && addr <= 0xC0FF)
857 WriteLog("\n*** Write at I/O address %04X\n", addr);
860 Check the BIKO version on Asimov to see if it's been cracked or not...
862 7F3D: 29 07 AND #$07 [PC=7F3F, SP=01EA, CC=---B-I--, A=01, X=4B, Y=00]
863 7F3F: C9 06 CMP #$06 [PC=7F41, SP=01EA, CC=N--B-I--, A=01, X=4B, Y=00]
864 7F41: 90 03 BCC $7F46 [PC=7F46, SP=01EA, CC=N--B-I--, A=01, X=4B, Y=00]
865 [7F43: 4C 83 7E JMP $7E83] <- Skipped over... (Prints "THANK YOU VERY MUCH!")
866 7F46: AA TAX [PC=7F47, SP=01EA, CC=---B-I--, A=01, X=01, Y=00]
868 ; INX here *ensures* 1 - 6!!! BUG!!!
869 ; Or is it? Could this be part of a braindead copy protection scheme? It's
870 ; awfully close to NOP ($EA)...
871 ; Nothing else touches it once it's been written... Hmm...
873 7F47: E8 INX [PC=7F48, SP=01EA, CC=---B-I--, A=01, X=02, Y=00]
874 7F48: F8 SED [PC=7F49, SP=01EA, CC=---BDI--, A=01, X=02, Y=00]
875 7F49: 18 CLC [PC=7F4A, SP=01EA, CC=---BDI--, A=01, X=02, Y=00]
876 7F4A: BD 15 4E LDA $4E15,X [PC=7F4D, SP=01EA, CC=---BDI--, A=15, X=02, Y=00]
879 ; 4E15: 25 25 15 15 10 20
880 ; 4E1B: 03 41 99 99 01 00 12
883 7F4D: 65 FC ADC $FC [PC=7F4F, SP=01EA, CC=---BDI--, A=16, X=02, Y=00]
884 7F4F: 65 FC ADC $FC [PC=7F51, SP=01EA, CC=---BDI--, A=17, X=02, Y=00]
885 7F51: 65 FC ADC $FC [PC=7F53, SP=01EA, CC=---BDI--, A=18, X=02, Y=00]
886 7F53: 65 FC ADC $FC [PC=7F55, SP=01EA, CC=---BDI--, A=19, X=02, Y=00]
888 ; NO checking is done on the raised stat! Aarrrgggghhhhh!
890 7F55: 9D 15 4E STA $4E15,X [PC=7F58, SP=01EA, CC=---BDI--, A=19, X=02, Y=00]
891 7F58: D8 CLD [PC=7F59, SP=01EA, CC=---B-I--, A=19, X=02, Y=00]
893 ; Print "ALAKAZAM!" and so on...
895 7F59: 20 2C 40 JSR $402C [PC=402C, SP=01E8, CC=---B-I--, A=19, X=02, Y=00]
899 WriteLog("\n*** Byte %02X written at address %04X\n", b, addr);
902 I think this is IIc/IIe only...
904 CLR80STORE=$C000 ;80STORE Off- disable 80-column memory mapping (Write)
905 SET80STORE=$C001 ;80STORE On- enable 80-column memory mapping (WR-only)
907 CLRAUXRD = $C002 ;read from main 48K (WR-only)
908 SETAUXRD = $C003 ;read from aux/alt 48K (WR-only)
910 CLRAUXWR = $C004 ;write to main 48K (WR-only)
911 SETAUXWR = $C005 ;write to aux/alt 48K (WR-only)
913 CLRCXROM = $C006 ;use ROM on cards (WR-only)
914 SETCXROM = $C007 ;use internal ROM (WR-only)
916 CLRAUXZP = $C008 ;use main zero page, stack, & LC (WR-only)
917 SETAUXZP = $C009 ;use alt zero page, stack, & LC (WR-only)
919 CLRC3ROM = $C00A ;use internal Slot 3 ROM (WR-only)
920 SETC3ROM = $C00B ;use external Slot 3 ROM (WR-only)
922 CLR80VID = $C00C ;disable 80-column display mode (WR-only)
923 SET80VID = $C00D ;enable 80-column display mode (WR-only)
925 CLRALTCH = $C00E ;use main char set- norm LC, Flash UC (WR-only)
926 SETALTCH = $C00F ;use alt char set- norm inverse, LC; no Flash (WR-only)
930 #ifdef SOFT_SWITCH_DEBUGGING
931 WriteLog("80STORE off (write)\n");
935 else if (addr == 0xC001)
937 #ifdef SOFT_SWITCH_DEBUGGING
938 WriteLog("80STORE on (write)\n");
942 else if (addr == 0xC002)
944 #ifdef SOFT_SWITCH_DEBUGGING
945 WriteLog("RAMRD off (write)\n");
949 else if (addr == 0xC003)
951 #ifdef SOFT_SWITCH_DEBUGGING
952 WriteLog("RAMRD on (write)\n");
956 else if (addr == 0xC004)
958 #ifdef SOFT_SWITCH_DEBUGGING
959 WriteLog("RAMWRT off (write)\n");
963 else if (addr == 0xC005)
965 #ifdef SOFT_SWITCH_DEBUGGING
966 WriteLog("RAMWRT on (write)\n");
970 else if (addr == 0xC006)
972 // This is the only soft switch that breaks the usual convention.
973 #ifdef SOFT_SWITCH_DEBUGGING
974 WriteLog("SLOTCXROM on (write)\n");
978 else if (addr == 0xC007)
980 #ifdef SOFT_SWITCH_DEBUGGING
981 WriteLog("SLOTCXROM off (write)\n");
985 else if (addr == 0xC008)
987 #ifdef SOFT_SWITCH_DEBUGGING
988 WriteLog("ALTZP off (write)\n");
992 else if (addr == 0xC009)
994 #ifdef SOFT_SWITCH_DEBUGGING
995 WriteLog("ALTZP on (write)\n");
999 else if (addr == 0xC00A)
1001 #ifdef SOFT_SWITCH_DEBUGGING
1002 WriteLog("SLOTC3ROM off (write)\n");
1006 else if (addr == 0xC00B)
1008 #ifdef SOFT_SWITCH_DEBUGGING
1009 WriteLog("SLOTC3ROM on (write)\n");
1013 else if (addr == 0xC00C)
1015 #ifdef SOFT_SWITCH_DEBUGGING
1016 WriteLog("80COL off (write)\n");
1020 else if (addr == 0xC00D)
1022 #ifdef SOFT_SWITCH_DEBUGGING
1023 WriteLog("80COL on (write)\n");
1027 else if (addr == 0xC00E)
1029 #ifdef SOFT_SWITCH_DEBUGGING
1030 WriteLog("ALTCHARSET off (write)\n");
1032 alternateCharset = false;
1034 else if (addr == 0xC00F)
1036 #ifdef SOFT_SWITCH_DEBUGGING
1037 WriteLog("ALTCHARSET on (write)\n");
1039 alternateCharset = true;
1041 else if ((addr & 0xFFF0) == 0xC010) // Keyboard strobe
1043 //Actually, according to the A2 ref, this should do nothing since a write
1044 //is immediately preceded by a read leaving it in the same state it was...
1045 //But leaving this out seems to fuck up the key handling of some games...
1048 else if (addr == 0xC050)
1050 #ifdef SOFT_SWITCH_DEBUGGING
1051 WriteLog("TEXT off (write)\n");
1055 else if (addr == 0xC051)
1057 #ifdef SOFT_SWITCH_DEBUGGING
1058 WriteLog("TEXT on (write)\n");
1062 else if (addr == 0xC052)
1064 #ifdef SOFT_SWITCH_DEBUGGING
1065 WriteLog("MIXED off (write)\n");
1069 else if (addr == 0xC053)
1071 #ifdef SOFT_SWITCH_DEBUGGING
1072 WriteLog("MIXED on (write)\n");
1076 else if (addr == 0xC054)
1078 #ifdef SOFT_SWITCH_DEBUGGING
1079 WriteLog("PAGE2 off (write)\n");
1081 displayPage2 = false;
1083 else if (addr == 0xC055)
1085 #ifdef SOFT_SWITCH_DEBUGGING
1086 WriteLog("PAGE2 on (write)\n");
1088 displayPage2 = true;
1090 else if (addr == 0xC056)
1092 #ifdef SOFT_SWITCH_DEBUGGING
1093 WriteLog("HIRES off (write)\n");
1097 else if (addr == 0xC057)
1099 #ifdef SOFT_SWITCH_DEBUGGING
1100 WriteLog("HIRES on (write)\n");
1104 else if (addr == 0xC05E)
1106 #ifdef SOFT_SWITCH_DEBUGGING
1107 WriteLog("DHIRES on (write)\n");
1112 //static int goDumpDis = 0;
1114 //if (goDumpDis == 2)
1117 else if (addr == 0xC05F)
1119 #ifdef SOFT_SWITCH_DEBUGGING
1120 WriteLog("DHIRES off (write)\n");
1125 else if (addr == 0xC07E)
1127 #ifdef SOFT_SWITCH_DEBUGGING
1128 WriteLog("IOUDIS on (write)\n");
1132 else if (addr == 0xC07F)
1134 #ifdef SOFT_SWITCH_DEBUGGING
1135 WriteLog("IOUDIS off (write)\n");
1139 else if ((addr & 0xFFFB) == 0xC080)
1142 WriteLog("LC(R): $C080 49280 OECG R Read RAM bank 2; no write\n");
1144 //$C080 49280 OECG R Read RAM bank 2; no write
1145 visibleBank = LC_BANK_2;
1149 else if ((addr & 0xFFFB) == 0xC081)
1152 WriteLog("LC(R): $C081 49281 OECG RR Read ROM; write RAM bank 2\n");
1154 //$C081 49281 ROMIN OECG RR Read ROM; write RAM bank 2
1155 visibleBank = LC_BANK_2;
1159 else if ((addr & 0xFFFB) == 0xC082)
1162 WriteLog("LC(R): $C082 49282 OECG R Read ROM; no write\n");
1164 //$C082 49282 OECG R Read ROM; no write
1165 visibleBank = LC_BANK_2;
1169 else if ((addr & 0xFFFB) == 0xC083)
1172 WriteLog("LC(R): $C083 49283 OECG RR Read/Write RAM bank 2\n");
1174 //$C083 49283 LCBANK2 OECG RR Read/write RAM bank 2
1175 visibleBank = LC_BANK_2;
1179 else if ((addr & 0xFFFB) == 0xC088)
1182 WriteLog("LC(R): $C088 49288 OECG R Read RAM bank 1; no write\n");
1184 //$C088 49288 OECG R Read RAM bank 1; no write
1185 visibleBank = LC_BANK_1;
1189 else if ((addr & 0xFFFB) == 0xC089)
1192 WriteLog("LC(R): $C089 49289 OECG RR Read ROM; write RAM bank 1\n");
1194 //$C089 49289 OECG RR Read ROM; write RAM bank 1
1195 visibleBank = LC_BANK_1;
1199 else if ((addr & 0xFFFB) == 0xC08A)
1202 WriteLog("LC(R): $C08A 49290 OECG R Read ROM; no write\n");
1204 //$C08A 49290 OECG R Read ROM; no write
1205 visibleBank = LC_BANK_1;
1209 else if ((addr & 0xFFFB) == 0xC08B)
1212 WriteLog("LC(R): $C08B 49291 OECG RR Read/Write RAM bank 1\n");
1214 //$C08B 49291 OECG RR Read/write RAM bank 1
1215 visibleBank = LC_BANK_1;
1219 //This is determined by which slot it is in--this assumes slot 6. !!! FIX !!!
1220 else if ((addr & 0xFFF8) == 0xC0E0)
1222 floppyDrive.ControlStepper(addr & 0x07);
1224 else if ((addr & 0xFFFE) == 0xC0E8)
1226 floppyDrive.ControlMotor(addr & 0x01);
1228 else if ((addr & 0xFFFE) == 0xC0EA)
1230 floppyDrive.DriveEnable(addr & 0x01);
1232 else if (addr == 0xC0EC)
1234 //change this to Write()? (and the other to Read()?) Dunno. Seems to work OK, but still...
1236 floppyDrive.ReadWrite();
1238 else if (addr == 0xC0ED)
1240 floppyDrive.SetLatchValue(b);
1242 else if (addr == 0xC0EE)
1244 floppyDrive.SetReadMode();
1246 else if (addr == 0xC0EF)
1248 floppyDrive.SetWriteMode();
1250 //Still need to add missing I/O switches here...
1252 //DEEE: BD 10 BF LDA $BF10,X [PC=DEF1, SP=01F4, CC=--.B-IZ-, A=00, X=0C, Y=07]
1254 if (addr >= 0xD000 && addr <= 0xD00F)
1256 WriteLog("*** Write to $%04X: $%02X (writeRAM=%s, PC=%04X, ram$D000=%02X)\n", addr, b, (writeRAM ? "T" : "F"), mainCPU.pc, ram[0xC000]);
1259 if (addr >= 0xC000 && addr <= 0xCFFF)
1260 return; // Protect LC bank #1 from being written to!
1267 if (addr <= 0xDFFF && visibleBank == LC_BANK_1)
1268 ram[addr - 0x1000] = b;
1272 if (addr <= 0xDFFF && visibleBank == LC_BANK_1)
1275 ram2[addr - 0x1000] = b;
1277 ram[addr - 0x1000] = b;
1292 // Check for 80STORE mode (STORE80 takes precedence over RAMRD/WRT)...
1293 if ((((addr >= 0x0400) && (addr <= 0x07FF)) || ((addr >= 0x2000) && (addr <= 0x3FFF))) && store80Mode)
1303 // Finally, check for auxillary/altzp write switches
1334 // Load a file into RAM/ROM image space
1336 bool LoadImg(char * filename, uint8_t * ram, int size)
1338 FILE * fp = fopen(filename, "rb");
1343 fread(ram, 1, size, fp);
1350 static void SaveApple2State(const char * filename)
1355 static bool LoadApple2State(const char * filename)
1361 #ifdef CPU_CLOCK_CHECKING
1362 uint8_t counter = 0;
1363 uint32_t totalCPU = 0;
1364 uint64_t lastClock = 0;
1369 int main(int /*argc*/, char * /*argv*/[])
1371 InitLog("./apple2.log");
1373 srand(time(NULL)); // Initialize RNG
1376 //Need to bankify this stuff for the IIe emulation...
1377 memset(ram, 0, 0x10000);
1378 memset(rom, 0, 0x10000);
1379 memset(ram2, 0, 0x10000);
1381 // Set up V65C02 execution context
1382 memset(&mainCPU, 0, sizeof(V65C02REGS));
1383 mainCPU.RdMem = RdMem;
1384 mainCPU.WrMem = WrMem;
1385 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
1387 // alternateCharset = true;
1388 // if (!LoadImg(settings.BIOSPath, rom + 0xD000, 0x3000))
1389 if (!LoadImg(settings.BIOSPath, rom + 0xC000, 0x4000))
1391 WriteLog("Could not open file '%s'!\n", settings.BIOSPath);
1395 //This is now included...
1396 /* if (!LoadImg(settings.diskPath, diskRom, 0x100))
1398 WriteLog("Could not open file '%s'!\nDisk II will be unavailable!\n", settings.diskPath);
1402 //Load up disk image from config file (for now)...
1403 floppyDrive.LoadImage(settings.diskImagePath1, 0);
1404 floppyDrive.LoadImage(settings.diskImagePath2, 1);
1405 // floppyDrive.LoadImage("./disks/temp.nib", 1); // Load temp .nib file into second drive...
1407 //Kill the DOS ROM in slot 6 for now...
1409 // memcpy(rom + 0xC600, diskROM, 0x100);
1410 // memcpy(rom + 0xC700, diskROM, 0x100); // Slot 7???
1412 WriteLog("About to initialize video...\n");
1416 std::cout << "Aborting!" << std::endl;
1420 // Have to do this *after* video init but *before* sound init...!
1421 //Shouldn't be necessary since we're not doing emulation in the ISR...
1422 if (settings.autoStateSaving)
1424 // Load last state from file...
1425 if (!LoadApple2State(settings.autoStatePath))
1426 WriteLog("Unable to use Apple2 state file \"%s\"!\n", settings.autoStatePath);
1432 if (!LoadImg("./BT1_6502_RAM_SPACE.bin", ram, 0x10000))
1434 cout << "Couldn't load state file!" << endl;
1435 cout << "Aborting!!" << endl;
1440 //-- -- -- -- ----- -----
1441 //00 75 3B 53 FD 01 41 44
1443 mainCPU.cpuFlags = 0;
1449 mainCPU.pc = 0x4441;
1453 displayPage2 = false;
1460 memcpy(ram + 0xD000, ram + 0xC000, 0x1000);
1463 WriteLog("About to initialize audio...\n");
1465 //nope SDL_EnableUNICODE(1); // Needed to do key translation shit
1467 // gui = new GUI(surface); // Set up the GUI system object...
1468 // gui = new GUI(mainSurface); // Set up the GUI system object...
1469 // SDL 2... this will likely cause Apple 2 to crash
1470 // gui = new GUI(NULL); // Set up the GUI system object...
1472 gui->AddMenuTitle("Apple2");
1473 gui->AddMenuItem("Test!", TestWindow/*, hotkey*/);
1474 gui->AddMenuItem("");
1475 gui->AddMenuItem("Quit", QuitEmulator, SDLK_q);
1476 gui->CommitItemsToMenu();
1479 SetupBlurTable(); // Set up the color TV emulation blur table
1480 running = true; // Set running status...
1482 InitializeEventList(); // Clear the event list before we use it...
1483 SetCallbackTime(FrameCallback, 16666.66666667); // Set frame to fire at 1/60 s interval
1484 SetCallbackTime(BlinkTimer, 250000); // Set up blinking at 1/4 s intervals
1485 startTicks = SDL_GetTicks();
1487 #ifdef THREADED_65C02
1488 cpuCond = SDL_CreateCond();
1489 mainSem = SDL_CreateSemaphore(1);
1490 cpuThread = SDL_CreateThread(CPUThreadFunc, NULL, NULL);
1491 //Hmm... CPU does POST (+1), wait, then WAIT (-1)
1492 // SDL_sem * mainMutex = SDL_CreateMutex();
1495 WriteLog("Entering main loop...\n");
1498 double timeToNextEvent = GetTimeToNextEvent();
1499 #ifndef THREADED_65C02
1500 Execute65C02(&mainCPU, USEC_TO_M6502_CYCLES(timeToNextEvent));
1502 //We MUST remove a frame's worth of time in order for the CPU to function... !!! FIX !!!
1503 //(Fix so that this is not a requirement!)
1504 //Fixed, but mainCPU.clock is destroyed in the bargain. Oh well.
1505 // mainCPU.clock -= USEC_TO_M6502_CYCLES(timeToNextEvent);
1507 #ifdef CPU_CLOCK_CHECKING
1508 #ifndef THREADED_65C02
1509 totalCPU += USEC_TO_M6502_CYCLES(timeToNextEvent);
1512 // Handle CPU time delta for sound...
1513 //Don't need this anymore now that we use absolute time...
1514 // AddToSoundTimeBase(USEC_TO_M6502_CYCLES(timeToNextEvent));
1518 #ifdef THREADED_65C02
1519 WriteLog("Main: cpuFinished = true;\n");
1521 //#warning "If sound thread is behind, CPU thread will never wake up... !!! FIX !!!" [DONE]
1522 //What to do? How do you know when the CPU is sleeping???
1523 //USE A CONDITIONAL!!! OF COURSE!!!!!!11!11!11!!!1!
1525 SDL_mutexP(mainMutex);
1526 SDL_CondWait(mainCond, mainMutex); // Wait for CPU thread to get to signal point...
1527 SDL_mutexV(mainMutex);
1529 //Nope, use a semaphore...
1530 WriteLog("Main: SDL_SemWait(mainSem);\n");
1531 SDL_SemWait(mainSem);//should lock until CPU thread is waiting...
1534 WriteLog("Main: SDL_CondSignal(cpuCond);//thread is probably asleep, wake it up\n");
1535 SDL_CondSignal(cpuCond);//thread is probably asleep, wake it up
1536 WriteLog("Main: SDL_WaitThread(cpuThread, NULL);\n");
1537 SDL_WaitThread(cpuThread, NULL);
1538 //nowok:SDL_WaitThread(CPUThreadFunc, NULL);
1539 WriteLog("Main: SDL_DestroyCond(cpuCond);\n");
1540 SDL_DestroyCond(cpuCond);
1542 //SDL_DestroyMutex(mainMutex);
1543 SDL_DestroySemaphore(mainSem);
1546 if (settings.autoStateSaving)
1548 // Save state here...
1549 SaveApple2State(settings.autoStatePath);
1551 floppyDrive.SaveImage();
1567 -----------------------
1568 space $A0 $A0 $A0 $A0 No xlation
1569 RETURN $8D $8D $8D $8D No xlation
1570 0 $B0 $B0 $B0 $B0 Need to screen shift+0 (?)
1571 1! $B1 $B1 $A1 $A1 No xlation
1572 2" $B2 $B2 $A2 $A2 No xlation
1573 3# $B3 $B3 $A3 $A3 No xlation
1574 4$ $B4 $B4 $A4 $A4 No xlation
1575 5% $B5 $B5 $A5 $A5 No xlation
1576 6& $B6 $B6 $A6 $A6 No xlation
1577 7' $B7 $B7 $A7 $A7 No xlation
1578 8( $B8 $B8 $A8 $A8 No xlation
1579 9) $B9 $B9 $A9 $A9 No xlation
1580 :* $BA $BA $AA $AA No xlation
1581 ;+ $BB $BB $AB $AB No xlation
1582 ,< $AC $AC $BC $BC No xlation
1583 -= $AD $AD $BD $BD No xlation
1584 .> $AE $AE $BE $BE No xlation
1585 /? $AF $AF $BF $BF No xlation
1598 M $CD $8D $DD $9D -> ODD
1599 N^ $CE $8E $DE $9E -> ODD
1601 P@ $D0 $90 $C0 $80 Need to xlate CTL+SHFT+P & SHFT+P (?)
1614 ESC $9B $9B $9B $9B No xlation
1617 //static uint64_t lastCPUCycles = 0;
1618 static uint32_t frameCount = 0;
1619 static void FrameCallback(void)
1623 while (SDL_PollEvent(&event))
1628 //Need to do some key translation here, and screen out non-apple keys as well...
1629 //(really, could do it all in SDL_KEYDOWN, would just have to get symbols &
1630 // everything else done separately. this is slightly easier. :-P)
1631 // if (event.key.keysym.sym == SDLK_TAB) // Prelim key screening...
1632 if (event.edit.text[0] == '\t') // Prelim key screening...
1635 lastKeyPressed = event.edit.text[0];
1638 //kludge: should have a caps lock thingy here...
1639 //or all uppercase for ][+...
1640 // if (lastKeyPressed >= 'a' && lastKeyPressed <='z')
1641 // lastKeyPressed &= 0xDF; // Convert to upper case...
1645 // CTRL+RESET key emulation (mapped to CTRL+`)
1646 // This doesn't work...
1647 // if (event.key.keysym.sym == SDLK_BREAK && (event.key.keysym.mod & KMOD_CTRL))
1648 // if (event.key.keysym.sym == SDLK_PAUSE && (event.key.keysym.mod & KMOD_CTRL))
1649 if (event.key.keysym.sym == SDLK_BACKQUOTE && (event.key.keysym.mod & KMOD_CTRL))
1650 //NOTE that this shouldn't take place until the key is lifted... !!! FIX !!!
1651 //ALSO it seems to leave the machine in an inconsistent state vis-a-vis the language card...
1652 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
1654 if (event.key.keysym.sym == SDLK_RIGHT)
1655 lastKeyPressed = 0x15, keyDown = true;
1656 else if (event.key.keysym.sym == SDLK_LEFT)
1657 lastKeyPressed = 0x08, keyDown = true;
1658 else if (event.key.keysym.sym == SDLK_UP)
1659 lastKeyPressed = 0x0B, keyDown = true;
1660 else if (event.key.keysym.sym == SDLK_DOWN)
1661 lastKeyPressed = 0x0A, keyDown = true;
1662 else if (event.key.keysym.sym == SDLK_RETURN)
1663 lastKeyPressed = 0x0D, keyDown = true;
1664 else if (event.key.keysym.sym == SDLK_ESCAPE)
1665 lastKeyPressed = 0x1B, keyDown = true;
1666 else if (event.key.keysym.sym == SDLK_BACKSPACE)
1667 lastKeyPressed = 0x7F, keyDown = true;
1669 // Fix CTRL+key combo...
1670 if (event.key.keysym.mod & KMOD_CTRL)
1672 if (event.key.keysym.sym >= SDLK_a && event.key.keysym.sym <= SDLK_z)
1674 lastKeyPressed = (event.key.keysym.sym - SDLK_a) + 1;
1676 //printf("Key combo pressed: CTRL+%c\n", lastKeyPressed + 0x40);
1680 // Use ALT+Q to exit, as well as the usual window decoration method
1681 if (event.key.keysym.sym == SDLK_q && (event.key.keysym.mod & KMOD_ALT))
1684 // Paddle buttons 0 & 1
1685 if (event.key.keysym.sym == SDLK_INSERT)
1686 openAppleDown = true;
1687 if (event.key.keysym.sym == SDLK_PAGEUP)
1688 closedAppleDown = true;
1690 if (event.key.keysym.sym == SDLK_F11)
1691 dumpDis = !dumpDis; // Toggle the disassembly process
1692 // else if (event.key.keysym.sym == SDLK_F11)
1693 // floppyDrive.LoadImage("./disks/bt1_char.dsk");//Kludge to load char disk...
1694 else if (event.key.keysym.sym == SDLK_F9)
1696 floppyDrive.CreateBlankImage();
1697 // SpawnMessage("Image cleared...");
1699 else if (event.key.keysym.sym == SDLK_F10)
1701 floppyDrive.SwapImages();
1702 // SpawnMessage("Image swapped...");
1705 if (event.key.keysym.sym == SDLK_F2)// Toggle the palette
1707 else if (event.key.keysym.sym == SDLK_F3)// Cycle through screen types
1710 // if (event.key.keysym.sym == SDLK_F5) // Temp GUI launch key
1711 if (event.key.keysym.sym == SDLK_F1) // GUI launch key
1712 //NOTE: Should parse the output to determine whether or not the user requested
1713 // to quit completely... !!! FIX !!!
1716 if (event.key.keysym.sym == SDLK_F5)
1719 char volStr[19] = "[****************]";
1720 // volStr[GetVolume()] = 0;
1721 for(int i=GetVolume(); i<16; i++)
1722 volStr[1 + i] = '-';
1723 SpawnMessage("Volume: %s", volStr);
1725 else if (event.key.keysym.sym == SDLK_F6)
1728 char volStr[19] = "[****************]";
1729 // volStr[GetVolume()] = 0;
1730 for(int i=GetVolume(); i<16; i++)
1731 volStr[1 + i] = '-';
1732 SpawnMessage("Volume: %s", volStr);
1735 static bool fullscreenDebounce = false;
1737 if (event.key.keysym.sym == SDLK_F12)
1739 if (!fullscreenDebounce)
1742 fullscreenDebounce = true;
1749 if (event.key.keysym.sym == SDLK_F12)
1750 fullscreenDebounce = false;
1752 // Paddle buttons 0 & 1
1753 if (event.key.keysym.sym == SDLK_INSERT)
1754 openAppleDown = false;
1755 if (event.key.keysym.sym == SDLK_PAGEUP)
1756 closedAppleDown = false;
1758 // if (event.key.keysym.sym >= SDLK_a && event.key.keysym.sym <= SDLK_z)
1767 //#warning "!!! Taking MAJOR time hit with the video frame rendering !!!"
1769 SetCallbackTime(FrameCallback, 16666.66666667);
1771 #ifdef CPU_CLOCK_CHECKING
1772 //We know it's stopped, so we can get away with this...
1776 uint64_t clock = GetCurrentV65C02Clock();
1777 //totalCPU += (uint32_t)(clock - lastClock);
1779 printf("Executed %u cycles...\n", (uint32_t)(clock - lastClock));
1785 //Instead of this, we should yield remaining time to other processes... !!! FIX !!! [DONE]
1788 //Actually, slows things down too much...
1790 // while (SDL_GetTicks() - startTicks < 16); // Wait for next frame...
1792 // This is the problem: If you set the interval to 16, it runs faster than
1793 // 1/60s per frame. If you set it to 17, it runs slower. What we need is to
1794 // have it do 16 for one frame, then 17 for two others. Then it should average
1795 // out to 1/60s per frame every 3 frames.
1796 frameCount = (frameCount + 1) % 3;
1798 uint32_t waitFrameTime = 17 - (frameCount == 0 ? 1 : 0);
1800 while (SDL_GetTicks() - startTicks < waitFrameTime)
1801 SDL_Delay(1); // Wait for next frame...
1803 startTicks = SDL_GetTicks();
1805 uint64_t cpuCycles = GetCurrentV65C02Clock();
1806 uint32_t cyclesBurned = (uint32_t)(cpuCycles - lastCPUCycles);
1807 WriteLog("FrameCallback: used %i cycles\n", cyclesBurned);
1808 lastCPUCycles = cpuCycles;
1811 //let's wait, then signal...
1812 //works longer, but then still falls behind...
1813 #ifdef THREADED_65C02
1814 SDL_CondSignal(cpuCond);//OK, let the CPU go another frame...
1819 static void BlinkTimer(void)
1822 SetCallbackTime(BlinkTimer, 250000); // Set up blinking at 1/4 sec intervals
1827 Next problem is this: How to have events occur and synchronize with the rest
1830 o Have the CPU thread manage the timer mechanism? (need to have a method of carrying
1831 remainder CPU cycles over...)
1833 One way would be to use a fractional accumulator, then subtract 1 every
1834 time it overflows. Like so:
1836 double overflow = 0;
1840 Execute6808(&soundCPU, time);
1841 overflow += 0.289115646;