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();
191 WriteLog("CPUThread: Supposedly end of frame...\n");
193 #ifdef THREAD_DEBUGGING
194 WriteLog("CPU: SDL_mutexP(cpuMutex);\n");
196 SDL_mutexP(cpuMutex);
198 if (SDL_CondWait(cpuCond, cpuMutex) != 0)
200 printf("SDL_CondWait != 0! (Error: '%s')\n", SDL_GetError());
204 // increment mainSem...
205 #ifdef THREAD_DEBUGGING
206 WriteLog("CPU: SDL_SemPost(mainSem);\n");
208 SDL_SemPost(mainSem);
209 // SDL_CondSignal(mainCond); // In case something is waiting on us...
211 #ifdef THREAD_DEBUGGING
212 WriteLog("CPU: SDL_CondWait(cpuCond, cpuMutex);\n");
214 SDL_CondWait(cpuCond, cpuMutex);
217 #ifdef THREAD_DEBUGGING
218 WriteLog("CPU: SDL_mutexV(cpuMutex);\n");
220 SDL_mutexV(cpuMutex);
222 while (!cpuFinished);
224 SDL_DestroyMutex(cpuMutex);
233 Element * TestWindow(void)
235 Element * win = new DraggableWindow2(10, 10, 128, 128);
236 // ((DraggableWindow *)win)->AddElement(new TextEdit(4, 16, 92, 0, "u2prog.dsk", win));
242 Element * QuitEmulator(void)
252 Small Apple II memory map:
254 $C010 - Clear bit 7 of keyboard data ($C000)
255 $C030 - Toggle speaker diaphragm
257 $C054 - Select page 1
258 $C056 - Select lo-res
259 $C058 - Set annuciator-0 output to 0
260 $C05A - Set annuciator-0 output to 0
261 $C05D - Set annuciator-0 output to 1
262 $C05F - Set annuciator-0 output to 1
263 $C0E0 - Disk control stepper ($C0E0-7)
264 $C0E9 - Disk control motor (on)
265 $C0EA - Disk enable (drive 1)
267 $C0EE - Disk set read mode
271 // V65C02 read/write memory functions
274 uint8_t RdMem(uint16_t addr)
279 if (addr >= 0xC000 && addr <= 0xC0FF)
280 WriteLog("\n*** Read at I/O address %04X\n", addr);
283 if (addr >= 0xC080 && addr <= 0xC08F)
284 WriteLog("\n*** Read at I/O address %04X\n", addr);
287 if ((addr & 0xFFF0) == 0xC000) // Read $C000-$C00F
289 return lastKeyPressed | (keyDown ? 0x80 : 0x00);
291 else if ((addr & 0xFFF0) == 0xC010) // Read $C010-$C01F
293 //This is bogus: keyDown is set to false, so return val NEVER is set...
295 //Also, this is IIe/IIc only...!
296 uint8_t retVal = lastKeyPressed | (keyDown ? 0x80 : 0x00);
300 else if ((addr & 0xFFF0) == 0xC030) // Read $C030-$C03F
303 This is problematic, mainly because the v65C02 removes actual cycles run after each call.
304 Therefore, we don't really have a reliable method of sending a timestamp to the sound routine.
307 What we need to send is a delta T value but related to the IRQ buffer routine. E.g., if the buffer
308 hasn't had any changes in it then we just fill it with the last sample value and are done. Then
309 we need to adjust our delta T accordingly. What we could do is keep a running total of time since the
310 last change and adjust it accordingly, i.e., whenever a sound IRQ happens.
313 Have deltaT somewhere. Then, whenever there's a toggle, backfill buffer with last spkr state and reset
314 deltaT to zero. In the sound IRQ, if deltaT > buffer size, then subtract buffer size from deltaT. (?)
319 ToggleSpeaker(GetCurrentV65C02Clock());
320 //should it return something else here???
323 else if (addr == 0xC050) // Read $C050
327 else if (addr == 0xC051) // Read $C051
331 else if (addr == 0xC052) // Read $C052
335 else if (addr == 0xC053) // Read $C053
339 else if (addr == 0xC054) // Read $C054
341 displayPage2 = false;
343 else if (addr == 0xC055) // Read $C055
347 else if (addr == 0xC056) // Read $C056
351 else if (addr == 0xC057) // Read $C057
355 else if (addr == 0xC061) // Read $C061
357 // Open Apple key (or push button 0)
358 return (openAppleDown ? 0x80 : 0x00);
360 else if (addr == 0xC062) // Read $C062
362 // Open Apple key (or push button 0)
363 return (closedAppleDown ? 0x80 : 0x00);
366 //Note that this is a kludge: The $D000-$DFFF 4K space is shared (since $C000-$CFFF is
367 //memory mapped) between TWO banks, and that that $E000-$FFFF RAM space is a single bank.
368 //[SHOULD BE FIXED NOW]
369 //OK! This switch selects bank 2 of the 4K bank at $D000-$DFFF. One access makes it
370 //visible, two makes it R/W.
405 else if ((addr & 0xFFFB) == 0xC080)
408 WriteLog("LC(R): $C080 49280 OECG R Read RAM bank 2; no write\n");
410 //$C080 49280 OECG R Read RAM bank 2; no write
411 visibleBank = LC_BANK_2;
415 else if ((addr & 0xFFFB) == 0xC081)
418 WriteLog("LC(R): $C081 49281 OECG RR Read ROM; write RAM bank 2\n");
420 //$C081 49281 ROMIN OECG RR Read ROM; write RAM bank 2
421 visibleBank = LC_BANK_2;
425 else if ((addr & 0xFFFB) == 0xC082)
428 WriteLog("LC(R): $C082 49282 OECG R Read ROM; no write\n");
430 //$C082 49282 OECG R Read ROM; no write
431 visibleBank = LC_BANK_2;
435 else if ((addr & 0xFFFB) == 0xC083)
438 WriteLog("LC(R): $C083 49283 OECG RR Read/Write RAM bank 2\n");
440 //$C083 49283 LCBANK2 OECG RR Read/write RAM bank 2
441 visibleBank = LC_BANK_2;
445 else if ((addr & 0xFFFB) == 0xC088)
448 WriteLog("LC(R): $%04X 49288 OECG R Read RAM bank 1; no write\n", addr);
450 //$C088 49288 OECG R Read RAM bank 1; no write
451 visibleBank = LC_BANK_1;
454 //Hm. Some stuff seems to want this.
455 //nope, was looking at $C0E8... return 0xFF;
457 else if ((addr & 0xFFFB) == 0xC089)
460 WriteLog("LC(R): $C089 49289 OECG RR Read ROM; write RAM bank 1\n");
462 //$C089 49289 OECG RR Read ROM; write RAM bank 1
463 visibleBank = LC_BANK_1;
467 else if ((addr & 0xFFFB) == 0xC08A)
470 WriteLog("LC(R): $C08A 49290 OECG R Read ROM; no write\n");
472 //$C08A 49290 OECG R Read ROM; no write
473 visibleBank = LC_BANK_1;
477 else if ((addr & 0xFFFB) == 0xC08B)
480 WriteLog("LC(R): $C08B 49291 OECG RR Read/Write RAM bank 1\n");
482 //$C08B 49291 OECG RR Read/write RAM bank 1
483 visibleBank = LC_BANK_1;
487 else if ((addr & 0xFFF8) == 0xC0E0)
489 floppyDrive.ControlStepper(addr & 0x07);
491 else if ((addr & 0xFFFE) == 0xC0E8)
493 floppyDrive.ControlMotor(addr & 0x01);
495 else if ((addr & 0xFFFE) == 0xC0EA)
497 floppyDrive.DriveEnable(addr & 0x01);
499 else if (addr == 0xC0EC)
501 return floppyDrive.ReadWrite();
503 else if (addr == 0xC0ED)
505 return floppyDrive.GetLatchValue();
507 else if (addr == 0xC0EE)
509 floppyDrive.SetReadMode();
511 else if (addr == 0xC0EF)
513 floppyDrive.SetWriteMode();
516 //#define LC_DEBUGGING
518 bool showpath = false;
519 if (addr >= 0xD000 && addr <= 0xD00F)
523 if (addr >= 0xC100 && addr <= 0xCFFF) // The $C000-$CFFF block is *never* RAM
530 WriteLog("b is from $C100-$CFFF block...\n");
533 else if (addr >= 0xD000)
537 if (addr <= 0xDFFF && visibleBank == LC_BANK_1)
541 b = ram[addr - 0x1000];
544 WriteLog("b is from LC bank #1 (ram[addr - 0x1000])...\n");
554 WriteLog("b is from LC bank #2 (ram[addr])...\n");
565 WriteLog("b is from LC ROM (rom[addr])...\n");
576 WriteLog("b is from ram[addr]...\n");
581 if (addr >= 0xD000 && addr <= 0xD00F)
583 WriteLog("*** Read from $%04X: $%02X (readRAM=%s, PC=%04X, ram$D000=%02X)\n", addr, b, (readRAM ? "T" : "F"), mainCPU.pc, ram[0xC000]);
592 APPENDIX F Assembly Language Program Listings
598 5 * ;ADDRESSES FOR FIRST 6522
599 6 ORB EQU $C400 ;PORT B
600 7 ORA EQU $C401 ;PORT A
601 8 DDRB EQU $C402 ;DATA DIRECTION REGISTER (A)
602 9 DDRA EQU $C403 ;DATA DIRECTION REGISTER (B)
603 10 * ;ADDRESSES FOR SECOND 6522
604 11 ORB2 EQU $C480 ;PORT B
605 12 ORA2 EQU $C481 ;PORT A
606 13 DDRB2 EQU $C482 ;DATA DIRECTION REGISTER (B)
607 14 DDRA2 EQU $C483 ;DATA DIRECTION REGISTER (A)
609 void WrMem(uint16_t addr, uint8_t b)
612 //extern V6809REGS regs;
613 //if (addr >= 0xC800 && addr <= 0xCBFE)
614 //if (addr == 0xC80F || addr == 0xC80D)
615 // WriteLog("WrMem: Writing address %04X with %02X [PC=%04X, $CB00=%02X]\n", addr, b, regs.pc, gram[0xCB00]);//*/
618 if (addr >= 0xC000 && addr <= 0xC0FF)
619 WriteLog("\n*** Write at I/O address %04X\n", addr);
622 Check the BIKO version on Asimov to see if it's been cracked or not...
624 7F3D: 29 07 AND #$07 [PC=7F3F, SP=01EA, CC=---B-I--, A=01, X=4B, Y=00]
625 7F3F: C9 06 CMP #$06 [PC=7F41, SP=01EA, CC=N--B-I--, A=01, X=4B, Y=00]
626 7F41: 90 03 BCC $7F46 [PC=7F46, SP=01EA, CC=N--B-I--, A=01, X=4B, Y=00]
627 [7F43: 4C 83 7E JMP $7E83] <- Skipped over... (Prints "THANK YOU VERY MUCH!")
628 7F46: AA TAX [PC=7F47, SP=01EA, CC=---B-I--, A=01, X=01, Y=00]
630 ; INX here *ensures* 1 - 6!!! BUG!!!
631 ; Or is it? Could this be part of a braindead copy protection scheme? It's
632 ; awfully close to NOP ($EA)...
633 ; Nothing else touches it once it's been written... Hmm...
635 7F47: E8 INX [PC=7F48, SP=01EA, CC=---B-I--, A=01, X=02, Y=00]
636 7F48: F8 SED [PC=7F49, SP=01EA, CC=---BDI--, A=01, X=02, Y=00]
637 7F49: 18 CLC [PC=7F4A, SP=01EA, CC=---BDI--, A=01, X=02, Y=00]
638 7F4A: BD 15 4E LDA $4E15,X [PC=7F4D, SP=01EA, CC=---BDI--, A=15, X=02, Y=00]
641 ; 4E15: 25 25 15 15 10 20
642 ; 4E1B: 03 41 99 99 01 00 12
645 7F4D: 65 FC ADC $FC [PC=7F4F, SP=01EA, CC=---BDI--, A=16, X=02, Y=00]
646 7F4F: 65 FC ADC $FC [PC=7F51, SP=01EA, CC=---BDI--, A=17, X=02, Y=00]
647 7F51: 65 FC ADC $FC [PC=7F53, SP=01EA, CC=---BDI--, A=18, X=02, Y=00]
648 7F53: 65 FC ADC $FC [PC=7F55, SP=01EA, CC=---BDI--, A=19, X=02, Y=00]
650 ; NO checking is done on the raised stat! Aarrrgggghhhhh!
652 7F55: 9D 15 4E STA $4E15,X [PC=7F58, SP=01EA, CC=---BDI--, A=19, X=02, Y=00]
653 7F58: D8 CLD [PC=7F59, SP=01EA, CC=---B-I--, A=19, X=02, Y=00]
655 ; Print "ALAKAZAM!" and so on...
657 7F59: 20 2C 40 JSR $402C [PC=402C, SP=01E8, CC=---B-I--, A=19, X=02, Y=00]
661 WriteLog("\n*** Byte %02X written at address %04X\n", b, addr);
664 I think this is IIc/IIe only...
666 CLR80STORE=$C000 ;80STORE Off- disable 80-column memory mapping (Write)
667 SET80STORE=$C001 ;80STORE On- enable 80-column memory mapping (WR-only)
669 CLRAUXRD = $C002 ;read from main 48K (WR-only)
670 SETAUXRD = $C003 ;read from aux/alt 48K (WR-only)
672 CLRAUXWR = $C004 ;write to main 48K (WR-only)
673 SETAUXWR = $C005 ;write to aux/alt 48K (WR-only)
675 CLRCXROM = $C006 ;use ROM on cards (WR-only)
676 SETCXROM = $C007 ;use internal ROM (WR-only)
678 CLRAUXZP = $C008 ;use main zero page, stack, & LC (WR-only)
679 SETAUXZP = $C009 ;use alt zero page, stack, & LC (WR-only)
681 CLRC3ROM = $C00A ;use internal Slot 3 ROM (WR-only)
682 SETC3ROM = $C00B ;use external Slot 3 ROM (WR-only)
684 CLR80VID = $C00C ;disable 80-column display mode (WR-only)
685 SET80VID = $C00D ;enable 80-column display mode (WR-only)
687 CLRALTCH = $C00E ;use main char set- norm LC, Flash UC (WR-only)
688 SETALTCH = $C00F ;use alt char set- norm inverse, LC; no Flash (WR-only)
692 alternateCharset = false;
694 else if (addr == 0xC00F)
696 alternateCharset = true;
698 else if ((addr & 0xFFF0) == 0xC010) // Keyboard strobe
700 //Actually, according to the A2 ref, this should do nothing since a write
701 //is immediately preceded by a read leaving it in the same state it was...
702 //But leaving this out seems to fuck up the key handling of some games...
705 else if (addr == 0xC050)
709 else if (addr == 0xC051)
713 else if (addr == 0xC052)
717 else if (addr == 0xC053)
721 else if (addr == 0xC054)
723 displayPage2 = false;
725 else if (addr == 0xC055)
729 else if (addr == 0xC056)
733 else if (addr == 0xC057)
737 else if ((addr & 0xFFFB) == 0xC080)
740 WriteLog("LC(R): $C080 49280 OECG R Read RAM bank 2; no write\n");
742 //$C080 49280 OECG R Read RAM bank 2; no write
743 visibleBank = LC_BANK_2;
747 else if ((addr & 0xFFFB) == 0xC081)
750 WriteLog("LC(R): $C081 49281 OECG RR Read ROM; write RAM bank 2\n");
752 //$C081 49281 ROMIN OECG RR Read ROM; write RAM bank 2
753 visibleBank = LC_BANK_2;
757 else if ((addr & 0xFFFB) == 0xC082)
760 WriteLog("LC(R): $C082 49282 OECG R Read ROM; no write\n");
762 //$C082 49282 OECG R Read ROM; no write
763 visibleBank = LC_BANK_2;
767 else if ((addr & 0xFFFB) == 0xC083)
770 WriteLog("LC(R): $C083 49283 OECG RR Read/Write RAM bank 2\n");
772 //$C083 49283 LCBANK2 OECG RR Read/write RAM bank 2
773 visibleBank = LC_BANK_2;
777 else if ((addr & 0xFFFB) == 0xC088)
780 WriteLog("LC(R): $C088 49288 OECG R Read RAM bank 1; no write\n");
782 //$C088 49288 OECG R Read RAM bank 1; no write
783 visibleBank = LC_BANK_1;
787 else if ((addr & 0xFFFB) == 0xC089)
790 WriteLog("LC(R): $C089 49289 OECG RR Read ROM; write RAM bank 1\n");
792 //$C089 49289 OECG RR Read ROM; write RAM bank 1
793 visibleBank = LC_BANK_1;
797 else if ((addr & 0xFFFB) == 0xC08A)
800 WriteLog("LC(R): $C08A 49290 OECG R Read ROM; no write\n");
802 //$C08A 49290 OECG R Read ROM; no write
803 visibleBank = LC_BANK_1;
807 else if ((addr & 0xFFFB) == 0xC08B)
810 WriteLog("LC(R): $C08B 49291 OECG RR Read/Write RAM bank 1\n");
812 //$C08B 49291 OECG RR Read/write RAM bank 1
813 visibleBank = LC_BANK_1;
817 //This is determined by which slot it is in--this assumes slot 6. !!! FIX !!!
818 else if ((addr & 0xFFF8) == 0xC0E0)
820 floppyDrive.ControlStepper(addr & 0x07);
822 else if ((addr & 0xFFFE) == 0xC0E8)
824 floppyDrive.ControlMotor(addr & 0x01);
826 else if ((addr & 0xFFFE) == 0xC0EA)
828 floppyDrive.DriveEnable(addr & 0x01);
830 else if (addr == 0xC0EC)
832 //change this to Write()? (and the other to Read()?) Dunno. Seems to work OK, but still...
834 floppyDrive.ReadWrite();
836 else if (addr == 0xC0ED)
838 floppyDrive.SetLatchValue(b);
840 else if (addr == 0xC0EE)
842 floppyDrive.SetReadMode();
844 else if (addr == 0xC0EF)
846 floppyDrive.SetWriteMode();
848 //Still need to add missing I/O switches here...
850 //DEEE: BD 10 BF LDA $BF10,X [PC=DEF1, SP=01F4, CC=--.B-IZ-, A=00, X=0C, Y=07]
852 if (addr >= 0xD000 && addr <= 0xD00F)
854 WriteLog("*** Write to $%04X: $%02X (writeRAM=%s, PC=%04X, ram$D000=%02X)\n", addr, b, (writeRAM ? "T" : "F"), mainCPU.pc, ram[0xC000]);
857 if (addr >= 0xC000 && addr <= 0xCFFF)
858 return; // Protect LC bank #1 from being written to!
864 if (addr <= 0xDFFF && visibleBank == LC_BANK_1)
865 ram[addr - 0x1000] = b;
878 // Load a file into RAM/ROM image space
880 bool LoadImg(char * filename, uint8_t * ram, int size)
882 FILE * fp = fopen(filename, "rb");
887 fread(ram, 1, size, fp);
894 static void SaveApple2State(const char * filename)
899 static bool LoadApple2State(const char * filename)
905 #ifdef CPU_CLOCK_CHECKING
907 uint32_t totalCPU = 0;
908 uint64_t lastClock = 0;
913 int main(int /*argc*/, char * /*argv*/[])
915 InitLog("./apple2.log");
917 srand(time(NULL)); // Initialize RNG
920 //Need to bankify this stuff for the IIe emulation...
921 memset(ram, 0, 0x10000);
922 memset(rom, 0, 0x10000);
923 memset(ram2, 0, 0x10000);
925 // Set up V65C02 execution context
926 memset(&mainCPU, 0, sizeof(V65C02REGS));
927 mainCPU.RdMem = RdMem;
928 mainCPU.WrMem = WrMem;
929 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
931 if (!LoadImg(settings.BIOSPath, rom + 0xD000, 0x3000))
933 WriteLog("Could not open file '%s'!\n", settings.BIOSPath);
937 //This is now included...
938 /* if (!LoadImg(settings.diskPath, diskRom, 0x100))
940 WriteLog("Could not open file '%s'!\nDisk II will be unavailable!\n", settings.diskPath);
944 //Load up disk image from config file (for now)...
945 floppyDrive.LoadImage(settings.diskImagePath1, 0);
946 floppyDrive.LoadImage(settings.diskImagePath2, 1);
947 // floppyDrive.LoadImage("./disks/temp.nib", 1); // Load temp .nib file into second drive...
949 //Kill the DOS ROM in slot 6 for now...
951 memcpy(rom + 0xC600, diskROM, 0x100);
952 // memcpy(rom + 0xC700, diskROM, 0x100); // Slot 7???
954 WriteLog("About to initialize video...\n");
958 std::cout << "Aborting!" << std::endl;
962 // Have to do this *after* video init but *before* sound init...!
963 //Shouldn't be necessary since we're not doing emulation in the ISR...
964 if (settings.autoStateSaving)
966 // Load last state from file...
967 if (!LoadApple2State(settings.autoStatePath))
968 WriteLog("Unable to use Apple2 state file \"%s\"!\n", settings.autoStatePath);
974 if (!LoadImg("./BT1_6502_RAM_SPACE.bin", ram, 0x10000))
976 cout << "Couldn't load state file!" << endl;
977 cout << "Aborting!!" << endl;
982 //-- -- -- -- ----- -----
983 //00 75 3B 53 FD 01 41 44
985 mainCPU.cpuFlags = 0;
995 displayPage2 = false;
1002 memcpy(ram + 0xD000, ram + 0xC000, 0x1000);
1005 WriteLog("About to initialize audio...\n");
1007 //nope SDL_EnableUNICODE(1); // Needed to do key translation shit
1009 // gui = new GUI(surface); // Set up the GUI system object...
1010 // gui = new GUI(mainSurface); // Set up the GUI system object...
1011 // SDL 2... this will likely cause Apple 2 to crash
1012 // gui = new GUI(NULL); // Set up the GUI system object...
1014 gui->AddMenuTitle("Apple2");
1015 gui->AddMenuItem("Test!", TestWindow/*, hotkey*/);
1016 gui->AddMenuItem("");
1017 gui->AddMenuItem("Quit", QuitEmulator, SDLK_q);
1018 gui->CommitItemsToMenu();
1021 SetupBlurTable(); // Set up the color TV emulation blur table
1022 running = true; // Set running status...
1024 InitializeEventList(); // Clear the event list before we use it...
1025 SetCallbackTime(FrameCallback, 16666.66666667); // Set frame to fire at 1/60 s interval
1026 SetCallbackTime(BlinkTimer, 250000); // Set up blinking at 1/4 s intervals
1027 startTicks = SDL_GetTicks();
1029 #ifdef THREADED_65C02
1030 cpuCond = SDL_CreateCond();
1031 mainSem = SDL_CreateSemaphore(1);
1032 cpuThread = SDL_CreateThread(CPUThreadFunc, NULL, NULL);
1033 //Hmm... CPU does POST (+1), wait, then WAIT (-1)
1034 // SDL_sem * mainMutex = SDL_CreateMutex();
1037 WriteLog("Entering main loop...\n");
1040 double timeToNextEvent = GetTimeToNextEvent();
1041 #ifndef THREADED_65C02
1042 Execute65C02(&mainCPU, USEC_TO_M6502_CYCLES(timeToNextEvent));
1044 //We MUST remove a frame's worth of time in order for the CPU to function... !!! FIX !!!
1045 //(Fix so that this is not a requirement!)
1046 //Fixed, but mainCPU.clock is destroyed in the bargain. Oh well.
1047 // mainCPU.clock -= USEC_TO_M6502_CYCLES(timeToNextEvent);
1049 #ifdef CPU_CLOCK_CHECKING
1050 #ifndef THREADED_65C02
1051 totalCPU += USEC_TO_M6502_CYCLES(timeToNextEvent);
1054 // Handle CPU time delta for sound...
1055 //Don't need this anymore now that we use absolute time...
1056 // AddToSoundTimeBase(USEC_TO_M6502_CYCLES(timeToNextEvent));
1060 #ifdef THREADED_65C02
1061 WriteLog("Main: cpuFinished = true;\n");
1063 //#warning "If sound thread is behind, CPU thread will never wake up... !!! FIX !!!" [DONE]
1064 //What to do? How do you know when the CPU is sleeping???
1065 //USE A CONDITIONAL!!! OF COURSE!!!!!!11!11!11!!!1!
1067 SDL_mutexP(mainMutex);
1068 SDL_CondWait(mainCond, mainMutex); // Wait for CPU thread to get to signal point...
1069 SDL_mutexV(mainMutex);
1071 //Nope, use a semaphore...
1072 WriteLog("Main: SDL_SemWait(mainSem);\n");
1073 SDL_SemWait(mainSem);//should lock until CPU thread is waiting...
1076 WriteLog("Main: SDL_CondSignal(cpuCond);//thread is probably asleep, wake it up\n");
1077 SDL_CondSignal(cpuCond);//thread is probably asleep, wake it up
1078 WriteLog("Main: SDL_WaitThread(cpuThread, NULL);\n");
1079 SDL_WaitThread(cpuThread, NULL);
1080 //nowok:SDL_WaitThread(CPUThreadFunc, NULL);
1081 WriteLog("Main: SDL_DestroyCond(cpuCond);\n");
1082 SDL_DestroyCond(cpuCond);
1084 //SDL_DestroyMutex(mainMutex);
1085 SDL_DestroySemaphore(mainSem);
1088 if (settings.autoStateSaving)
1090 // Save state here...
1091 SaveApple2State(settings.autoStatePath);
1093 floppyDrive.SaveImage();
1109 -----------------------
1110 space $A0 $A0 $A0 $A0 No xlation
1111 RETURN $8D $8D $8D $8D No xlation
1112 0 $B0 $B0 $B0 $B0 Need to screen shift+0 (?)
1113 1! $B1 $B1 $A1 $A1 No xlation
1114 2" $B2 $B2 $A2 $A2 No xlation
1115 3# $B3 $B3 $A3 $A3 No xlation
1116 4$ $B4 $B4 $A4 $A4 No xlation
1117 5% $B5 $B5 $A5 $A5 No xlation
1118 6& $B6 $B6 $A6 $A6 No xlation
1119 7' $B7 $B7 $A7 $A7 No xlation
1120 8( $B8 $B8 $A8 $A8 No xlation
1121 9) $B9 $B9 $A9 $A9 No xlation
1122 :* $BA $BA $AA $AA No xlation
1123 ;+ $BB $BB $AB $AB No xlation
1124 ,< $AC $AC $BC $BC No xlation
1125 -= $AD $AD $BD $BD No xlation
1126 .> $AE $AE $BE $BE No xlation
1127 /? $AF $AF $BF $BF No xlation
1140 M $CD $8D $DD $9D -> ODD
1141 N^ $CE $8E $DE $9E -> ODD
1143 P@ $D0 $90 $C0 $80 Need to xlate CTL+SHFT+P & SHFT+P (?)
1156 ESC $9B $9B $9B $9B No xlation
1159 static uint64_t lastCPUCycles = 0;
1160 static uint32_t frameCount = 0;
1161 static void FrameCallback(void)
1165 while (SDL_PollEvent(&event))
1170 //Need to do some key translation here, and screen out non-apple keys as well...
1171 //(really, could do it all in SDL_KEYDOWN, would just have to get symbols &
1172 // everything else done separately. this is slightly easier. :-P)
1173 // if (event.key.keysym.sym == SDLK_TAB) // Prelim key screening...
1174 if (event.edit.text[0] == '\t') // Prelim key screening...
1177 lastKeyPressed = event.edit.text[0];
1180 //kludge: should have a caps lock thingy here...
1181 //or all uppercase for ][+...
1182 if (lastKeyPressed >= 'a' && lastKeyPressed <='z')
1183 lastKeyPressed &= 0xDF; // Convert to upper case...
1187 // CTRL+RESET key emulation (mapped to CTRL+`)
1188 // This doesn't work...
1189 // if (event.key.keysym.sym == SDLK_BREAK && (event.key.keysym.mod & KMOD_CTRL))
1190 // if (event.key.keysym.sym == SDLK_PAUSE && (event.key.keysym.mod & KMOD_CTRL))
1191 if (event.key.keysym.sym == SDLK_BACKQUOTE && (event.key.keysym.mod & KMOD_CTRL))
1192 //NOTE that this shouldn't take place until the key is lifted... !!! FIX !!!
1193 //ALSO it seems to leave the machine in an inconsistent state vis-a-vis the language card...
1194 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
1196 if (event.key.keysym.sym == SDLK_RIGHT)
1197 lastKeyPressed = 0x15, keyDown = true;
1198 else if (event.key.keysym.sym == SDLK_LEFT)
1199 lastKeyPressed = 0x08, keyDown = true;
1200 else if (event.key.keysym.sym == SDLK_RETURN)
1201 lastKeyPressed = 0x0D, keyDown = true;
1202 else if (event.key.keysym.sym == SDLK_ESCAPE)
1203 lastKeyPressed = 0x1B, keyDown = true;
1205 // Fix CTRL+key combo...
1206 if (event.key.keysym.mod & KMOD_CTRL)
1208 if (event.key.keysym.sym >= SDLK_a && event.key.keysym.sym <= SDLK_z)
1210 lastKeyPressed = (event.key.keysym.sym - SDLK_a) + 1;
1212 //printf("Key combo pressed: CTRL+%c\n", lastKeyPressed + 0x40);
1216 // Use ALT+Q to exit, as well as the usual window decoration method
1217 if (event.key.keysym.sym == SDLK_q && (event.key.keysym.mod & KMOD_ALT))
1220 // Paddle buttons 0 & 1
1221 if (event.key.keysym.sym == SDLK_INSERT)
1222 openAppleDown = true;
1223 if (event.key.keysym.sym == SDLK_PAGEUP)
1224 closedAppleDown = true;
1226 if (event.key.keysym.sym == SDLK_F11)
1227 dumpDis = !dumpDis; // Toggle the disassembly process
1228 // else if (event.key.keysym.sym == SDLK_F11)
1229 // floppyDrive.LoadImage("./disks/bt1_char.dsk");//Kludge to load char disk...
1230 else if (event.key.keysym.sym == SDLK_F9)
1232 floppyDrive.CreateBlankImage();
1233 // SpawnMessage("Image cleared...");
1235 else if (event.key.keysym.sym == SDLK_F10)
1237 floppyDrive.SwapImages();
1238 // SpawnMessage("Image swapped...");
1241 if (event.key.keysym.sym == SDLK_F2)// Toggle the palette
1243 else if (event.key.keysym.sym == SDLK_F3)// Cycle through screen types
1246 // if (event.key.keysym.sym == SDLK_F5) // Temp GUI launch key
1247 if (event.key.keysym.sym == SDLK_F1) // GUI launch key
1248 //NOTE: Should parse the output to determine whether or not the user requested
1249 // to quit completely... !!! FIX !!!
1252 if (event.key.keysym.sym == SDLK_F5)
1255 char volStr[19] = "[****************]";
1256 // volStr[GetVolume()] = 0;
1257 for(int i=GetVolume(); i<16; i++)
1258 volStr[1 + i] = '-';
1259 SpawnMessage("Volume: %s", volStr);
1261 else if (event.key.keysym.sym == SDLK_F6)
1264 char volStr[19] = "[****************]";
1265 // volStr[GetVolume()] = 0;
1266 for(int i=GetVolume(); i<16; i++)
1267 volStr[1 + i] = '-';
1268 SpawnMessage("Volume: %s", volStr);
1271 static bool fullscreenDebounce = false;
1273 if (event.key.keysym.sym == SDLK_F12)
1275 if (!fullscreenDebounce)
1278 fullscreenDebounce = true;
1285 if (event.key.keysym.sym == SDLK_F12)
1286 fullscreenDebounce = false;
1288 // Paddle buttons 0 & 1
1289 if (event.key.keysym.sym == SDLK_INSERT)
1290 openAppleDown = false;
1291 if (event.key.keysym.sym == SDLK_PAGEUP)
1292 closedAppleDown = false;
1294 // if (event.key.keysym.sym >= SDLK_a && event.key.keysym.sym <= SDLK_z)
1303 //#warning "!!! Taking MAJOR time hit with the video frame rendering !!!"
1305 SetCallbackTime(FrameCallback, 16666.66666667);
1307 #ifdef CPU_CLOCK_CHECKING
1308 //We know it's stopped, so we can get away with this...
1312 uint64_t clock = GetCurrentV65C02Clock();
1313 //totalCPU += (uint32_t)(clock - lastClock);
1315 printf("Executed %u cycles...\n", (uint32_t)(clock - lastClock));
1321 //Instead of this, we should yield remaining time to other processes... !!! FIX !!! [DONE]
1324 //Actually, slows things down too much...
1326 // while (SDL_GetTicks() - startTicks < 16); // Wait for next frame...
1328 // This is the problem: If you set the interval to 16, it runs faster than
1329 // 1/60s per frame. If you set it to 17, it runs slower. What we need is to
1330 // have it do 16 for one frame, then 17 for two others. Then it should average
1331 // out to 1/60s per frame every 3 frames.
1332 frameCount = (frameCount + 1) % 3;
1334 uint32_t waitFrameTime = 17 - (frameCount == 0 ? 1 : 0);
1336 while (SDL_GetTicks() - startTicks < waitFrameTime)
1337 SDL_Delay(1); // Wait for next frame...
1339 startTicks = SDL_GetTicks();
1341 uint64_t cpuCycles = GetCurrentV65C02Clock();
1342 uint32_t cyclesBurned = (uint32_t)(cpuCycles - lastCPUCycles);
1343 WriteLog("FrameCallback: used %i cycles\n", cyclesBurned);
1344 lastCPUCycles = cpuCycles;
1347 //let's wait, then signal...
1348 //works longer, but then still falls behind...
1349 #ifdef THREADED_65C02
1350 SDL_CondSignal(cpuCond);//OK, let the CPU go another frame...
1355 static void BlinkTimer(void)
1358 SetCallbackTime(BlinkTimer, 250000); // Set up blinking at 1/4 sec intervals
1363 Next problem is this: How to have events occur and synchronize with the rest
1366 o Have the CPU thread manage the timer mechanism? (need to have a method of carrying
1367 remainder CPU cycles over...)
1369 One way would be to use a fractional accumulator, then subtract 1 every
1370 time it overflows. Like so:
1372 double overflow = 0;
1376 Execute6808(&soundCPU, time);
1377 overflow += 0.289115646;