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
67 uint8 ram[0x10000], rom[0x10000]; // RAM & ROM spaces
69 uint8 diskRom[0x100]; // Disk ROM space
70 V65C02REGS mainCPU; // v65C02 execution context
71 uint8 appleType = APPLE_TYPE_II;
72 FloppyDrive floppyDrive;
76 static uint8 lastKeyPressed = 0;
77 static bool keyDown = false;
79 //static FloppyDrive floppyDrive;
81 enum { LC_BANK_1, LC_BANK_2 };
83 static uint8 visibleBank = LC_BANK_1;
84 static bool readRAM = false;
85 static bool writeRAM = false;
87 static bool running = true; // Machine running state flag...
88 static uint32 startTicks;
90 static GUI * gui = NULL;
92 // Local functions (technically, they're global...)
94 bool LoadImg(char * filename, uint8 * ram, int size);
95 uint8 RdMem(uint16 addr);
96 void WrMem(uint16 addr, uint8 b);
97 static void SaveApple2State(const char * filename);
98 static bool LoadApple2State(const char * filename);
100 // Local timer callback functions
102 static void FrameCallback(void);
103 static void BlinkTimer(void);
105 #ifdef THREADED_65C02
106 // Test of threaded execution of 6502
107 static SDL_Thread * cpuThread = NULL;
108 //static SDL_mutex * cpuMutex = NULL;
109 static SDL_cond * cpuCond = NULL;
110 static SDL_sem * mainSem = NULL;
111 static bool cpuFinished = false;
112 static bool cpuSleep = false;
114 // Let's try a thread...
116 Here's how it works: Execute 1 frame's worth, then sleep.
117 Other stuff wakes it up
119 int CPUThreadFunc(void * data)
121 // Mutex must be locked for conditional to work...
122 // Also, must be created in the thread that uses it...
123 SDL_mutex * cpuMutex = SDL_CreateMutex();
125 // decrement mainSem...
126 //SDL_SemWait(mainSem);
127 #ifdef CPU_THREAD_OVERFLOW_COMPENSATION
128 float overflow = 0.0;
134 SDL_CondWait(cpuCond, cpuMutex);
136 // decrement mainSem...
137 #ifdef THREAD_DEBUGGING
138 WriteLog("CPU: SDL_SemWait(mainSem);\n");
140 SDL_SemWait(mainSem);
142 uint32 cycles = 17066;
143 #ifdef CPU_THREAD_OVERFLOW_COMPENSATION
144 // ODD! It's closer *without* this overflow compensation. ??? WHY ???
145 overflow += 0.666666667;
154 #ifdef THREAD_DEBUGGING
155 WriteLog("CPU: Execute65C02(&mainCPU, cycles);\n");
157 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!
159 // Adjust the sound routine's last cycle toggled time base
160 // Also, since we're finished executing, .clock is now valid
161 #ifdef THREAD_DEBUGGING
162 WriteLog("CPU: AdjustLastToggleCycles(mainCPU.clock);\n");
164 AdjustLastToggleCycles(mainCPU.clock);
166 #ifdef THREAD_DEBUGGING
167 WriteLog("CPU: SDL_mutexP(cpuMutex);\n");
169 SDL_mutexP(cpuMutex);
171 if (SDL_CondWait(cpuCond, cpuMutex) != 0)
173 printf("SDL_CondWait != 0! (Error: '%s')\n", SDL_GetError());
177 // increment mainSem...
178 #ifdef THREAD_DEBUGGING
179 WriteLog("CPU: SDL_SemPost(mainSem);\n");
181 SDL_SemPost(mainSem);
182 // SDL_CondSignal(mainCond); // In case something is waiting on us...
184 #ifdef THREAD_DEBUGGING
185 WriteLog("CPU: SDL_CondWait(cpuCond, cpuMutex);\n");
187 SDL_CondWait(cpuCond, cpuMutex);
190 #ifdef THREAD_DEBUGGING
191 WriteLog("CPU: SDL_mutexV(cpuMutex);\n");
193 SDL_mutexV(cpuMutex);
195 while (!cpuFinished);
197 SDL_DestroyMutex(cpuMutex);
205 Element * TestWindow(void)
207 Element * win = new DraggableWindow2(10, 10, 128, 128);
208 // ((DraggableWindow *)win)->AddElement(new TextEdit(4, 16, 92, 0, "u2prog.dsk", win));
213 Element * QuitEmulator(void)
222 Small Apple II memory map:
224 $C010 - Clear bit 7 of keyboard data ($C000)
225 $C030 - Toggle speaker diaphragm
227 $C054 - Select page 1
228 $C056 - Select lo-res
229 $C058 - Set annuciator-0 output to 0
230 $C05A - Set annuciator-0 output to 0
231 $C05D - Set annuciator-0 output to 1
232 $C05F - Set annuciator-0 output to 1
233 $C0E0 - Disk control stepper ($C0E0-7)
234 $C0E9 - Disk control motor (on)
235 $C0EA - Disk enable (drive 1)
237 $C0EE - Disk set read mode
241 // V65C02 read/write memory functions
244 uint8 RdMem(uint16 addr)
249 if (addr >= 0xC000 && addr <= 0xC0FF)
250 WriteLog("\n*** Read at I/O address %04X\n", addr);
253 if (addr >= 0xC080 && addr <= 0xC08F)
254 WriteLog("\n*** Read at I/O address %04X\n", addr);
257 if ((addr & 0xFFF0) == 0xC000) // Read $C000-$C00F
259 return lastKeyPressed | (keyDown ? 0x80 : 0x00);
261 else if ((addr & 0xFFF0) == 0xC010) // Read $C010-$C01F
263 //This is bogus: keyDown is set to false, so return val NEVER is set...
265 //Also, this is IIe/IIc only...!
266 uint8 retVal = lastKeyPressed | (keyDown ? 0x80 : 0x00);
270 else if ((addr & 0xFFF0) == 0xC030) // Read $C030-$C03F
273 This is problematic, mainly because the v65C02 removes actual cycles run after each call.
274 Therefore, we don't really have a reliable method of sending a timestamp to the sound routine.
277 What we need to send is a delta T value but related to the IRQ buffer routine. E.g., if the buffer
278 hasn't had any changes in it then we just fill it with the last sample value and are done. Then
279 we need to adjust our delta T accordingly. What we could do is keep a running total of time since the
280 last change and adjust it accordingly, i.e., whenever a sound IRQ happens.
283 Have deltaT somewhere. Then, whenever there's a toggle, backfill buffer with last spkr state and reset
284 deltaT to zero. In the sound IRQ, if deltaT > buffer size, then subtract buffer size from deltaT. (?)
289 ToggleSpeaker(GetCurrentV65C02Clock());
290 //should it return something else here???
293 else if (addr == 0xC050) // Read $C050
297 else if (addr == 0xC051) // Read $C051
301 else if (addr == 0xC052) // Read $C052
305 else if (addr == 0xC053) // Read $C053
309 else if (addr == 0xC054) // Read $C054
311 displayPage2 = false;
313 else if (addr == 0xC055) // Read $C055
317 else if (addr == 0xC056) // Read $C056
321 else if (addr == 0xC057) // Read $C057
326 //Note that this is a kludge: The $D000-$DFFF 4K space is shared (since $C000-$CFFF is
327 //memory mapped) between TWO banks, and that that $E000-$FFFF RAM space is a single bank.
328 //[SHOULD BE FIXED NOW]
329 //OK! This switch selects bank 2 of the 4K bank at $D000-$DFFF. One access makes it
330 //visible, two makes it R/W.
365 else if ((addr & 0xFFFB) == 0xC080)
368 WriteLog("LC(R): $C080 49280 OECG R Read RAM bank 2; no write\n");
370 //$C080 49280 OECG R Read RAM bank 2; no write
371 visibleBank = LC_BANK_2;
375 else if ((addr & 0xFFFB) == 0xC081)
378 WriteLog("LC(R): $C081 49281 OECG RR Read ROM; write RAM bank 2\n");
380 //$C081 49281 ROMIN OECG RR Read ROM; write RAM bank 2
381 visibleBank = LC_BANK_2;
385 else if ((addr & 0xFFFB) == 0xC082)
388 WriteLog("LC(R): $C082 49282 OECG R Read ROM; no write\n");
390 //$C082 49282 OECG R Read ROM; no write
391 visibleBank = LC_BANK_2;
395 else if ((addr & 0xFFFB) == 0xC083)
398 WriteLog("LC(R): $C083 49283 OECG RR Read/Write RAM bank 2\n");
400 //$C083 49283 LCBANK2 OECG RR Read/write RAM bank 2
401 visibleBank = LC_BANK_2;
405 else if ((addr & 0xFFFB) == 0xC088)
408 WriteLog("LC(R): $%04X 49288 OECG R Read RAM bank 1; no write\n", addr);
410 //$C088 49288 OECG R Read RAM bank 1; no write
411 visibleBank = LC_BANK_1;
414 //Hm. Some stuff seems to want this.
415 //nope, was looking at $C0E8... return 0xFF;
417 else if ((addr & 0xFFFB) == 0xC089)
420 WriteLog("LC(R): $C089 49289 OECG RR Read ROM; write RAM bank 1\n");
422 //$C089 49289 OECG RR Read ROM; write RAM bank 1
423 visibleBank = LC_BANK_1;
427 else if ((addr & 0xFFFB) == 0xC08A)
430 WriteLog("LC(R): $C08A 49290 OECG R Read ROM; no write\n");
432 //$C08A 49290 OECG R Read ROM; no write
433 visibleBank = LC_BANK_1;
437 else if ((addr & 0xFFFB) == 0xC08B)
440 WriteLog("LC(R): $C08B 49291 OECG RR Read/Write RAM bank 1\n");
442 //$C08B 49291 OECG RR Read/write RAM bank 1
443 visibleBank = LC_BANK_1;
447 else if ((addr & 0xFFF8) == 0xC0E0)
449 floppyDrive.ControlStepper(addr & 0x07);
451 else if ((addr & 0xFFFE) == 0xC0E8)
453 floppyDrive.ControlMotor(addr & 0x01);
455 else if ((addr & 0xFFFE) == 0xC0EA)
457 floppyDrive.DriveEnable(addr & 0x01);
459 else if (addr == 0xC0EC)
461 return floppyDrive.ReadWrite();
462 //Hm, some stuff is looking at the return value. Dunno what it *should* be...
463 // OK, it's from the ReadWrite routine...
466 else if (addr == 0xC0ED)
468 return floppyDrive.GetLatchValue();
470 else if (addr == 0xC0EE)
472 floppyDrive.SetReadMode();
474 else if (addr == 0xC0EF)
476 floppyDrive.SetWriteMode();
479 //#define LC_DEBUGGING
481 bool showpath = false;
482 if (addr >= 0xD000 && addr <= 0xD00F)
486 if (addr >= 0xC100 && addr <= 0xCFFF) // The $C000-$CFFF block is *never* RAM
493 WriteLog("b is from $C100-$CFFF block...\n");
496 else if (addr >= 0xD000)
500 if (addr <= 0xDFFF && visibleBank == LC_BANK_1)
504 b = ram[addr - 0x1000];
507 WriteLog("b is from LC bank #1 (ram[addr - 0x1000])...\n");
517 WriteLog("b is from LC bank #2 (ram[addr])...\n");
528 WriteLog("b is from LC ROM (rom[addr])...\n");
539 WriteLog("b is from ram[addr]...\n");
544 if (addr >= 0xD000 && addr <= 0xD00F)
546 WriteLog("*** Read from $%04X: $%02X (readRAM=%s, PC=%04X, ram$D000=%02X)\n", addr, b, (readRAM ? "T" : "F"), mainCPU.pc, ram[0xC000]);
555 APPENDIX F Assembly Language Program Listings
561 5 * ;ADDRESSES FOR FIRST 6522
562 6 ORB EQU $C400 ;PORT B
563 7 ORA EQU $C401 ;PORT A
564 8 DDRB EQU $C402 ;DATA DIRECTION REGISTER (A)
565 9 DDRA EQU $C403 ;DATA DIRECTION REGISTER (B)
566 10 * ;ADDRESSES FOR SECOND 6522
567 11 ORB2 EQU $C480 ;PORT B
568 12 ORA2 EQU $C481 ;PORT A
569 13 DDRB2 EQU $C482 ;DATA DIRECTION REGISTER (B)
570 14 DDRA2 EQU $C483 ;DATA DIRECTION REGISTER (A)
572 void WrMem(uint16 addr, uint8 b)
575 //extern V6809REGS regs;
576 //if (addr >= 0xC800 && addr <= 0xCBFE)
577 //if (addr == 0xC80F || addr == 0xC80D)
578 // WriteLog("WrMem: Writing address %04X with %02X [PC=%04X, $CB00=%02X]\n", addr, b, regs.pc, gram[0xCB00]);//*/
581 if (addr >= 0xC000 && addr <= 0xC0FF)
582 WriteLog("\n*** Write at I/O address %04X\n", addr);
585 Check the BIKO version on Asimov to see if it's been cracked or not...
587 7F3D: 29 07 AND #$07 [PC=7F3F, SP=01EA, CC=---B-I--, A=01, X=4B, Y=00]
588 7F3F: C9 06 CMP #$06 [PC=7F41, SP=01EA, CC=N--B-I--, A=01, X=4B, Y=00]
589 7F41: 90 03 BCC $7F46 [PC=7F46, SP=01EA, CC=N--B-I--, A=01, X=4B, Y=00]
590 [7F43: 4C 83 7E JMP $7E83] <- Skipped over... (Prints "THANK YOU VERY MUCH!")
591 7F46: AA TAX [PC=7F47, SP=01EA, CC=---B-I--, A=01, X=01, Y=00]
593 ; INX here *ensures* 1 - 6!!! BUG!!!
594 ; Or is it? Could this be part of a braindead copy protection scheme? It's
595 ; awfully close to NOP ($EA)...
596 ; Nothing else touches it once it's been written... Hmm...
598 7F47: E8 INX [PC=7F48, SP=01EA, CC=---B-I--, A=01, X=02, Y=00]
599 7F48: F8 SED [PC=7F49, SP=01EA, CC=---BDI--, A=01, X=02, Y=00]
600 7F49: 18 CLC [PC=7F4A, SP=01EA, CC=---BDI--, A=01, X=02, Y=00]
601 7F4A: BD 15 4E LDA $4E15,X [PC=7F4D, SP=01EA, CC=---BDI--, A=15, X=02, Y=00]
604 ; 4E15: 25 25 15 15 10 20
605 ; 4E1B: 03 41 99 99 01 00 12
608 7F4D: 65 FC ADC $FC [PC=7F4F, SP=01EA, CC=---BDI--, A=16, X=02, Y=00]
609 7F4F: 65 FC ADC $FC [PC=7F51, SP=01EA, CC=---BDI--, A=17, X=02, Y=00]
610 7F51: 65 FC ADC $FC [PC=7F53, SP=01EA, CC=---BDI--, A=18, X=02, Y=00]
611 7F53: 65 FC ADC $FC [PC=7F55, SP=01EA, CC=---BDI--, A=19, X=02, Y=00]
613 ; NO checking is done on the raised stat! Aarrrgggghhhhh!
615 7F55: 9D 15 4E STA $4E15,X [PC=7F58, SP=01EA, CC=---BDI--, A=19, X=02, Y=00]
616 7F58: D8 CLD [PC=7F59, SP=01EA, CC=---B-I--, A=19, X=02, Y=00]
618 ; Print "ALAKAZAM!" and so on...
620 7F59: 20 2C 40 JSR $402C [PC=402C, SP=01E8, CC=---B-I--, A=19, X=02, Y=00]
624 WriteLog("\n*** Byte %02X written at address %04X\n", b, addr);
627 I think this is IIc/IIe only...
629 CLR80STORE=$C000 ;80STORE Off- disable 80-column memory mapping (Write)
630 SET80STORE=$C001 ;80STORE On- enable 80-column memory mapping (WR-only)
632 CLRAUXRD = $C002 ;read from main 48K (WR-only)
633 SETAUXRD = $C003 ;read from aux/alt 48K (WR-only)
635 CLRAUXWR = $C004 ;write to main 48K (WR-only)
636 SETAUXWR = $C005 ;write to aux/alt 48K (WR-only)
638 CLRCXROM = $C006 ;use ROM on cards (WR-only)
639 SETCXROM = $C007 ;use internal ROM (WR-only)
641 CLRAUXZP = $C008 ;use main zero page, stack, & LC (WR-only)
642 SETAUXZP = $C009 ;use alt zero page, stack, & LC (WR-only)
644 CLRC3ROM = $C00A ;use internal Slot 3 ROM (WR-only)
645 SETC3ROM = $C00B ;use external Slot 3 ROM (WR-only)
647 CLR80VID = $C00C ;disable 80-column display mode (WR-only)
648 SET80VID = $C00D ;enable 80-column display mode (WR-only)
650 CLRALTCH = $C00E ;use main char set- norm LC, Flash UC (WR-only)
651 SETALTCH = $C00F ;use alt char set- norm inverse, LC; no Flash (WR-only)
655 alternateCharset = false;
657 else if (addr == 0xC00F)
659 alternateCharset = true;
661 else if ((addr & 0xFFF0) == 0xC010) // Keyboard strobe
663 //Actually, according to the A2 ref, this should do nothing since a write
664 //is immediately preceded by a read leaving it in the same state it was...
665 //But leaving this out seems to fuck up the key handling of some games...
668 else if (addr == 0xC050)
672 else if (addr == 0xC051)
676 else if (addr == 0xC052)
680 else if (addr == 0xC053)
684 else if (addr == 0xC054)
686 displayPage2 = false;
688 else if (addr == 0xC055)
692 else if (addr == 0xC056)
696 else if (addr == 0xC057)
700 else if ((addr & 0xFFFB) == 0xC080)
703 WriteLog("LC(R): $C080 49280 OECG R Read RAM bank 2; no write\n");
705 //$C080 49280 OECG R Read RAM bank 2; no write
706 visibleBank = LC_BANK_2;
710 else if ((addr & 0xFFFB) == 0xC081)
713 WriteLog("LC(R): $C081 49281 OECG RR Read ROM; write RAM bank 2\n");
715 //$C081 49281 ROMIN OECG RR Read ROM; write RAM bank 2
716 visibleBank = LC_BANK_2;
720 else if ((addr & 0xFFFB) == 0xC082)
723 WriteLog("LC(R): $C082 49282 OECG R Read ROM; no write\n");
725 //$C082 49282 OECG R Read ROM; no write
726 visibleBank = LC_BANK_2;
730 else if ((addr & 0xFFFB) == 0xC083)
733 WriteLog("LC(R): $C083 49283 OECG RR Read/Write RAM bank 2\n");
735 //$C083 49283 LCBANK2 OECG RR Read/write RAM bank 2
736 visibleBank = LC_BANK_2;
740 else if ((addr & 0xFFFB) == 0xC088)
743 WriteLog("LC(R): $C088 49288 OECG R Read RAM bank 1; no write\n");
745 //$C088 49288 OECG R Read RAM bank 1; no write
746 visibleBank = LC_BANK_1;
750 else if ((addr & 0xFFFB) == 0xC089)
753 WriteLog("LC(R): $C089 49289 OECG RR Read ROM; write RAM bank 1\n");
755 //$C089 49289 OECG RR Read ROM; write RAM bank 1
756 visibleBank = LC_BANK_1;
760 else if ((addr & 0xFFFB) == 0xC08A)
763 WriteLog("LC(R): $C08A 49290 OECG R Read ROM; no write\n");
765 //$C08A 49290 OECG R Read ROM; no write
766 visibleBank = LC_BANK_1;
770 else if ((addr & 0xFFFB) == 0xC08B)
773 WriteLog("LC(R): $C08B 49291 OECG RR Read/Write RAM bank 1\n");
775 //$C08B 49291 OECG RR Read/write RAM bank 1
776 visibleBank = LC_BANK_1;
780 //This is determined by which slot it is in--this assumes slot 6. !!! FIX !!!
781 else if ((addr & 0xFFF8) == 0xC0E0)
783 floppyDrive.ControlStepper(addr & 0x07);
785 else if ((addr & 0xFFFE) == 0xC0E8)
787 floppyDrive.ControlMotor(addr & 0x01);
789 else if ((addr & 0xFFFE) == 0xC0EA)
791 floppyDrive.DriveEnable(addr & 0x01);
793 else if (addr == 0xC0EC)
795 //change this to Write()? (and the other to Read()?) Dunno. Seems to work OK, but still...
797 floppyDrive.ReadWrite();
799 else if (addr == 0xC0ED)
801 floppyDrive.SetLatchValue(b);
803 else if (addr == 0xC0EE)
805 floppyDrive.SetReadMode();
807 else if (addr == 0xC0EF)
809 floppyDrive.SetWriteMode();
811 //Still need to add missing I/O switches here...
813 //DEEE: BD 10 BF LDA $BF10,X [PC=DEF1, SP=01F4, CC=--.B-IZ-, A=00, X=0C, Y=07]
815 if (addr >= 0xD000 && addr <= 0xD00F)
817 WriteLog("*** Write to $%04X: $%02X (writeRAM=%s, PC=%04X, ram$D000=%02X)\n", addr, b, (writeRAM ? "T" : "F"), mainCPU.pc, ram[0xC000]);
820 if (addr >= 0xC000 && addr <= 0xCFFF)
821 return; // Protect LC bank #1 from being written to!
827 if (addr <= 0xDFFF && visibleBank == LC_BANK_1)
828 ram[addr - 0x1000] = b;
840 // Load a file into RAM/ROM image space
842 bool LoadImg(char * filename, uint8 * ram, int size)
844 FILE * fp = fopen(filename, "rb");
849 fread(ram, 1, size, fp);
855 static void SaveApple2State(const char * filename)
859 static bool LoadApple2State(const char * filename)
864 #ifdef CPU_CLOCK_CHECKING
867 uint64 lastClock = 0;
872 int main(int /*argc*/, char * /*argv*/[])
874 InitLog("./apple2.log");
876 srand(time(NULL)); // Initialize RNG
879 //Need to bankify this stuff for the IIe emulation...
880 memset(ram, 0, 0x10000);
881 memset(rom, 0, 0x10000);
882 memset(ram2, 0, 0x10000);
884 // Set up V65C02 execution context
885 memset(&mainCPU, 0, sizeof(V65C02REGS));
886 mainCPU.RdMem = RdMem;
887 mainCPU.WrMem = WrMem;
888 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
890 if (!LoadImg(settings.BIOSPath, rom + 0xD000, 0x3000))
892 WriteLog("Could not open file '%s'!\n", settings.BIOSPath);
896 //This is now included...
897 /* if (!LoadImg(settings.diskPath, diskRom, 0x100))
899 WriteLog("Could not open file '%s'!\nDisk II will be unavailable!\n", settings.diskPath);
903 //Load up disk image from config file (for now)...
904 floppyDrive.LoadImage(settings.diskImagePath1, 0);
905 floppyDrive.LoadImage(settings.diskImagePath2, 1);
906 // floppyDrive.LoadImage("./disks/temp.nib", 1); // Load temp .nib file into second drive...
908 //Kill the DOS ROM in slot 6 for now...
910 memcpy(rom + 0xC600, diskROM, 0x100);
912 WriteLog("About to initialize video...\n");
915 std::cout << "Aborting!" << std::endl;
919 // Have to do this *after* video init but *before* sound init...!
920 //Shouldn't be necessary since we're not doing emulation in the ISR...
921 if (settings.autoStateSaving)
923 // Load last state from file...
924 if (!LoadApple2State(settings.autoStatePath))
925 WriteLog("Unable to use Apple2 state file \"%s\"!\n", settings.autoStatePath);
931 if (!LoadImg("./BT1_6502_RAM_SPACE.bin", ram, 0x10000))
933 cout << "Couldn't load state file!" << endl;
934 cout << "Aborting!!" << endl;
939 //-- -- -- -- ----- -----
940 //00 75 3B 53 FD 01 41 44
942 mainCPU.cpuFlags = 0;
952 displayPage2 = false;
959 memcpy(ram + 0xD000, ram + 0xC000, 0x1000);
962 WriteLog("About to initialize audio...\n");
964 SDL_EnableUNICODE(1); // Needed to do key translation shit
966 // gui = new GUI(surface); // Set up the GUI system object...
967 gui = new GUI(mainSurface); // Set up the GUI system object...
969 gui->AddMenuTitle("Apple2");
970 gui->AddMenuItem("Test!", TestWindow/*, hotkey*/);
971 gui->AddMenuItem("");
972 gui->AddMenuItem("Quit", QuitEmulator, SDLK_q);
973 gui->CommitItemsToMenu();
976 SetupBlurTable(); // Set up the color TV emulation blur table
977 running = true; // Set running status...
979 InitializeEventList(); // Clear the event list before we use it...
980 SetCallbackTime(FrameCallback, 16666.66666667); // Set frame to fire at 1/60 s interval
981 SetCallbackTime(BlinkTimer, 250000); // Set up blinking at 1/4 s intervals
982 startTicks = SDL_GetTicks();
984 #ifdef THREADED_65C02
985 cpuCond = SDL_CreateCond();
986 cpuThread = SDL_CreateThread(CPUThreadFunc, NULL);
987 //Hmm... CPU does POST (+1), wait, then WAIT (-1)
988 mainSem = SDL_CreateSemaphore(1);
989 // SDL_sem * mainMutex = SDL_CreateMutex();
992 WriteLog("Entering main loop...\n");
995 double timeToNextEvent = GetTimeToNextEvent();
996 #ifndef THREADED_65C02
997 Execute65C02(&mainCPU, USEC_TO_M6502_CYCLES(timeToNextEvent));
999 //We MUST remove a frame's worth of time in order for the CPU to function... !!! FIX !!!
1000 //(Fix so that this is not a requirement!)
1001 //Fixed, but mainCPU.clock is destroyed in the bargain. Oh well.
1002 // mainCPU.clock -= USEC_TO_M6502_CYCLES(timeToNextEvent);
1003 #ifdef CPU_CLOCK_CHECKING
1004 #ifndef THREADED_65C02
1005 totalCPU += USEC_TO_M6502_CYCLES(timeToNextEvent);
1008 // Handle CPU time delta for sound...
1009 //Don't need this anymore now that we use absolute time...
1010 // AddToSoundTimeBase(USEC_TO_M6502_CYCLES(timeToNextEvent));
1014 #ifdef THREADED_65C02
1015 WriteLog("Main: cpuFinished = true;\n");
1017 //#warning "If sound thread is behind, CPU thread will never wake up... !!! FIX !!!" [DONE]
1018 //What to do? How do you know when the CPU is sleeping???
1019 //USE A CONDITIONAL!!! OF COURSE!!!!!!11!11!11!!!1!
1021 SDL_mutexP(mainMutex);
1022 SDL_CondWait(mainCond, mainMutex); // Wait for CPU thread to get to signal point...
1023 SDL_mutexV(mainMutex);
1025 //Nope, use a semaphore...
1026 WriteLog("Main: SDL_SemWait(mainSem);\n");
1027 SDL_SemWait(mainSem);//should lock until CPU thread is waiting...
1030 WriteLog("Main: SDL_CondSignal(cpuCond);//thread is probably asleep, wake it up\n");
1031 SDL_CondSignal(cpuCond);//thread is probably asleep, wake it up
1032 WriteLog("Main: SDL_WaitThread(cpuThread, NULL);\n");
1033 SDL_WaitThread(cpuThread, NULL);
1034 //nowok:SDL_WaitThread(CPUThreadFunc, NULL);
1035 WriteLog("Main: SDL_DestroyCond(cpuCond);\n");
1036 SDL_DestroyCond(cpuCond);
1038 //SDL_DestroyMutex(mainMutex);
1039 SDL_DestroySemaphore(mainSem);
1042 if (settings.autoStateSaving)
1044 // Save state here...
1045 SaveApple2State(settings.autoStatePath);
1047 floppyDrive.SaveImage();
1062 -----------------------
1063 space $A0 $A0 $A0 $A0 No xlation
1064 RETURN $8D $8D $8D $8D No xlation
1065 0 $B0 $B0 $B0 $B0 Need to screen shift+0 (?)
1066 1! $B1 $B1 $A1 $A1 No xlation
1067 2" $B2 $B2 $A2 $A2 No xlation
1068 3# $B3 $B3 $A3 $A3 No xlation
1069 4$ $B4 $B4 $A4 $A4 No xlation
1070 5% $B5 $B5 $A5 $A5 No xlation
1071 6& $B6 $B6 $A6 $A6 No xlation
1072 7' $B7 $B7 $A7 $A7 No xlation
1073 8( $B8 $B8 $A8 $A8 No xlation
1074 9) $B9 $B9 $A9 $A9 No xlation
1075 :* $BA $BA $AA $AA No xlation
1076 ;+ $BB $BB $AB $AB No xlation
1077 ,< $AC $AC $BC $BC No xlation
1078 -= $AD $AD $BD $BD No xlation
1079 .> $AE $AE $BE $BE No xlation
1080 /? $AF $AF $BF $BF No xlation
1093 M $CD $8D $DD $9D -> ODD
1094 N^ $CE $8E $DE $9E -> ODD
1096 P@ $D0 $90 $C0 $80 Need to xlate CTL+SHFT+P & SHFT+P (?)
1109 ESC $9B $9B $9B $9B No xlation
1112 static void FrameCallback(void)
1116 while (SDL_PollEvent(&event))
1121 if (event.key.keysym.unicode != 0)
1123 //Need to do some key translation here, and screen out non-apple keys as well...
1124 if (event.key.keysym.sym == SDLK_TAB) // Prelim key screening...
1127 lastKeyPressed = event.key.keysym.unicode;
1129 //kludge: should have a caps lock thingy here...
1130 //or all uppercase for ][+...
1131 if (lastKeyPressed >= 'a' && lastKeyPressed <='z')
1132 lastKeyPressed &= 0xDF; // Convert to upper case...
1135 // CTRL+RESET key emulation (mapped to CTRL+`)
1136 // This doesn't work...
1137 // if (event.key.keysym.sym == SDLK_BREAK && (event.key.keysym.mod & KMOD_CTRL))
1138 // if (event.key.keysym.sym == SDLK_PAUSE && (event.key.keysym.mod & KMOD_CTRL))
1139 if (event.key.keysym.sym == SDLK_BACKQUOTE && (event.key.keysym.mod & KMOD_CTRL))
1140 //NOTE that this shouldn't take place until the key is lifted... !!! FIX !!!
1141 //ALSO it seems to leave the machine in an inconsistent state vis-a-vis the language card...
1142 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
1144 if (event.key.keysym.sym == SDLK_RIGHT)
1145 lastKeyPressed = 0x15, keyDown = true;
1146 else if (event.key.keysym.sym == SDLK_LEFT)
1147 lastKeyPressed = 0x08, keyDown = true;
1149 // Use ALT+Q to exit, as well as the usual window decoration method
1150 if (event.key.keysym.sym == SDLK_q && (event.key.keysym.mod & KMOD_ALT))
1153 if (event.key.keysym.sym == SDLK_F12)
1154 dumpDis = !dumpDis; // Toggle the disassembly process
1155 // else if (event.key.keysym.sym == SDLK_F11)
1156 // floppyDrive.LoadImage("./disks/bt1_char.dsk");//Kludge to load char disk...
1157 else if (event.key.keysym.sym == SDLK_F9)
1159 floppyDrive.CreateBlankImage();
1160 // SpawnMessage("Image cleared...");
1162 else if (event.key.keysym.sym == SDLK_F10)
1164 floppyDrive.SwapImages();
1165 // SpawnMessage("Image swapped...");
1168 if (event.key.keysym.sym == SDLK_F2)// Toggle the palette
1170 else if (event.key.keysym.sym == SDLK_F3)// Cycle through screen types
1173 // if (event.key.keysym.sym == SDLK_F5) // Temp GUI launch key
1174 if (event.key.keysym.sym == SDLK_F1) // GUI launch key
1175 //NOTE: Should parse the output to determine whether or not the user requested
1176 // to quit completely... !!! FIX !!!
1179 if (event.key.keysym.sym == SDLK_F5)
1182 char volStr[19] = "[****************]";
1183 // volStr[GetVolume()] = 0;
1184 for(int i=GetVolume(); i<16; i++)
1185 volStr[1 + i] = '-';
1186 SpawnMessage("Volume: %s", volStr);
1188 else if (event.key.keysym.sym == SDLK_F6)
1191 char volStr[19] = "[****************]";
1192 // volStr[GetVolume()] = 0;
1193 for(int i=GetVolume(); i<16; i++)
1194 volStr[1 + i] = '-';
1195 SpawnMessage("Volume: %s", volStr);
1205 SetCallbackTime(FrameCallback, 16666.66666667);
1207 #ifdef CPU_CLOCK_CHECKING
1208 //We know it's stopped, so we can get away with this...
1212 uint64 clock = GetCurrentV65C02Clock();
1213 //totalCPU += (uint32)(clock - lastClock);
1215 printf("Executed %u cycles...\n", (uint32)(clock - lastClock));
1221 #ifdef THREADED_65C02
1222 SDL_CondSignal(cpuCond);//OK, let the CPU go another frame...
1224 //Instead of this, we should yield remaining time to other processes... !!! FIX !!!
1227 //Actually, slows things down too much...
1229 while (SDL_GetTicks() - startTicks < 16); // Wait for next frame...
1230 startTicks = SDL_GetTicks();
1233 static void BlinkTimer(void)
1236 SetCallbackTime(BlinkTimer, 250000); // Set up blinking at 1/4 sec intervals
1240 Next problem is this: How to have events occur and synchronize with the rest
1243 o Have the CPU thread manage the timer mechanism? (need to have a method of carrying
1244 remainder CPU cycles over...)
1246 One way would be to use a fractional accumulator, then subtract 1 every
1247 time it overflows. Like so:
1249 double overflow = 0;
1253 Execute6808(&soundCPU, time);
1254 overflow += 0.289115646;