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
66 uint8 ram[0x10000], rom[0x10000]; // RAM & ROM spaces
68 uint8 diskRom[0x100]; // Disk ROM space
69 V65C02REGS mainCPU; // v65C02 execution context
70 uint8 appleType = APPLE_TYPE_II;
71 FloppyDrive floppyDrive;
75 static uint8 lastKeyPressed = 0;
76 static bool keyDown = false;
78 //static FloppyDrive floppyDrive;
80 enum { LC_BANK_1, LC_BANK_2 };
82 static uint8 visibleBank = LC_BANK_1;
83 static bool readRAM = false;
84 static bool writeRAM = false;
86 static bool running = true; // Machine running state flag...
87 static uint32 startTicks;
89 static GUI * gui = NULL;
91 // Local functions (technically, they're global...)
93 bool LoadImg(char * filename, uint8 * ram, int size);
94 uint8 RdMem(uint16 addr);
95 void WrMem(uint16 addr, uint8 b);
96 static void SaveApple2State(const char * filename);
97 static bool LoadApple2State(const char * filename);
99 // Local timer callback functions
101 static void FrameCallback(void);
102 static void BlinkTimer(void);
104 #ifdef THREADED_65C02
105 // Test of threaded execution of 6502
106 static SDL_Thread * cpuThread = NULL;
107 //static SDL_mutex * cpuMutex = NULL;
108 static SDL_cond * cpuCond = NULL;
109 static bool cpuFinished = false;
110 static bool cpuSleep = false;
112 // Let's try a thread...
114 Here's how it works: Execute 1 frame's worth, then sleep.
115 Other stuff wakes it up
117 int CPUThreadFunc(void * data)
119 // Mutex must be locked for conditional to work...
120 // Also, must be created in the thread that uses it...
121 SDL_mutex * cpuMutex = SDL_CreateMutex();
123 #ifdef CPU_THREAD_OVERFLOW_COMPENSATION
124 float overflow = 0.0;
130 SDL_CondWait(cpuCond, cpuMutex);
132 uint32 cycles = 17066;
133 #ifdef CPU_THREAD_OVERFLOW_COMPENSATION
134 // ODD! It's closer *without* this overflow compensation. ??? WHY ???
135 overflow += 0.666666667;
144 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!
145 SDL_mutexP(cpuMutex);
146 // Adjust the sound routine's last cycle toggled time base
147 // Also, since we're finished executing, .clock is now valid
148 AdjustLastToggleCycles(mainCPU.clock);
150 if (SDL_CondWait(cpuCond, cpuMutex) != 0)
152 printf("SDL_CondWait != 0! (Error: '%s')\n", SDL_GetError());
156 SDL_CondWait(cpuCond, cpuMutex);
158 SDL_mutexV(cpuMutex);
160 while (!cpuFinished);
168 Element * TestWindow(void)
170 Element * win = new DraggableWindow2(10, 10, 128, 128);
171 // ((DraggableWindow *)win)->AddElement(new TextEdit(4, 16, 92, 0, "u2prog.dsk", win));
176 Element * QuitEmulator(void)
185 Small Apple II memory map:
187 $C010 - Clear bit 7 of keyboard data ($C000)
188 $C030 - Toggle speaker diaphragm
190 $C054 - Select page 1
191 $C056 - Select lo-res
192 $C058 - Set annuciator-0 output to 0
193 $C05A - Set annuciator-0 output to 0
194 $C05D - Set annuciator-0 output to 1
195 $C05F - Set annuciator-0 output to 1
196 $C0E0 - Disk control stepper ($C0E0-7)
197 $C0E9 - Disk control motor (on)
198 $C0EA - Disk enable (drive 1)
200 $C0EE - Disk set read mode
204 // V65C02 read/write memory functions
207 uint8 RdMem(uint16 addr)
212 if (addr >= 0xC000 && addr <= 0xC0FF)
213 WriteLog("\n*** Read at I/O address %04X\n", addr);
216 if (addr >= 0xC080 && addr <= 0xC08F)
217 WriteLog("\n*** Read at I/O address %04X\n", addr);
220 if ((addr & 0xFFF0) == 0xC000) // Read $C000-$C00F
222 return lastKeyPressed | (keyDown ? 0x80 : 0x00);
224 else if ((addr & 0xFFF0) == 0xC010) // Read $C010-$C01F
226 //This is bogus: keyDown is set to false, so return val NEVER is set...
228 //Also, this is IIe/IIc only...!
229 uint8 retVal = lastKeyPressed | (keyDown ? 0x80 : 0x00);
233 else if ((addr & 0xFFF0) == 0xC030) // Read $C030-$C03F
236 This is problematic, mainly because the v65C02 removes actual cycles run after each call.
237 Therefore, we don't really have a reliable method of sending a timestamp to the sound routine.
240 What we need to send is a delta T value but related to the IRQ buffer routine. E.g., if the buffer
241 hasn't had any changes in it then we just fill it with the last sample value and are done. Then
242 we need to adjust our delta T accordingly. What we could do is keep a running total of time since the
243 last change and adjust it accordingly, i.e., whenever a sound IRQ happens.
246 Have deltaT somewhere. Then, whenever there's a toggle, backfill buffer with last spkr state and reset
247 deltaT to zero. In the sound IRQ, if deltaT > buffer size, then subtract buffer size from deltaT. (?)
252 ToggleSpeaker(GetCurrentV65C02Clock());
253 //should it return something else here???
256 else if (addr == 0xC050) // Read $C050
260 else if (addr == 0xC051) // Read $C051
264 else if (addr == 0xC052) // Read $C052
268 else if (addr == 0xC053) // Read $C053
272 else if (addr == 0xC054) // Read $C054
274 displayPage2 = false;
276 else if (addr == 0xC055) // Read $C055
280 else if (addr == 0xC056) // Read $C056
284 else if (addr == 0xC057) // Read $C057
289 //Note that this is a kludge: The $D000-$DFFF 4K space is shared (since $C000-$CFFF is
290 //memory mapped) between TWO banks, and that that $E000-$FFFF RAM space is a single bank.
291 //[SHOULD BE FIXED NOW]
292 //OK! This switch selects bank 2 of the 4K bank at $D000-$DFFF. One access makes it
293 //visible, two makes it R/W.
328 else if ((addr & 0xFFFB) == 0xC080)
331 WriteLog("LC(R): $C080 49280 OECG R Read RAM bank 2; no write\n");
333 //$C080 49280 OECG R Read RAM bank 2; no write
334 visibleBank = LC_BANK_2;
338 else if ((addr & 0xFFFB) == 0xC081)
341 WriteLog("LC(R): $C081 49281 OECG RR Read ROM; write RAM bank 2\n");
343 //$C081 49281 ROMIN OECG RR Read ROM; write RAM bank 2
344 visibleBank = LC_BANK_2;
348 else if ((addr & 0xFFFB) == 0xC082)
351 WriteLog("LC(R): $C082 49282 OECG R Read ROM; no write\n");
353 //$C082 49282 OECG R Read ROM; no write
354 visibleBank = LC_BANK_2;
358 else if ((addr & 0xFFFB) == 0xC083)
361 WriteLog("LC(R): $C083 49283 OECG RR Read/Write RAM bank 2\n");
363 //$C083 49283 LCBANK2 OECG RR Read/write RAM bank 2
364 visibleBank = LC_BANK_2;
368 else if ((addr & 0xFFFB) == 0xC088)
371 WriteLog("LC(R): $%04X 49288 OECG R Read RAM bank 1; no write\n", addr);
373 //$C088 49288 OECG R Read RAM bank 1; no write
374 visibleBank = LC_BANK_1;
377 //Hm. Some stuff seems to want this.
378 //nope, was looking at $C0E8... return 0xFF;
380 else if ((addr & 0xFFFB) == 0xC089)
383 WriteLog("LC(R): $C089 49289 OECG RR Read ROM; write RAM bank 1\n");
385 //$C089 49289 OECG RR Read ROM; write RAM bank 1
386 visibleBank = LC_BANK_1;
390 else if ((addr & 0xFFFB) == 0xC08A)
393 WriteLog("LC(R): $C08A 49290 OECG R Read ROM; no write\n");
395 //$C08A 49290 OECG R Read ROM; no write
396 visibleBank = LC_BANK_1;
400 else if ((addr & 0xFFFB) == 0xC08B)
403 WriteLog("LC(R): $C08B 49291 OECG RR Read/Write RAM bank 1\n");
405 //$C08B 49291 OECG RR Read/write RAM bank 1
406 visibleBank = LC_BANK_1;
410 else if ((addr & 0xFFF8) == 0xC0E0)
412 floppyDrive.ControlStepper(addr & 0x07);
414 else if ((addr & 0xFFFE) == 0xC0E8)
416 floppyDrive.ControlMotor(addr & 0x01);
418 else if ((addr & 0xFFFE) == 0xC0EA)
420 floppyDrive.DriveEnable(addr & 0x01);
422 else if (addr == 0xC0EC)
424 return floppyDrive.ReadWrite();
425 //Hm, some stuff is looking at the return value. Dunno what it *should* be...
426 // OK, it's from the ReadWrite routine...
429 else if (addr == 0xC0ED)
431 return floppyDrive.GetLatchValue();
433 else if (addr == 0xC0EE)
435 floppyDrive.SetReadMode();
437 else if (addr == 0xC0EF)
439 floppyDrive.SetWriteMode();
442 //#define LC_DEBUGGING
444 bool showpath = false;
445 if (addr >= 0xD000 && addr <= 0xD00F)
449 if (addr >= 0xC100 && addr <= 0xCFFF) // The $C000-$CFFF block is *never* RAM
456 WriteLog("b is from $C100-$CFFF block...\n");
459 else if (addr >= 0xD000)
463 if (addr <= 0xDFFF && visibleBank == LC_BANK_1)
467 b = ram[addr - 0x1000];
470 WriteLog("b is from LC bank #1 (ram[addr - 0x1000])...\n");
480 WriteLog("b is from LC bank #2 (ram[addr])...\n");
491 WriteLog("b is from LC ROM (rom[addr])...\n");
502 WriteLog("b is from ram[addr]...\n");
507 if (addr >= 0xD000 && addr <= 0xD00F)
509 WriteLog("*** Read from $%04X: $%02X (readRAM=%s, PC=%04X, ram$D000=%02X)\n", addr, b, (readRAM ? "T" : "F"), mainCPU.pc, ram[0xC000]);
518 APPENDIX F Assembly Language Program Listings
524 5 * ;ADDRESSES FOR FIRST 6522
525 6 ORB EQU $C400 ;PORT B
526 7 ORA EQU $C401 ;PORT A
527 8 DDRB EQU $C402 ;DATA DIRECTION REGISTER (A)
528 9 DDRA EQU $C403 ;DATA DIRECTION REGISTER (B)
529 10 * ;ADDRESSES FOR SECOND 6522
530 11 ORB2 EQU $C480 ;PORT B
531 12 ORA2 EQU $C481 ;PORT A
532 13 DDRB2 EQU $C482 ;DATA DIRECTION REGISTER (B)
533 14 DDRA2 EQU $C483 ;DATA DIRECTION REGISTER (A)
535 void WrMem(uint16 addr, uint8 b)
538 //extern V6809REGS regs;
539 //if (addr >= 0xC800 && addr <= 0xCBFE)
540 //if (addr == 0xC80F || addr == 0xC80D)
541 // WriteLog("WrMem: Writing address %04X with %02X [PC=%04X, $CB00=%02X]\n", addr, b, regs.pc, gram[0xCB00]);//*/
544 if (addr >= 0xC000 && addr <= 0xC0FF)
545 WriteLog("\n*** Write at I/O address %04X\n", addr);
548 Check the BIKO version on Asimov to see if it's been cracked or not...
550 7F3D: 29 07 AND #$07 [PC=7F3F, SP=01EA, CC=---B-I--, A=01, X=4B, Y=00]
551 7F3F: C9 06 CMP #$06 [PC=7F41, SP=01EA, CC=N--B-I--, A=01, X=4B, Y=00]
552 7F41: 90 03 BCC $7F46 [PC=7F46, SP=01EA, CC=N--B-I--, A=01, X=4B, Y=00]
553 [7F43: 4C 83 7E JMP $7E83] <- Skipped over... (Prints "THANK YOU VERY MUCH!")
554 7F46: AA TAX [PC=7F47, SP=01EA, CC=---B-I--, A=01, X=01, Y=00]
556 ; INX here *ensures* 1 - 6!!! BUG!!!
557 ; Or is it? Could this be part of a braindead copy protection scheme? It's
558 ; awfully close to NOP ($EA)...
559 ; Nothing else touches it once it's been written... Hmm...
561 7F47: E8 INX [PC=7F48, SP=01EA, CC=---B-I--, A=01, X=02, Y=00]
562 7F48: F8 SED [PC=7F49, SP=01EA, CC=---BDI--, A=01, X=02, Y=00]
563 7F49: 18 CLC [PC=7F4A, SP=01EA, CC=---BDI--, A=01, X=02, Y=00]
564 7F4A: BD 15 4E LDA $4E15,X [PC=7F4D, SP=01EA, CC=---BDI--, A=15, X=02, Y=00]
567 ; 4E15: 25 25 15 15 10 20
568 ; 4E1B: 03 41 99 99 01 00 12
571 7F4D: 65 FC ADC $FC [PC=7F4F, SP=01EA, CC=---BDI--, A=16, X=02, Y=00]
572 7F4F: 65 FC ADC $FC [PC=7F51, SP=01EA, CC=---BDI--, A=17, X=02, Y=00]
573 7F51: 65 FC ADC $FC [PC=7F53, SP=01EA, CC=---BDI--, A=18, X=02, Y=00]
574 7F53: 65 FC ADC $FC [PC=7F55, SP=01EA, CC=---BDI--, A=19, X=02, Y=00]
576 ; NO checking is done on the raised stat! Aarrrgggghhhhh!
578 7F55: 9D 15 4E STA $4E15,X [PC=7F58, SP=01EA, CC=---BDI--, A=19, X=02, Y=00]
579 7F58: D8 CLD [PC=7F59, SP=01EA, CC=---B-I--, A=19, X=02, Y=00]
581 ; Print "ALAKAZAM!" and so on...
583 7F59: 20 2C 40 JSR $402C [PC=402C, SP=01E8, CC=---B-I--, A=19, X=02, Y=00]
587 WriteLog("\n*** Byte %02X written at address %04X\n", b, addr);
590 I think this is IIc/IIe only...
592 CLR80STORE=$C000 ;80STORE Off- disable 80-column memory mapping (Write)
593 SET80STORE=$C001 ;80STORE On- enable 80-column memory mapping (WR-only)
595 CLRAUXRD = $C002 ;read from main 48K (WR-only)
596 SETAUXRD = $C003 ;read from aux/alt 48K (WR-only)
598 CLRAUXWR = $C004 ;write to main 48K (WR-only)
599 SETAUXWR = $C005 ;write to aux/alt 48K (WR-only)
601 CLRCXROM = $C006 ;use ROM on cards (WR-only)
602 SETCXROM = $C007 ;use internal ROM (WR-only)
604 CLRAUXZP = $C008 ;use main zero page, stack, & LC (WR-only)
605 SETAUXZP = $C009 ;use alt zero page, stack, & LC (WR-only)
607 CLRC3ROM = $C00A ;use internal Slot 3 ROM (WR-only)
608 SETC3ROM = $C00B ;use external Slot 3 ROM (WR-only)
610 CLR80VID = $C00C ;disable 80-column display mode (WR-only)
611 SET80VID = $C00D ;enable 80-column display mode (WR-only)
613 CLRALTCH = $C00E ;use main char set- norm LC, Flash UC (WR-only)
614 SETALTCH = $C00F ;use alt char set- norm inverse, LC; no Flash (WR-only)
618 alternateCharset = false;
620 else if (addr == 0xC00F)
622 alternateCharset = true;
624 else if ((addr & 0xFFF0) == 0xC010) // Keyboard strobe
626 //Actually, according to the A2 ref, this should do nothing since a write
627 //is immediately preceded by a read leaving it in the same state it was...
628 //But leaving this out seems to fuck up the key handling of some games...
631 else if (addr == 0xC050)
635 else if (addr == 0xC051)
639 else if (addr == 0xC052)
643 else if (addr == 0xC053)
647 else if (addr == 0xC054)
649 displayPage2 = false;
651 else if (addr == 0xC055)
655 else if (addr == 0xC056)
659 else if (addr == 0xC057)
663 else if ((addr & 0xFFFB) == 0xC080)
666 WriteLog("LC(R): $C080 49280 OECG R Read RAM bank 2; no write\n");
668 //$C080 49280 OECG R Read RAM bank 2; no write
669 visibleBank = LC_BANK_2;
673 else if ((addr & 0xFFFB) == 0xC081)
676 WriteLog("LC(R): $C081 49281 OECG RR Read ROM; write RAM bank 2\n");
678 //$C081 49281 ROMIN OECG RR Read ROM; write RAM bank 2
679 visibleBank = LC_BANK_2;
683 else if ((addr & 0xFFFB) == 0xC082)
686 WriteLog("LC(R): $C082 49282 OECG R Read ROM; no write\n");
688 //$C082 49282 OECG R Read ROM; no write
689 visibleBank = LC_BANK_2;
693 else if ((addr & 0xFFFB) == 0xC083)
696 WriteLog("LC(R): $C083 49283 OECG RR Read/Write RAM bank 2\n");
698 //$C083 49283 LCBANK2 OECG RR Read/write RAM bank 2
699 visibleBank = LC_BANK_2;
703 else if ((addr & 0xFFFB) == 0xC088)
706 WriteLog("LC(R): $C088 49288 OECG R Read RAM bank 1; no write\n");
708 //$C088 49288 OECG R Read RAM bank 1; no write
709 visibleBank = LC_BANK_1;
713 else if ((addr & 0xFFFB) == 0xC089)
716 WriteLog("LC(R): $C089 49289 OECG RR Read ROM; write RAM bank 1\n");
718 //$C089 49289 OECG RR Read ROM; write RAM bank 1
719 visibleBank = LC_BANK_1;
723 else if ((addr & 0xFFFB) == 0xC08A)
726 WriteLog("LC(R): $C08A 49290 OECG R Read ROM; no write\n");
728 //$C08A 49290 OECG R Read ROM; no write
729 visibleBank = LC_BANK_1;
733 else if ((addr & 0xFFFB) == 0xC08B)
736 WriteLog("LC(R): $C08B 49291 OECG RR Read/Write RAM bank 1\n");
738 //$C08B 49291 OECG RR Read/write RAM bank 1
739 visibleBank = LC_BANK_1;
743 //This is determined by which slot it is in--this assumes slot 6. !!! FIX !!!
744 else if ((addr & 0xFFF8) == 0xC0E0)
746 floppyDrive.ControlStepper(addr & 0x07);
748 else if ((addr & 0xFFFE) == 0xC0E8)
750 floppyDrive.ControlMotor(addr & 0x01);
752 else if ((addr & 0xFFFE) == 0xC0EA)
754 floppyDrive.DriveEnable(addr & 0x01);
756 else if (addr == 0xC0EC)
758 //change this to Write()? (and the other to Read()?) Dunno. Seems to work OK, but still...
760 floppyDrive.ReadWrite();
762 else if (addr == 0xC0ED)
764 floppyDrive.SetLatchValue(b);
766 else if (addr == 0xC0EE)
768 floppyDrive.SetReadMode();
770 else if (addr == 0xC0EF)
772 floppyDrive.SetWriteMode();
774 //Still need to add missing I/O switches here...
776 //DEEE: BD 10 BF LDA $BF10,X [PC=DEF1, SP=01F4, CC=--.B-IZ-, A=00, X=0C, Y=07]
778 if (addr >= 0xD000 && addr <= 0xD00F)
780 WriteLog("*** Write to $%04X: $%02X (writeRAM=%s, PC=%04X, ram$D000=%02X)\n", addr, b, (writeRAM ? "T" : "F"), mainCPU.pc, ram[0xC000]);
783 if (addr >= 0xC000 && addr <= 0xCFFF)
784 return; // Protect LC bank #1 from being written to!
790 if (addr <= 0xDFFF && visibleBank == LC_BANK_1)
791 ram[addr - 0x1000] = b;
803 // Load a file into RAM/ROM image space
805 bool LoadImg(char * filename, uint8 * ram, int size)
807 FILE * fp = fopen(filename, "rb");
812 fread(ram, 1, size, fp);
818 static void SaveApple2State(const char * filename)
822 static bool LoadApple2State(const char * filename)
827 #ifdef CPU_CLOCK_CHECKING
830 uint64 lastClock = 0;
835 int main(int /*argc*/, char * /*argv*/[])
837 InitLog("./apple2.log");
839 srand(time(NULL)); // Initialize RNG
842 //Need to bankify this stuff for the IIe emulation...
843 memset(ram, 0, 0x10000);
844 memset(rom, 0, 0x10000);
845 memset(ram2, 0, 0x10000);
847 // Set up V65C02 execution context
848 memset(&mainCPU, 0, sizeof(V65C02REGS));
849 mainCPU.RdMem = RdMem;
850 mainCPU.WrMem = WrMem;
851 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
853 if (!LoadImg(settings.BIOSPath, rom + 0xD000, 0x3000))
855 WriteLog("Could not open file '%s'!\n", settings.BIOSPath);
859 //This is now included...
860 /* if (!LoadImg(settings.diskPath, diskRom, 0x100))
862 WriteLog("Could not open file '%s'!\nDisk II will be unavailable!\n", settings.diskPath);
866 //Load up disk image from config file (for now)...
867 floppyDrive.LoadImage(settings.diskImagePath1, 0);
868 floppyDrive.LoadImage(settings.diskImagePath2, 1);
869 // floppyDrive.LoadImage("./disks/temp.nib", 1); // Load temp .nib file into second drive...
871 //Kill the DOS ROM in slot 6 for now...
873 memcpy(rom + 0xC600, diskROM, 0x100);
875 WriteLog("About to initialize video...\n");
878 std::cout << "Aborting!" << std::endl;
882 // Have to do this *after* video init but *before* sound init...!
883 //Shouldn't be necessary since we're not doing emulation in the ISR...
884 if (settings.autoStateSaving)
886 // Load last state from file...
887 if (!LoadApple2State(settings.autoStatePath))
888 WriteLog("Unable to use Apple2 state file \"%s\"!\n", settings.autoStatePath);
894 if (!LoadImg("./BT1_6502_RAM_SPACE.bin", ram, 0x10000))
896 cout << "Couldn't load state file!" << endl;
897 cout << "Aborting!!" << endl;
902 //-- -- -- -- ----- -----
903 //00 75 3B 53 FD 01 41 44
905 mainCPU.cpuFlags = 0;
915 displayPage2 = false;
922 memcpy(ram + 0xD000, ram + 0xC000, 0x1000);
925 WriteLog("About to initialize audio...\n");
927 SDL_EnableUNICODE(1); // Needed to do key translation shit
929 // gui = new GUI(surface); // Set up the GUI system object...
930 gui = new GUI(mainSurface); // Set up the GUI system object...
932 gui->AddMenuTitle("Apple2");
933 gui->AddMenuItem("Test!", TestWindow/*, hotkey*/);
934 gui->AddMenuItem("");
935 gui->AddMenuItem("Quit", QuitEmulator, SDLK_q);
936 gui->CommitItemsToMenu();
939 SetupBlurTable(); // Set up the color TV emulation blur table
940 running = true; // Set running status...
942 InitializeEventList(); // Clear the event list before we use it...
943 SetCallbackTime(FrameCallback, 16666.66666667); // Set frame to fire at 1/60 s interval
944 SetCallbackTime(BlinkTimer, 250000); // Set up blinking at 1/4 s intervals
945 startTicks = SDL_GetTicks();
947 #ifdef THREADED_65C02
948 cpuCond = SDL_CreateCond();
949 cpuThread = SDL_CreateThread(CPUThreadFunc, NULL);
952 WriteLog("Entering main loop...\n");
955 double timeToNextEvent = GetTimeToNextEvent();
956 #ifndef THREADED_65C02
957 Execute65C02(&mainCPU, USEC_TO_M6502_CYCLES(timeToNextEvent));
959 //We MUST remove a frame's worth of time in order for the CPU to function... !!! FIX !!!
960 //(Fix so that this is not a requirement!)
961 //Fixed, but mainCPU.clock is destroyed in the bargain. Oh well.
962 // mainCPU.clock -= USEC_TO_M6502_CYCLES(timeToNextEvent);
963 #ifdef CPU_CLOCK_CHECKING
964 #ifndef THREADED_65C02
965 totalCPU += USEC_TO_M6502_CYCLES(timeToNextEvent);
968 // Handle CPU time delta for sound...
969 //Don't need this anymore now that we use absolute time...
970 // AddToSoundTimeBase(USEC_TO_M6502_CYCLES(timeToNextEvent));
974 #ifdef THREADED_65C02
976 SDL_CondSignal(cpuCond);//thread is probably asleep, wake it up
977 SDL_WaitThread(cpuThread, NULL);
978 //nowok:SDL_WaitThread(CPUThreadFunc, NULL);
979 SDL_DestroyCond(cpuCond);
982 if (settings.autoStateSaving)
984 // Save state here...
985 SaveApple2State(settings.autoStatePath);
987 floppyDrive.SaveImage();
1002 -----------------------
1003 space $A0 $A0 $A0 $A0 No xlation
1004 RETURN $8D $8D $8D $8D No xlation
1005 0 $B0 $B0 $B0 $B0 Need to screen shift+0 (?)
1006 1! $B1 $B1 $A1 $A1 No xlation
1007 2" $B2 $B2 $A2 $A2 No xlation
1008 3# $B3 $B3 $A3 $A3 No xlation
1009 4$ $B4 $B4 $A4 $A4 No xlation
1010 5% $B5 $B5 $A5 $A5 No xlation
1011 6& $B6 $B6 $A6 $A6 No xlation
1012 7' $B7 $B7 $A7 $A7 No xlation
1013 8( $B8 $B8 $A8 $A8 No xlation
1014 9) $B9 $B9 $A9 $A9 No xlation
1015 :* $BA $BA $AA $AA No xlation
1016 ;+ $BB $BB $AB $AB No xlation
1017 ,< $AC $AC $BC $BC No xlation
1018 -= $AD $AD $BD $BD No xlation
1019 .> $AE $AE $BE $BE No xlation
1020 /? $AF $AF $BF $BF No xlation
1033 M $CD $8D $DD $9D -> ODD
1034 N^ $CE $8E $DE $9E -> ODD
1036 P@ $D0 $90 $C0 $80 Need to xlate CTL+SHFT+P & SHFT+P (?)
1049 ESC $9B $9B $9B $9B No xlation
1052 static void FrameCallback(void)
1056 while (SDL_PollEvent(&event))
1061 if (event.key.keysym.unicode != 0)
1063 //Need to do some key translation here, and screen out non-apple keys as well...
1064 if (event.key.keysym.sym == SDLK_TAB) // Prelim key screening...
1067 lastKeyPressed = event.key.keysym.unicode;
1069 //kludge: should have a caps lock thingy here...
1070 //or all uppercase for ][+...
1071 if (lastKeyPressed >= 'a' && lastKeyPressed <='z')
1072 lastKeyPressed &= 0xDF; // Convert to upper case...
1075 // CTRL+RESET key emulation (mapped to CTRL+`)
1076 // This doesn't work...
1077 // if (event.key.keysym.sym == SDLK_BREAK && (event.key.keysym.mod & KMOD_CTRL))
1078 // if (event.key.keysym.sym == SDLK_PAUSE && (event.key.keysym.mod & KMOD_CTRL))
1079 if (event.key.keysym.sym == SDLK_BACKQUOTE && (event.key.keysym.mod & KMOD_CTRL))
1080 //NOTE that this shouldn't take place until the key is lifted... !!! FIX !!!
1081 //ALSO it seems to leave the machine in an inconsistent state vis-a-vis the language card...
1082 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
1084 if (event.key.keysym.sym == SDLK_RIGHT)
1085 lastKeyPressed = 0x15, keyDown = true;
1086 else if (event.key.keysym.sym == SDLK_LEFT)
1087 lastKeyPressed = 0x08, keyDown = true;
1089 // Use ALT+Q to exit, as well as the usual window decoration method
1090 if (event.key.keysym.sym == SDLK_q && (event.key.keysym.mod & KMOD_ALT))
1093 if (event.key.keysym.sym == SDLK_F12)
1094 dumpDis = !dumpDis; // Toggle the disassembly process
1095 // else if (event.key.keysym.sym == SDLK_F11)
1096 // floppyDrive.LoadImage("./disks/bt1_char.dsk");//Kludge to load char disk...
1097 else if (event.key.keysym.sym == SDLK_F9)
1099 floppyDrive.CreateBlankImage();
1100 // SpawnMessage("Image cleared...");
1102 else if (event.key.keysym.sym == SDLK_F10)
1104 floppyDrive.SwapImages();
1105 // SpawnMessage("Image swapped...");
1108 if (event.key.keysym.sym == SDLK_F2)// Toggle the palette
1110 else if (event.key.keysym.sym == SDLK_F3)// Cycle through screen types
1113 // if (event.key.keysym.sym == SDLK_F5) // Temp GUI launch key
1114 if (event.key.keysym.sym == SDLK_F1) // GUI launch key
1115 //NOTE: Should parse the output to determine whether or not the user requested
1116 // to quit completely... !!! FIX !!!
1119 if (event.key.keysym.sym == SDLK_F5)
1122 char volStr[10] = "*********";
1123 volStr[GetVolume()] = 0;
1124 SpawnMessage("Volume: %s", volStr);
1126 else if (event.key.keysym.sym == SDLK_F6)
1129 char volStr[10] = "*********";
1130 volStr[GetVolume()] = 0;
1131 SpawnMessage("Volume: %s", volStr);
1141 SetCallbackTime(FrameCallback, 16666.66666667);
1143 #ifdef CPU_CLOCK_CHECKING
1144 //We know it's stopped, so we can get away with this...
1145 uint64 clock = GetCurrentV65C02Clock();
1146 totalCPU += (uint32)(clock - lastClock);
1151 printf("Executed %u cycles...\n", totalCPU);
1156 #ifdef THREADED_65C02
1157 SDL_CondSignal(cpuCond);//OK, let the CPU go another frame...
1159 //Instead of this, we should yield remaining time to other processes... !!! FIX !!!
1162 //Actually, slows things down too much...
1164 while (SDL_GetTicks() - startTicks < 16); // Wait for next frame...
1165 startTicks = SDL_GetTicks();
1168 static void BlinkTimer(void)
1171 SetCallbackTime(BlinkTimer, 250000); // Set up blinking at 1/4 sec intervals
1175 Next problem is this: How to have events occur and synchronize with the rest
1178 o Have the CPU thread manage the timer mechanism? (need to have a method of carrying
1179 remainder CPU cycles over...)
1181 One way would be to use a fractional accumulator, then subtract 1 every
1182 time it overflows. Like so:
1184 double overflow = 0;
1188 Execute6808(&soundCPU, time);
1189 overflow += 0.289115646;