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_t ram[0x10000], rom[0x10000]; // RAM & ROM spaces
68 uint8_t ram2[0x10000];
69 uint8_t diskRom[0x100]; // Disk ROM space
70 V65C02REGS mainCPU; // v65C02 execution context
71 uint8_t appleType = APPLE_TYPE_II;
72 FloppyDrive floppyDrive;
76 static uint8_t lastKeyPressed = 0;
77 static bool keyDown = false;
78 static bool openAppleDown = false;
79 static bool closedAppleDown = false;
81 //static FloppyDrive floppyDrive;
83 enum { LC_BANK_1, LC_BANK_2 };
85 static uint8_t visibleBank = LC_BANK_1;
86 static bool readRAM = false;
87 static bool writeRAM = false;
89 static bool running = true; // Machine running state flag...
90 static uint32_t startTicks;
92 static GUI * gui = NULL;
94 // Local functions (technically, they're global...)
96 bool LoadImg(char * filename, uint8_t * ram, int size);
97 uint8_t RdMem(uint16_t addr);
98 void WrMem(uint16_t addr, uint8_t b);
99 static void SaveApple2State(const char * filename);
100 static bool LoadApple2State(const char * filename);
102 // Local timer callback functions
104 static void FrameCallback(void);
105 static void BlinkTimer(void);
107 #ifdef THREADED_65C02
108 // Test of threaded execution of 6502
109 static SDL_Thread * cpuThread = NULL;
110 //static SDL_mutex * cpuMutex = NULL;
111 static SDL_cond * cpuCond = NULL;
112 static SDL_sem * mainSem = NULL;
113 static bool cpuFinished = false;
114 static bool cpuSleep = false;
117 // Let's try a thread...
119 Here's how it works: Execute 1 frame's worth, then sleep.
120 Other stuff wakes it up
122 int CPUThreadFunc(void * data)
124 // Mutex must be locked for conditional to work...
125 // Also, must be created in the thread that uses it...
126 SDL_mutex * cpuMutex = SDL_CreateMutex();
128 // decrement mainSem...
129 //SDL_SemWait(mainSem);
130 #ifdef CPU_THREAD_OVERFLOW_COMPENSATION
131 float overflow = 0.0;
137 SDL_CondWait(cpuCond, cpuMutex);
139 // decrement mainSem...
140 #ifdef THREAD_DEBUGGING
141 WriteLog("CPU: SDL_SemWait(mainSem);\n");
143 SDL_SemWait(mainSem);
145 // There are exactly 800 slices of 21.333 cycles per frame, so it works out
148 uint32_t cycles = 17066;
149 #ifdef CPU_THREAD_OVERFLOW_COMPENSATION
150 // ODD! It's closer *without* this overflow compensation. ??? WHY ???
151 overflow += 0.666666667;
160 #ifdef THREAD_DEBUGGING
161 WriteLog("CPU: Execute65C02(&mainCPU, cycles);\n");
163 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!
165 // Adjust the sound routine's last cycle toggled time base
166 // Also, since we're finished executing, .clock is now valid
167 #ifdef THREAD_DEBUGGING
168 WriteLog("CPU: AdjustLastToggleCycles(mainCPU.clock);\n");
170 AdjustLastToggleCycles(mainCPU.clock);
172 #ifdef THREAD_DEBUGGING
173 WriteLog("CPU: Execute65C02(&mainCPU, cycles);\n");
175 for(int i=0; i<800; i++)
177 uint32_t cycles = 21;
178 overflow += 0.333333334;
186 Execute65C02(&mainCPU, cycles);
187 WriteSampleToBuffer();
192 #ifdef THREAD_DEBUGGING
193 WriteLog("CPU: SDL_mutexP(cpuMutex);\n");
195 SDL_mutexP(cpuMutex);
197 if (SDL_CondWait(cpuCond, cpuMutex) != 0)
199 printf("SDL_CondWait != 0! (Error: '%s')\n", SDL_GetError());
203 // increment mainSem...
204 #ifdef THREAD_DEBUGGING
205 WriteLog("CPU: SDL_SemPost(mainSem);\n");
207 SDL_SemPost(mainSem);
208 // SDL_CondSignal(mainCond); // In case something is waiting on us...
210 #ifdef THREAD_DEBUGGING
211 WriteLog("CPU: SDL_CondWait(cpuCond, cpuMutex);\n");
213 SDL_CondWait(cpuCond, cpuMutex);
216 #ifdef THREAD_DEBUGGING
217 WriteLog("CPU: SDL_mutexV(cpuMutex);\n");
219 SDL_mutexV(cpuMutex);
221 while (!cpuFinished);
223 SDL_DestroyMutex(cpuMutex);
232 Element * TestWindow(void)
234 Element * win = new DraggableWindow2(10, 10, 128, 128);
235 // ((DraggableWindow *)win)->AddElement(new TextEdit(4, 16, 92, 0, "u2prog.dsk", win));
241 Element * QuitEmulator(void)
251 Small Apple II memory map:
253 $C010 - Clear bit 7 of keyboard data ($C000)
254 $C030 - Toggle speaker diaphragm
256 $C054 - Select page 1
257 $C056 - Select lo-res
258 $C058 - Set annuciator-0 output to 0
259 $C05A - Set annuciator-0 output to 0
260 $C05D - Set annuciator-0 output to 1
261 $C05F - Set annuciator-0 output to 1
262 $C0E0 - Disk control stepper ($C0E0-7)
263 $C0E9 - Disk control motor (on)
264 $C0EA - Disk enable (drive 1)
266 $C0EE - Disk set read mode
270 // V65C02 read/write memory functions
273 uint8_t RdMem(uint16_t addr)
278 if (addr >= 0xC000 && addr <= 0xC0FF)
279 WriteLog("\n*** Read at I/O address %04X\n", addr);
282 if (addr >= 0xC080 && addr <= 0xC08F)
283 WriteLog("\n*** Read at I/O address %04X\n", addr);
286 if ((addr & 0xFFF0) == 0xC000) // Read $C000-$C00F
288 return lastKeyPressed | (keyDown ? 0x80 : 0x00);
290 else if ((addr & 0xFFF0) == 0xC010) // Read $C010-$C01F
292 //This is bogus: keyDown is set to false, so return val NEVER is set...
294 //Also, this is IIe/IIc only...!
295 uint8_t retVal = lastKeyPressed | (keyDown ? 0x80 : 0x00);
299 else if ((addr & 0xFFF0) == 0xC030) // Read $C030-$C03F
302 This is problematic, mainly because the v65C02 removes actual cycles run after each call.
303 Therefore, we don't really have a reliable method of sending a timestamp to the sound routine.
306 What we need to send is a delta T value but related to the IRQ buffer routine. E.g., if the buffer
307 hasn't had any changes in it then we just fill it with the last sample value and are done. Then
308 we need to adjust our delta T accordingly. What we could do is keep a running total of time since the
309 last change and adjust it accordingly, i.e., whenever a sound IRQ happens.
312 Have deltaT somewhere. Then, whenever there's a toggle, backfill buffer with last spkr state and reset
313 deltaT to zero. In the sound IRQ, if deltaT > buffer size, then subtract buffer size from deltaT. (?)
318 ToggleSpeaker(GetCurrentV65C02Clock());
319 //should it return something else here???
322 else if (addr == 0xC050) // Read $C050
326 else if (addr == 0xC051) // Read $C051
330 else if (addr == 0xC052) // Read $C052
334 else if (addr == 0xC053) // Read $C053
338 else if (addr == 0xC054) // Read $C054
340 displayPage2 = false;
342 else if (addr == 0xC055) // Read $C055
346 else if (addr == 0xC056) // Read $C056
350 else if (addr == 0xC057) // Read $C057
354 else if (addr == 0xC061) // Read $C061
356 // Open Apple key (or push button 0)
357 return (openAppleDown ? 0x80 : 0x00);
359 else if (addr == 0xC062) // Read $C062
361 // Open Apple key (or push button 0)
362 return (closedAppleDown ? 0x80 : 0x00);
365 //Note that this is a kludge: The $D000-$DFFF 4K space is shared (since $C000-$CFFF is
366 //memory mapped) between TWO banks, and that that $E000-$FFFF RAM space is a single bank.
367 //[SHOULD BE FIXED NOW]
368 //OK! This switch selects bank 2 of the 4K bank at $D000-$DFFF. One access makes it
369 //visible, two makes it R/W.
404 else if ((addr & 0xFFFB) == 0xC080)
407 WriteLog("LC(R): $C080 49280 OECG R Read RAM bank 2; no write\n");
409 //$C080 49280 OECG R Read RAM bank 2; no write
410 visibleBank = LC_BANK_2;
414 else if ((addr & 0xFFFB) == 0xC081)
417 WriteLog("LC(R): $C081 49281 OECG RR Read ROM; write RAM bank 2\n");
419 //$C081 49281 ROMIN OECG RR Read ROM; write RAM bank 2
420 visibleBank = LC_BANK_2;
424 else if ((addr & 0xFFFB) == 0xC082)
427 WriteLog("LC(R): $C082 49282 OECG R Read ROM; no write\n");
429 //$C082 49282 OECG R Read ROM; no write
430 visibleBank = LC_BANK_2;
434 else if ((addr & 0xFFFB) == 0xC083)
437 WriteLog("LC(R): $C083 49283 OECG RR Read/Write RAM bank 2\n");
439 //$C083 49283 LCBANK2 OECG RR Read/write RAM bank 2
440 visibleBank = LC_BANK_2;
444 else if ((addr & 0xFFFB) == 0xC088)
447 WriteLog("LC(R): $%04X 49288 OECG R Read RAM bank 1; no write\n", addr);
449 //$C088 49288 OECG R Read RAM bank 1; no write
450 visibleBank = LC_BANK_1;
453 //Hm. Some stuff seems to want this.
454 //nope, was looking at $C0E8... return 0xFF;
456 else if ((addr & 0xFFFB) == 0xC089)
459 WriteLog("LC(R): $C089 49289 OECG RR Read ROM; write RAM bank 1\n");
461 //$C089 49289 OECG RR Read ROM; write RAM bank 1
462 visibleBank = LC_BANK_1;
466 else if ((addr & 0xFFFB) == 0xC08A)
469 WriteLog("LC(R): $C08A 49290 OECG R Read ROM; no write\n");
471 //$C08A 49290 OECG R Read ROM; no write
472 visibleBank = LC_BANK_1;
476 else if ((addr & 0xFFFB) == 0xC08B)
479 WriteLog("LC(R): $C08B 49291 OECG RR Read/Write RAM bank 1\n");
481 //$C08B 49291 OECG RR Read/write RAM bank 1
482 visibleBank = LC_BANK_1;
486 else if ((addr & 0xFFF8) == 0xC0E0)
488 floppyDrive.ControlStepper(addr & 0x07);
490 else if ((addr & 0xFFFE) == 0xC0E8)
492 floppyDrive.ControlMotor(addr & 0x01);
494 else if ((addr & 0xFFFE) == 0xC0EA)
496 floppyDrive.DriveEnable(addr & 0x01);
498 else if (addr == 0xC0EC)
500 return floppyDrive.ReadWrite();
502 else if (addr == 0xC0ED)
504 return floppyDrive.GetLatchValue();
506 else if (addr == 0xC0EE)
508 floppyDrive.SetReadMode();
510 else if (addr == 0xC0EF)
512 floppyDrive.SetWriteMode();
515 //#define LC_DEBUGGING
517 bool showpath = false;
518 if (addr >= 0xD000 && addr <= 0xD00F)
522 if (addr >= 0xC100 && addr <= 0xCFFF) // The $C000-$CFFF block is *never* RAM
529 WriteLog("b is from $C100-$CFFF block...\n");
532 else if (addr >= 0xD000)
536 if (addr <= 0xDFFF && visibleBank == LC_BANK_1)
540 b = ram[addr - 0x1000];
543 WriteLog("b is from LC bank #1 (ram[addr - 0x1000])...\n");
553 WriteLog("b is from LC bank #2 (ram[addr])...\n");
564 WriteLog("b is from LC ROM (rom[addr])...\n");
575 WriteLog("b is from ram[addr]...\n");
580 if (addr >= 0xD000 && addr <= 0xD00F)
582 WriteLog("*** Read from $%04X: $%02X (readRAM=%s, PC=%04X, ram$D000=%02X)\n", addr, b, (readRAM ? "T" : "F"), mainCPU.pc, ram[0xC000]);
591 APPENDIX F Assembly Language Program Listings
597 5 * ;ADDRESSES FOR FIRST 6522
598 6 ORB EQU $C400 ;PORT B
599 7 ORA EQU $C401 ;PORT A
600 8 DDRB EQU $C402 ;DATA DIRECTION REGISTER (A)
601 9 DDRA EQU $C403 ;DATA DIRECTION REGISTER (B)
602 10 * ;ADDRESSES FOR SECOND 6522
603 11 ORB2 EQU $C480 ;PORT B
604 12 ORA2 EQU $C481 ;PORT A
605 13 DDRB2 EQU $C482 ;DATA DIRECTION REGISTER (B)
606 14 DDRA2 EQU $C483 ;DATA DIRECTION REGISTER (A)
608 void WrMem(uint16_t addr, uint8_t b)
611 //extern V6809REGS regs;
612 //if (addr >= 0xC800 && addr <= 0xCBFE)
613 //if (addr == 0xC80F || addr == 0xC80D)
614 // WriteLog("WrMem: Writing address %04X with %02X [PC=%04X, $CB00=%02X]\n", addr, b, regs.pc, gram[0xCB00]);//*/
617 if (addr >= 0xC000 && addr <= 0xC0FF)
618 WriteLog("\n*** Write at I/O address %04X\n", addr);
621 Check the BIKO version on Asimov to see if it's been cracked or not...
623 7F3D: 29 07 AND #$07 [PC=7F3F, SP=01EA, CC=---B-I--, A=01, X=4B, Y=00]
624 7F3F: C9 06 CMP #$06 [PC=7F41, SP=01EA, CC=N--B-I--, A=01, X=4B, Y=00]
625 7F41: 90 03 BCC $7F46 [PC=7F46, SP=01EA, CC=N--B-I--, A=01, X=4B, Y=00]
626 [7F43: 4C 83 7E JMP $7E83] <- Skipped over... (Prints "THANK YOU VERY MUCH!")
627 7F46: AA TAX [PC=7F47, SP=01EA, CC=---B-I--, A=01, X=01, Y=00]
629 ; INX here *ensures* 1 - 6!!! BUG!!!
630 ; Or is it? Could this be part of a braindead copy protection scheme? It's
631 ; awfully close to NOP ($EA)...
632 ; Nothing else touches it once it's been written... Hmm...
634 7F47: E8 INX [PC=7F48, SP=01EA, CC=---B-I--, A=01, X=02, Y=00]
635 7F48: F8 SED [PC=7F49, SP=01EA, CC=---BDI--, A=01, X=02, Y=00]
636 7F49: 18 CLC [PC=7F4A, SP=01EA, CC=---BDI--, A=01, X=02, Y=00]
637 7F4A: BD 15 4E LDA $4E15,X [PC=7F4D, SP=01EA, CC=---BDI--, A=15, X=02, Y=00]
640 ; 4E15: 25 25 15 15 10 20
641 ; 4E1B: 03 41 99 99 01 00 12
644 7F4D: 65 FC ADC $FC [PC=7F4F, SP=01EA, CC=---BDI--, A=16, X=02, Y=00]
645 7F4F: 65 FC ADC $FC [PC=7F51, SP=01EA, CC=---BDI--, A=17, X=02, Y=00]
646 7F51: 65 FC ADC $FC [PC=7F53, SP=01EA, CC=---BDI--, A=18, X=02, Y=00]
647 7F53: 65 FC ADC $FC [PC=7F55, SP=01EA, CC=---BDI--, A=19, X=02, Y=00]
649 ; NO checking is done on the raised stat! Aarrrgggghhhhh!
651 7F55: 9D 15 4E STA $4E15,X [PC=7F58, SP=01EA, CC=---BDI--, A=19, X=02, Y=00]
652 7F58: D8 CLD [PC=7F59, SP=01EA, CC=---B-I--, A=19, X=02, Y=00]
654 ; Print "ALAKAZAM!" and so on...
656 7F59: 20 2C 40 JSR $402C [PC=402C, SP=01E8, CC=---B-I--, A=19, X=02, Y=00]
660 WriteLog("\n*** Byte %02X written at address %04X\n", b, addr);
663 I think this is IIc/IIe only...
665 CLR80STORE=$C000 ;80STORE Off- disable 80-column memory mapping (Write)
666 SET80STORE=$C001 ;80STORE On- enable 80-column memory mapping (WR-only)
668 CLRAUXRD = $C002 ;read from main 48K (WR-only)
669 SETAUXRD = $C003 ;read from aux/alt 48K (WR-only)
671 CLRAUXWR = $C004 ;write to main 48K (WR-only)
672 SETAUXWR = $C005 ;write to aux/alt 48K (WR-only)
674 CLRCXROM = $C006 ;use ROM on cards (WR-only)
675 SETCXROM = $C007 ;use internal ROM (WR-only)
677 CLRAUXZP = $C008 ;use main zero page, stack, & LC (WR-only)
678 SETAUXZP = $C009 ;use alt zero page, stack, & LC (WR-only)
680 CLRC3ROM = $C00A ;use internal Slot 3 ROM (WR-only)
681 SETC3ROM = $C00B ;use external Slot 3 ROM (WR-only)
683 CLR80VID = $C00C ;disable 80-column display mode (WR-only)
684 SET80VID = $C00D ;enable 80-column display mode (WR-only)
686 CLRALTCH = $C00E ;use main char set- norm LC, Flash UC (WR-only)
687 SETALTCH = $C00F ;use alt char set- norm inverse, LC; no Flash (WR-only)
691 alternateCharset = false;
693 else if (addr == 0xC00F)
695 alternateCharset = true;
697 else if ((addr & 0xFFF0) == 0xC010) // Keyboard strobe
699 //Actually, according to the A2 ref, this should do nothing since a write
700 //is immediately preceded by a read leaving it in the same state it was...
701 //But leaving this out seems to fuck up the key handling of some games...
704 else if (addr == 0xC050)
708 else if (addr == 0xC051)
712 else if (addr == 0xC052)
716 else if (addr == 0xC053)
720 else if (addr == 0xC054)
722 displayPage2 = false;
724 else if (addr == 0xC055)
728 else if (addr == 0xC056)
732 else if (addr == 0xC057)
736 else if ((addr & 0xFFFB) == 0xC080)
739 WriteLog("LC(R): $C080 49280 OECG R Read RAM bank 2; no write\n");
741 //$C080 49280 OECG R Read RAM bank 2; no write
742 visibleBank = LC_BANK_2;
746 else if ((addr & 0xFFFB) == 0xC081)
749 WriteLog("LC(R): $C081 49281 OECG RR Read ROM; write RAM bank 2\n");
751 //$C081 49281 ROMIN OECG RR Read ROM; write RAM bank 2
752 visibleBank = LC_BANK_2;
756 else if ((addr & 0xFFFB) == 0xC082)
759 WriteLog("LC(R): $C082 49282 OECG R Read ROM; no write\n");
761 //$C082 49282 OECG R Read ROM; no write
762 visibleBank = LC_BANK_2;
766 else if ((addr & 0xFFFB) == 0xC083)
769 WriteLog("LC(R): $C083 49283 OECG RR Read/Write RAM bank 2\n");
771 //$C083 49283 LCBANK2 OECG RR Read/write RAM bank 2
772 visibleBank = LC_BANK_2;
776 else if ((addr & 0xFFFB) == 0xC088)
779 WriteLog("LC(R): $C088 49288 OECG R Read RAM bank 1; no write\n");
781 //$C088 49288 OECG R Read RAM bank 1; no write
782 visibleBank = LC_BANK_1;
786 else if ((addr & 0xFFFB) == 0xC089)
789 WriteLog("LC(R): $C089 49289 OECG RR Read ROM; write RAM bank 1\n");
791 //$C089 49289 OECG RR Read ROM; write RAM bank 1
792 visibleBank = LC_BANK_1;
796 else if ((addr & 0xFFFB) == 0xC08A)
799 WriteLog("LC(R): $C08A 49290 OECG R Read ROM; no write\n");
801 //$C08A 49290 OECG R Read ROM; no write
802 visibleBank = LC_BANK_1;
806 else if ((addr & 0xFFFB) == 0xC08B)
809 WriteLog("LC(R): $C08B 49291 OECG RR Read/Write RAM bank 1\n");
811 //$C08B 49291 OECG RR Read/write RAM bank 1
812 visibleBank = LC_BANK_1;
816 //This is determined by which slot it is in--this assumes slot 6. !!! FIX !!!
817 else if ((addr & 0xFFF8) == 0xC0E0)
819 floppyDrive.ControlStepper(addr & 0x07);
821 else if ((addr & 0xFFFE) == 0xC0E8)
823 floppyDrive.ControlMotor(addr & 0x01);
825 else if ((addr & 0xFFFE) == 0xC0EA)
827 floppyDrive.DriveEnable(addr & 0x01);
829 else if (addr == 0xC0EC)
831 //change this to Write()? (and the other to Read()?) Dunno. Seems to work OK, but still...
833 floppyDrive.ReadWrite();
835 else if (addr == 0xC0ED)
837 floppyDrive.SetLatchValue(b);
839 else if (addr == 0xC0EE)
841 floppyDrive.SetReadMode();
843 else if (addr == 0xC0EF)
845 floppyDrive.SetWriteMode();
847 //Still need to add missing I/O switches here...
849 //DEEE: BD 10 BF LDA $BF10,X [PC=DEF1, SP=01F4, CC=--.B-IZ-, A=00, X=0C, Y=07]
851 if (addr >= 0xD000 && addr <= 0xD00F)
853 WriteLog("*** Write to $%04X: $%02X (writeRAM=%s, PC=%04X, ram$D000=%02X)\n", addr, b, (writeRAM ? "T" : "F"), mainCPU.pc, ram[0xC000]);
856 if (addr >= 0xC000 && addr <= 0xCFFF)
857 return; // Protect LC bank #1 from being written to!
863 if (addr <= 0xDFFF && visibleBank == LC_BANK_1)
864 ram[addr - 0x1000] = b;
877 // Load a file into RAM/ROM image space
879 bool LoadImg(char * filename, uint8_t * ram, int size)
881 FILE * fp = fopen(filename, "rb");
886 fread(ram, 1, size, fp);
893 static void SaveApple2State(const char * filename)
898 static bool LoadApple2State(const char * filename)
904 #ifdef CPU_CLOCK_CHECKING
906 uint32_t totalCPU = 0;
907 uint64_t lastClock = 0;
912 int main(int /*argc*/, char * /*argv*/[])
914 InitLog("./apple2.log");
916 srand(time(NULL)); // Initialize RNG
919 //Need to bankify this stuff for the IIe emulation...
920 memset(ram, 0, 0x10000);
921 memset(rom, 0, 0x10000);
922 memset(ram2, 0, 0x10000);
924 // Set up V65C02 execution context
925 memset(&mainCPU, 0, sizeof(V65C02REGS));
926 mainCPU.RdMem = RdMem;
927 mainCPU.WrMem = WrMem;
928 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
930 if (!LoadImg(settings.BIOSPath, rom + 0xD000, 0x3000))
932 WriteLog("Could not open file '%s'!\n", settings.BIOSPath);
936 //This is now included...
937 /* if (!LoadImg(settings.diskPath, diskRom, 0x100))
939 WriteLog("Could not open file '%s'!\nDisk II will be unavailable!\n", settings.diskPath);
943 //Load up disk image from config file (for now)...
944 floppyDrive.LoadImage(settings.diskImagePath1, 0);
945 floppyDrive.LoadImage(settings.diskImagePath2, 1);
946 // floppyDrive.LoadImage("./disks/temp.nib", 1); // Load temp .nib file into second drive...
948 //Kill the DOS ROM in slot 6 for now...
950 memcpy(rom + 0xC600, diskROM, 0x100);
951 // memcpy(rom + 0xC700, diskROM, 0x100); // Slot 7???
953 WriteLog("About to initialize video...\n");
957 std::cout << "Aborting!" << std::endl;
961 // Have to do this *after* video init but *before* sound init...!
962 //Shouldn't be necessary since we're not doing emulation in the ISR...
963 if (settings.autoStateSaving)
965 // Load last state from file...
966 if (!LoadApple2State(settings.autoStatePath))
967 WriteLog("Unable to use Apple2 state file \"%s\"!\n", settings.autoStatePath);
973 if (!LoadImg("./BT1_6502_RAM_SPACE.bin", ram, 0x10000))
975 cout << "Couldn't load state file!" << endl;
976 cout << "Aborting!!" << endl;
981 //-- -- -- -- ----- -----
982 //00 75 3B 53 FD 01 41 44
984 mainCPU.cpuFlags = 0;
994 displayPage2 = false;
1001 memcpy(ram + 0xD000, ram + 0xC000, 0x1000);
1004 WriteLog("About to initialize audio...\n");
1006 //nope SDL_EnableUNICODE(1); // Needed to do key translation shit
1008 // gui = new GUI(surface); // Set up the GUI system object...
1009 // gui = new GUI(mainSurface); // Set up the GUI system object...
1010 // SDL 2... this will likely cause Apple 2 to crash
1011 // gui = new GUI(NULL); // Set up the GUI system object...
1013 gui->AddMenuTitle("Apple2");
1014 gui->AddMenuItem("Test!", TestWindow/*, hotkey*/);
1015 gui->AddMenuItem("");
1016 gui->AddMenuItem("Quit", QuitEmulator, SDLK_q);
1017 gui->CommitItemsToMenu();
1020 SetupBlurTable(); // Set up the color TV emulation blur table
1021 running = true; // Set running status...
1023 InitializeEventList(); // Clear the event list before we use it...
1024 SetCallbackTime(FrameCallback, 16666.66666667); // Set frame to fire at 1/60 s interval
1025 SetCallbackTime(BlinkTimer, 250000); // Set up blinking at 1/4 s intervals
1026 startTicks = SDL_GetTicks();
1028 #ifdef THREADED_65C02
1029 cpuCond = SDL_CreateCond();
1030 mainSem = SDL_CreateSemaphore(1);
1031 cpuThread = SDL_CreateThread(CPUThreadFunc, NULL, NULL);
1032 //Hmm... CPU does POST (+1), wait, then WAIT (-1)
1033 // SDL_sem * mainMutex = SDL_CreateMutex();
1036 WriteLog("Entering main loop...\n");
1039 double timeToNextEvent = GetTimeToNextEvent();
1040 #ifndef THREADED_65C02
1041 Execute65C02(&mainCPU, USEC_TO_M6502_CYCLES(timeToNextEvent));
1043 //We MUST remove a frame's worth of time in order for the CPU to function... !!! FIX !!!
1044 //(Fix so that this is not a requirement!)
1045 //Fixed, but mainCPU.clock is destroyed in the bargain. Oh well.
1046 // mainCPU.clock -= USEC_TO_M6502_CYCLES(timeToNextEvent);
1048 #ifdef CPU_CLOCK_CHECKING
1049 #ifndef THREADED_65C02
1050 totalCPU += USEC_TO_M6502_CYCLES(timeToNextEvent);
1053 // Handle CPU time delta for sound...
1054 //Don't need this anymore now that we use absolute time...
1055 // AddToSoundTimeBase(USEC_TO_M6502_CYCLES(timeToNextEvent));
1059 #ifdef THREADED_65C02
1060 WriteLog("Main: cpuFinished = true;\n");
1062 //#warning "If sound thread is behind, CPU thread will never wake up... !!! FIX !!!" [DONE]
1063 //What to do? How do you know when the CPU is sleeping???
1064 //USE A CONDITIONAL!!! OF COURSE!!!!!!11!11!11!!!1!
1066 SDL_mutexP(mainMutex);
1067 SDL_CondWait(mainCond, mainMutex); // Wait for CPU thread to get to signal point...
1068 SDL_mutexV(mainMutex);
1070 //Nope, use a semaphore...
1071 WriteLog("Main: SDL_SemWait(mainSem);\n");
1072 SDL_SemWait(mainSem);//should lock until CPU thread is waiting...
1075 WriteLog("Main: SDL_CondSignal(cpuCond);//thread is probably asleep, wake it up\n");
1076 SDL_CondSignal(cpuCond);//thread is probably asleep, wake it up
1077 WriteLog("Main: SDL_WaitThread(cpuThread, NULL);\n");
1078 SDL_WaitThread(cpuThread, NULL);
1079 //nowok:SDL_WaitThread(CPUThreadFunc, NULL);
1080 WriteLog("Main: SDL_DestroyCond(cpuCond);\n");
1081 SDL_DestroyCond(cpuCond);
1083 //SDL_DestroyMutex(mainMutex);
1084 SDL_DestroySemaphore(mainSem);
1087 if (settings.autoStateSaving)
1089 // Save state here...
1090 SaveApple2State(settings.autoStatePath);
1092 floppyDrive.SaveImage();
1108 -----------------------
1109 space $A0 $A0 $A0 $A0 No xlation
1110 RETURN $8D $8D $8D $8D No xlation
1111 0 $B0 $B0 $B0 $B0 Need to screen shift+0 (?)
1112 1! $B1 $B1 $A1 $A1 No xlation
1113 2" $B2 $B2 $A2 $A2 No xlation
1114 3# $B3 $B3 $A3 $A3 No xlation
1115 4$ $B4 $B4 $A4 $A4 No xlation
1116 5% $B5 $B5 $A5 $A5 No xlation
1117 6& $B6 $B6 $A6 $A6 No xlation
1118 7' $B7 $B7 $A7 $A7 No xlation
1119 8( $B8 $B8 $A8 $A8 No xlation
1120 9) $B9 $B9 $A9 $A9 No xlation
1121 :* $BA $BA $AA $AA No xlation
1122 ;+ $BB $BB $AB $AB No xlation
1123 ,< $AC $AC $BC $BC No xlation
1124 -= $AD $AD $BD $BD No xlation
1125 .> $AE $AE $BE $BE No xlation
1126 /? $AF $AF $BF $BF No xlation
1139 M $CD $8D $DD $9D -> ODD
1140 N^ $CE $8E $DE $9E -> ODD
1142 P@ $D0 $90 $C0 $80 Need to xlate CTL+SHFT+P & SHFT+P (?)
1155 ESC $9B $9B $9B $9B No xlation
1158 static void FrameCallback(void)
1162 while (SDL_PollEvent(&event))
1167 //Need to do some key translation here, and screen out non-apple keys as well...
1168 //(really, could do it all in SDL_KEYDOWN, would just have to get symbols &
1169 // everything else done separately. this is slightly easier. :-P)
1170 // if (event.key.keysym.sym == SDLK_TAB) // Prelim key screening...
1171 if (event.edit.text[0] == '\t') // Prelim key screening...
1174 lastKeyPressed = event.edit.text[0];
1177 //kludge: should have a caps lock thingy here...
1178 //or all uppercase for ][+...
1179 if (lastKeyPressed >= 'a' && lastKeyPressed <='z')
1180 lastKeyPressed &= 0xDF; // Convert to upper case...
1184 // CTRL+RESET key emulation (mapped to CTRL+`)
1185 // This doesn't work...
1186 // if (event.key.keysym.sym == SDLK_BREAK && (event.key.keysym.mod & KMOD_CTRL))
1187 // if (event.key.keysym.sym == SDLK_PAUSE && (event.key.keysym.mod & KMOD_CTRL))
1188 if (event.key.keysym.sym == SDLK_BACKQUOTE && (event.key.keysym.mod & KMOD_CTRL))
1189 //NOTE that this shouldn't take place until the key is lifted... !!! FIX !!!
1190 //ALSO it seems to leave the machine in an inconsistent state vis-a-vis the language card...
1191 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
1193 if (event.key.keysym.sym == SDLK_RIGHT)
1194 lastKeyPressed = 0x15, keyDown = true;
1195 else if (event.key.keysym.sym == SDLK_LEFT)
1196 lastKeyPressed = 0x08, keyDown = true;
1197 else if (event.key.keysym.sym == SDLK_RETURN)
1198 lastKeyPressed = 0x0D, keyDown = true;
1199 else if (event.key.keysym.sym == SDLK_ESCAPE)
1200 lastKeyPressed = 0x1B, keyDown = true;
1202 // Fix CTRL+key combo...
1203 if (event.key.keysym.mod & KMOD_CTRL)
1205 if (event.key.keysym.sym >= SDLK_a && event.key.keysym.sym <= SDLK_z)
1207 lastKeyPressed = (event.key.keysym.sym - SDLK_a) + 1;
1209 //printf("Key combo pressed: CTRL+%c\n", lastKeyPressed + 0x40);
1213 // Use ALT+Q to exit, as well as the usual window decoration method
1214 if (event.key.keysym.sym == SDLK_q && (event.key.keysym.mod & KMOD_ALT))
1217 // Paddle buttons 0 & 1
1218 if (event.key.keysym.sym == SDLK_INSERT)
1219 openAppleDown = true;
1220 if (event.key.keysym.sym == SDLK_PAGEUP)
1221 closedAppleDown = true;
1223 if (event.key.keysym.sym == SDLK_F11)
1224 dumpDis = !dumpDis; // Toggle the disassembly process
1225 // else if (event.key.keysym.sym == SDLK_F11)
1226 // floppyDrive.LoadImage("./disks/bt1_char.dsk");//Kludge to load char disk...
1227 else if (event.key.keysym.sym == SDLK_F9)
1229 floppyDrive.CreateBlankImage();
1230 // SpawnMessage("Image cleared...");
1232 else if (event.key.keysym.sym == SDLK_F10)
1234 floppyDrive.SwapImages();
1235 // SpawnMessage("Image swapped...");
1238 if (event.key.keysym.sym == SDLK_F2)// Toggle the palette
1240 else if (event.key.keysym.sym == SDLK_F3)// Cycle through screen types
1243 // if (event.key.keysym.sym == SDLK_F5) // Temp GUI launch key
1244 if (event.key.keysym.sym == SDLK_F1) // GUI launch key
1245 //NOTE: Should parse the output to determine whether or not the user requested
1246 // to quit completely... !!! FIX !!!
1249 if (event.key.keysym.sym == SDLK_F5)
1252 char volStr[19] = "[****************]";
1253 // volStr[GetVolume()] = 0;
1254 for(int i=GetVolume(); i<16; i++)
1255 volStr[1 + i] = '-';
1256 SpawnMessage("Volume: %s", volStr);
1258 else if (event.key.keysym.sym == SDLK_F6)
1261 char volStr[19] = "[****************]";
1262 // volStr[GetVolume()] = 0;
1263 for(int i=GetVolume(); i<16; i++)
1264 volStr[1 + i] = '-';
1265 SpawnMessage("Volume: %s", volStr);
1268 static bool fullscreenDebounce = false;
1270 if (event.key.keysym.sym == SDLK_F12)
1272 if (!fullscreenDebounce)
1275 fullscreenDebounce = true;
1282 if (event.key.keysym.sym == SDLK_F12)
1283 fullscreenDebounce = false;
1285 // Paddle buttons 0 & 1
1286 if (event.key.keysym.sym == SDLK_INSERT)
1287 openAppleDown = false;
1288 if (event.key.keysym.sym == SDLK_PAGEUP)
1289 closedAppleDown = false;
1291 // if (event.key.keysym.sym >= SDLK_a && event.key.keysym.sym <= SDLK_z)
1300 #warning "!!! Taking MAJOR time hit with the video frame rendering !!!"
1302 SetCallbackTime(FrameCallback, 16666.66666667);
1304 #ifdef CPU_CLOCK_CHECKING
1305 //We know it's stopped, so we can get away with this...
1309 uint64_t clock = GetCurrentV65C02Clock();
1310 //totalCPU += (uint32_t)(clock - lastClock);
1312 printf("Executed %u cycles...\n", (uint32_t)(clock - lastClock));
1318 //Instead of this, we should yield remaining time to other processes... !!! FIX !!! [DONE]
1321 //Actually, slows things down too much...
1323 // while (SDL_GetTicks() - startTicks < 16); // Wait for next frame...
1324 while (SDL_GetTicks() - startTicks < 16)
1325 SDL_Delay(1); // Wait for next frame...
1327 startTicks = SDL_GetTicks();
1328 //let's wait, then signal...
1329 //works longer, but then still falls behind...
1330 #ifdef THREADED_65C02
1331 SDL_CondSignal(cpuCond);//OK, let the CPU go another frame...
1336 static void BlinkTimer(void)
1339 SetCallbackTime(BlinkTimer, 250000); // Set up blinking at 1/4 sec intervals
1344 Next problem is this: How to have events occur and synchronize with the rest
1347 o Have the CPU thread manage the timer mechanism? (need to have a method of carrying
1348 remainder CPU cycles over...)
1350 One way would be to use a fractional accumulator, then subtract 1 every
1351 time it overflows. Like so:
1353 double overflow = 0;
1357 Execute6808(&soundCPU, time);
1358 overflow += 0.289115646;