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;
116 // Let's try a thread...
118 Here's how it works: Execute 1 frame's worth, then sleep.
119 Other stuff wakes it up
121 int CPUThreadFunc(void * data)
123 // Mutex must be locked for conditional to work...
124 // Also, must be created in the thread that uses it...
125 SDL_mutex * cpuMutex = SDL_CreateMutex();
127 // decrement mainSem...
128 //SDL_SemWait(mainSem);
129 #ifdef CPU_THREAD_OVERFLOW_COMPENSATION
130 float overflow = 0.0;
136 SDL_CondWait(cpuCond, cpuMutex);
138 // decrement mainSem...
139 #ifdef THREAD_DEBUGGING
140 WriteLog("CPU: SDL_SemWait(mainSem);\n");
142 SDL_SemWait(mainSem);
144 uint32_t cycles = 17066;
145 #ifdef CPU_THREAD_OVERFLOW_COMPENSATION
146 // ODD! It's closer *without* this overflow compensation. ??? WHY ???
147 overflow += 0.666666667;
156 #ifdef THREAD_DEBUGGING
157 WriteLog("CPU: Execute65C02(&mainCPU, cycles);\n");
159 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!
161 // Adjust the sound routine's last cycle toggled time base
162 // Also, since we're finished executing, .clock is now valid
163 #ifdef THREAD_DEBUGGING
164 WriteLog("CPU: AdjustLastToggleCycles(mainCPU.clock);\n");
166 AdjustLastToggleCycles(mainCPU.clock);
168 #ifdef THREAD_DEBUGGING
169 WriteLog("CPU: SDL_mutexP(cpuMutex);\n");
171 SDL_mutexP(cpuMutex);
173 if (SDL_CondWait(cpuCond, cpuMutex) != 0)
175 printf("SDL_CondWait != 0! (Error: '%s')\n", SDL_GetError());
179 // increment mainSem...
180 #ifdef THREAD_DEBUGGING
181 WriteLog("CPU: SDL_SemPost(mainSem);\n");
183 SDL_SemPost(mainSem);
184 // SDL_CondSignal(mainCond); // In case something is waiting on us...
186 #ifdef THREAD_DEBUGGING
187 WriteLog("CPU: SDL_CondWait(cpuCond, cpuMutex);\n");
189 SDL_CondWait(cpuCond, cpuMutex);
192 #ifdef THREAD_DEBUGGING
193 WriteLog("CPU: SDL_mutexV(cpuMutex);\n");
195 SDL_mutexV(cpuMutex);
197 while (!cpuFinished);
199 SDL_DestroyMutex(cpuMutex);
207 Element * TestWindow(void)
209 Element * win = new DraggableWindow2(10, 10, 128, 128);
210 // ((DraggableWindow *)win)->AddElement(new TextEdit(4, 16, 92, 0, "u2prog.dsk", win));
215 Element * QuitEmulator(void)
224 Small Apple II memory map:
226 $C010 - Clear bit 7 of keyboard data ($C000)
227 $C030 - Toggle speaker diaphragm
229 $C054 - Select page 1
230 $C056 - Select lo-res
231 $C058 - Set annuciator-0 output to 0
232 $C05A - Set annuciator-0 output to 0
233 $C05D - Set annuciator-0 output to 1
234 $C05F - Set annuciator-0 output to 1
235 $C0E0 - Disk control stepper ($C0E0-7)
236 $C0E9 - Disk control motor (on)
237 $C0EA - Disk enable (drive 1)
239 $C0EE - Disk set read mode
243 // V65C02 read/write memory functions
246 uint8_t RdMem(uint16_t addr)
251 if (addr >= 0xC000 && addr <= 0xC0FF)
252 WriteLog("\n*** Read at I/O address %04X\n", addr);
255 if (addr >= 0xC080 && addr <= 0xC08F)
256 WriteLog("\n*** Read at I/O address %04X\n", addr);
259 if ((addr & 0xFFF0) == 0xC000) // Read $C000-$C00F
261 return lastKeyPressed | (keyDown ? 0x80 : 0x00);
263 else if ((addr & 0xFFF0) == 0xC010) // Read $C010-$C01F
265 //This is bogus: keyDown is set to false, so return val NEVER is set...
267 //Also, this is IIe/IIc only...!
268 uint8_t retVal = lastKeyPressed | (keyDown ? 0x80 : 0x00);
272 else if ((addr & 0xFFF0) == 0xC030) // Read $C030-$C03F
275 This is problematic, mainly because the v65C02 removes actual cycles run after each call.
276 Therefore, we don't really have a reliable method of sending a timestamp to the sound routine.
279 What we need to send is a delta T value but related to the IRQ buffer routine. E.g., if the buffer
280 hasn't had any changes in it then we just fill it with the last sample value and are done. Then
281 we need to adjust our delta T accordingly. What we could do is keep a running total of time since the
282 last change and adjust it accordingly, i.e., whenever a sound IRQ happens.
285 Have deltaT somewhere. Then, whenever there's a toggle, backfill buffer with last spkr state and reset
286 deltaT to zero. In the sound IRQ, if deltaT > buffer size, then subtract buffer size from deltaT. (?)
291 ToggleSpeaker(GetCurrentV65C02Clock());
292 //should it return something else here???
295 else if (addr == 0xC050) // Read $C050
299 else if (addr == 0xC051) // Read $C051
303 else if (addr == 0xC052) // Read $C052
307 else if (addr == 0xC053) // Read $C053
311 else if (addr == 0xC054) // Read $C054
313 displayPage2 = false;
315 else if (addr == 0xC055) // Read $C055
319 else if (addr == 0xC056) // Read $C056
323 else if (addr == 0xC057) // Read $C057
327 else if (addr == 0xC061) // Read $C061
329 // Open Apple key (or push button 0)
330 return (openAppleDown ? 0x80 : 0x00);
332 else if (addr == 0xC062) // Read $C062
334 // Open Apple key (or push button 0)
335 return (closedAppleDown ? 0x80 : 0x00);
338 //Note that this is a kludge: The $D000-$DFFF 4K space is shared (since $C000-$CFFF is
339 //memory mapped) between TWO banks, and that that $E000-$FFFF RAM space is a single bank.
340 //[SHOULD BE FIXED NOW]
341 //OK! This switch selects bank 2 of the 4K bank at $D000-$DFFF. One access makes it
342 //visible, two makes it R/W.
377 else if ((addr & 0xFFFB) == 0xC080)
380 WriteLog("LC(R): $C080 49280 OECG R Read RAM bank 2; no write\n");
382 //$C080 49280 OECG R Read RAM bank 2; no write
383 visibleBank = LC_BANK_2;
387 else if ((addr & 0xFFFB) == 0xC081)
390 WriteLog("LC(R): $C081 49281 OECG RR Read ROM; write RAM bank 2\n");
392 //$C081 49281 ROMIN OECG RR Read ROM; write RAM bank 2
393 visibleBank = LC_BANK_2;
397 else if ((addr & 0xFFFB) == 0xC082)
400 WriteLog("LC(R): $C082 49282 OECG R Read ROM; no write\n");
402 //$C082 49282 OECG R Read ROM; no write
403 visibleBank = LC_BANK_2;
407 else if ((addr & 0xFFFB) == 0xC083)
410 WriteLog("LC(R): $C083 49283 OECG RR Read/Write RAM bank 2\n");
412 //$C083 49283 LCBANK2 OECG RR Read/write RAM bank 2
413 visibleBank = LC_BANK_2;
417 else if ((addr & 0xFFFB) == 0xC088)
420 WriteLog("LC(R): $%04X 49288 OECG R Read RAM bank 1; no write\n", addr);
422 //$C088 49288 OECG R Read RAM bank 1; no write
423 visibleBank = LC_BANK_1;
426 //Hm. Some stuff seems to want this.
427 //nope, was looking at $C0E8... return 0xFF;
429 else if ((addr & 0xFFFB) == 0xC089)
432 WriteLog("LC(R): $C089 49289 OECG RR Read ROM; write RAM bank 1\n");
434 //$C089 49289 OECG RR Read ROM; write RAM bank 1
435 visibleBank = LC_BANK_1;
439 else if ((addr & 0xFFFB) == 0xC08A)
442 WriteLog("LC(R): $C08A 49290 OECG R Read ROM; no write\n");
444 //$C08A 49290 OECG R Read ROM; no write
445 visibleBank = LC_BANK_1;
449 else if ((addr & 0xFFFB) == 0xC08B)
452 WriteLog("LC(R): $C08B 49291 OECG RR Read/Write RAM bank 1\n");
454 //$C08B 49291 OECG RR Read/write RAM bank 1
455 visibleBank = LC_BANK_1;
459 else if ((addr & 0xFFF8) == 0xC0E0)
461 floppyDrive.ControlStepper(addr & 0x07);
463 else if ((addr & 0xFFFE) == 0xC0E8)
465 floppyDrive.ControlMotor(addr & 0x01);
467 else if ((addr & 0xFFFE) == 0xC0EA)
469 floppyDrive.DriveEnable(addr & 0x01);
471 else if (addr == 0xC0EC)
473 return floppyDrive.ReadWrite();
475 else if (addr == 0xC0ED)
477 return floppyDrive.GetLatchValue();
479 else if (addr == 0xC0EE)
481 floppyDrive.SetReadMode();
483 else if (addr == 0xC0EF)
485 floppyDrive.SetWriteMode();
488 //#define LC_DEBUGGING
490 bool showpath = false;
491 if (addr >= 0xD000 && addr <= 0xD00F)
495 if (addr >= 0xC100 && addr <= 0xCFFF) // The $C000-$CFFF block is *never* RAM
502 WriteLog("b is from $C100-$CFFF block...\n");
505 else if (addr >= 0xD000)
509 if (addr <= 0xDFFF && visibleBank == LC_BANK_1)
513 b = ram[addr - 0x1000];
516 WriteLog("b is from LC bank #1 (ram[addr - 0x1000])...\n");
526 WriteLog("b is from LC bank #2 (ram[addr])...\n");
537 WriteLog("b is from LC ROM (rom[addr])...\n");
548 WriteLog("b is from ram[addr]...\n");
553 if (addr >= 0xD000 && addr <= 0xD00F)
555 WriteLog("*** Read from $%04X: $%02X (readRAM=%s, PC=%04X, ram$D000=%02X)\n", addr, b, (readRAM ? "T" : "F"), mainCPU.pc, ram[0xC000]);
564 APPENDIX F Assembly Language Program Listings
570 5 * ;ADDRESSES FOR FIRST 6522
571 6 ORB EQU $C400 ;PORT B
572 7 ORA EQU $C401 ;PORT A
573 8 DDRB EQU $C402 ;DATA DIRECTION REGISTER (A)
574 9 DDRA EQU $C403 ;DATA DIRECTION REGISTER (B)
575 10 * ;ADDRESSES FOR SECOND 6522
576 11 ORB2 EQU $C480 ;PORT B
577 12 ORA2 EQU $C481 ;PORT A
578 13 DDRB2 EQU $C482 ;DATA DIRECTION REGISTER (B)
579 14 DDRA2 EQU $C483 ;DATA DIRECTION REGISTER (A)
581 void WrMem(uint16_t addr, uint8_t b)
584 //extern V6809REGS regs;
585 //if (addr >= 0xC800 && addr <= 0xCBFE)
586 //if (addr == 0xC80F || addr == 0xC80D)
587 // WriteLog("WrMem: Writing address %04X with %02X [PC=%04X, $CB00=%02X]\n", addr, b, regs.pc, gram[0xCB00]);//*/
590 if (addr >= 0xC000 && addr <= 0xC0FF)
591 WriteLog("\n*** Write at I/O address %04X\n", addr);
594 Check the BIKO version on Asimov to see if it's been cracked or not...
596 7F3D: 29 07 AND #$07 [PC=7F3F, SP=01EA, CC=---B-I--, A=01, X=4B, Y=00]
597 7F3F: C9 06 CMP #$06 [PC=7F41, SP=01EA, CC=N--B-I--, A=01, X=4B, Y=00]
598 7F41: 90 03 BCC $7F46 [PC=7F46, SP=01EA, CC=N--B-I--, A=01, X=4B, Y=00]
599 [7F43: 4C 83 7E JMP $7E83] <- Skipped over... (Prints "THANK YOU VERY MUCH!")
600 7F46: AA TAX [PC=7F47, SP=01EA, CC=---B-I--, A=01, X=01, Y=00]
602 ; INX here *ensures* 1 - 6!!! BUG!!!
603 ; Or is it? Could this be part of a braindead copy protection scheme? It's
604 ; awfully close to NOP ($EA)...
605 ; Nothing else touches it once it's been written... Hmm...
607 7F47: E8 INX [PC=7F48, SP=01EA, CC=---B-I--, A=01, X=02, Y=00]
608 7F48: F8 SED [PC=7F49, SP=01EA, CC=---BDI--, A=01, X=02, Y=00]
609 7F49: 18 CLC [PC=7F4A, SP=01EA, CC=---BDI--, A=01, X=02, Y=00]
610 7F4A: BD 15 4E LDA $4E15,X [PC=7F4D, SP=01EA, CC=---BDI--, A=15, X=02, Y=00]
613 ; 4E15: 25 25 15 15 10 20
614 ; 4E1B: 03 41 99 99 01 00 12
617 7F4D: 65 FC ADC $FC [PC=7F4F, SP=01EA, CC=---BDI--, A=16, X=02, Y=00]
618 7F4F: 65 FC ADC $FC [PC=7F51, SP=01EA, CC=---BDI--, A=17, X=02, Y=00]
619 7F51: 65 FC ADC $FC [PC=7F53, SP=01EA, CC=---BDI--, A=18, X=02, Y=00]
620 7F53: 65 FC ADC $FC [PC=7F55, SP=01EA, CC=---BDI--, A=19, X=02, Y=00]
622 ; NO checking is done on the raised stat! Aarrrgggghhhhh!
624 7F55: 9D 15 4E STA $4E15,X [PC=7F58, SP=01EA, CC=---BDI--, A=19, X=02, Y=00]
625 7F58: D8 CLD [PC=7F59, SP=01EA, CC=---B-I--, A=19, X=02, Y=00]
627 ; Print "ALAKAZAM!" and so on...
629 7F59: 20 2C 40 JSR $402C [PC=402C, SP=01E8, CC=---B-I--, A=19, X=02, Y=00]
633 WriteLog("\n*** Byte %02X written at address %04X\n", b, addr);
636 I think this is IIc/IIe only...
638 CLR80STORE=$C000 ;80STORE Off- disable 80-column memory mapping (Write)
639 SET80STORE=$C001 ;80STORE On- enable 80-column memory mapping (WR-only)
641 CLRAUXRD = $C002 ;read from main 48K (WR-only)
642 SETAUXRD = $C003 ;read from aux/alt 48K (WR-only)
644 CLRAUXWR = $C004 ;write to main 48K (WR-only)
645 SETAUXWR = $C005 ;write to aux/alt 48K (WR-only)
647 CLRCXROM = $C006 ;use ROM on cards (WR-only)
648 SETCXROM = $C007 ;use internal ROM (WR-only)
650 CLRAUXZP = $C008 ;use main zero page, stack, & LC (WR-only)
651 SETAUXZP = $C009 ;use alt zero page, stack, & LC (WR-only)
653 CLRC3ROM = $C00A ;use internal Slot 3 ROM (WR-only)
654 SETC3ROM = $C00B ;use external Slot 3 ROM (WR-only)
656 CLR80VID = $C00C ;disable 80-column display mode (WR-only)
657 SET80VID = $C00D ;enable 80-column display mode (WR-only)
659 CLRALTCH = $C00E ;use main char set- norm LC, Flash UC (WR-only)
660 SETALTCH = $C00F ;use alt char set- norm inverse, LC; no Flash (WR-only)
664 alternateCharset = false;
666 else if (addr == 0xC00F)
668 alternateCharset = true;
670 else if ((addr & 0xFFF0) == 0xC010) // Keyboard strobe
672 //Actually, according to the A2 ref, this should do nothing since a write
673 //is immediately preceded by a read leaving it in the same state it was...
674 //But leaving this out seems to fuck up the key handling of some games...
677 else if (addr == 0xC050)
681 else if (addr == 0xC051)
685 else if (addr == 0xC052)
689 else if (addr == 0xC053)
693 else if (addr == 0xC054)
695 displayPage2 = false;
697 else if (addr == 0xC055)
701 else if (addr == 0xC056)
705 else if (addr == 0xC057)
709 else if ((addr & 0xFFFB) == 0xC080)
712 WriteLog("LC(R): $C080 49280 OECG R Read RAM bank 2; no write\n");
714 //$C080 49280 OECG R Read RAM bank 2; no write
715 visibleBank = LC_BANK_2;
719 else if ((addr & 0xFFFB) == 0xC081)
722 WriteLog("LC(R): $C081 49281 OECG RR Read ROM; write RAM bank 2\n");
724 //$C081 49281 ROMIN OECG RR Read ROM; write RAM bank 2
725 visibleBank = LC_BANK_2;
729 else if ((addr & 0xFFFB) == 0xC082)
732 WriteLog("LC(R): $C082 49282 OECG R Read ROM; no write\n");
734 //$C082 49282 OECG R Read ROM; no write
735 visibleBank = LC_BANK_2;
739 else if ((addr & 0xFFFB) == 0xC083)
742 WriteLog("LC(R): $C083 49283 OECG RR Read/Write RAM bank 2\n");
744 //$C083 49283 LCBANK2 OECG RR Read/write RAM bank 2
745 visibleBank = LC_BANK_2;
749 else if ((addr & 0xFFFB) == 0xC088)
752 WriteLog("LC(R): $C088 49288 OECG R Read RAM bank 1; no write\n");
754 //$C088 49288 OECG R Read RAM bank 1; no write
755 visibleBank = LC_BANK_1;
759 else if ((addr & 0xFFFB) == 0xC089)
762 WriteLog("LC(R): $C089 49289 OECG RR Read ROM; write RAM bank 1\n");
764 //$C089 49289 OECG RR Read ROM; write RAM bank 1
765 visibleBank = LC_BANK_1;
769 else if ((addr & 0xFFFB) == 0xC08A)
772 WriteLog("LC(R): $C08A 49290 OECG R Read ROM; no write\n");
774 //$C08A 49290 OECG R Read ROM; no write
775 visibleBank = LC_BANK_1;
779 else if ((addr & 0xFFFB) == 0xC08B)
782 WriteLog("LC(R): $C08B 49291 OECG RR Read/Write RAM bank 1\n");
784 //$C08B 49291 OECG RR Read/write RAM bank 1
785 visibleBank = LC_BANK_1;
789 //This is determined by which slot it is in--this assumes slot 6. !!! FIX !!!
790 else if ((addr & 0xFFF8) == 0xC0E0)
792 floppyDrive.ControlStepper(addr & 0x07);
794 else if ((addr & 0xFFFE) == 0xC0E8)
796 floppyDrive.ControlMotor(addr & 0x01);
798 else if ((addr & 0xFFFE) == 0xC0EA)
800 floppyDrive.DriveEnable(addr & 0x01);
802 else if (addr == 0xC0EC)
804 //change this to Write()? (and the other to Read()?) Dunno. Seems to work OK, but still...
806 floppyDrive.ReadWrite();
808 else if (addr == 0xC0ED)
810 floppyDrive.SetLatchValue(b);
812 else if (addr == 0xC0EE)
814 floppyDrive.SetReadMode();
816 else if (addr == 0xC0EF)
818 floppyDrive.SetWriteMode();
820 //Still need to add missing I/O switches here...
822 //DEEE: BD 10 BF LDA $BF10,X [PC=DEF1, SP=01F4, CC=--.B-IZ-, A=00, X=0C, Y=07]
824 if (addr >= 0xD000 && addr <= 0xD00F)
826 WriteLog("*** Write to $%04X: $%02X (writeRAM=%s, PC=%04X, ram$D000=%02X)\n", addr, b, (writeRAM ? "T" : "F"), mainCPU.pc, ram[0xC000]);
829 if (addr >= 0xC000 && addr <= 0xCFFF)
830 return; // Protect LC bank #1 from being written to!
836 if (addr <= 0xDFFF && visibleBank == LC_BANK_1)
837 ram[addr - 0x1000] = b;
849 // Load a file into RAM/ROM image space
851 bool LoadImg(char * filename, uint8_t * ram, int size)
853 FILE * fp = fopen(filename, "rb");
858 fread(ram, 1, size, fp);
864 static void SaveApple2State(const char * filename)
868 static bool LoadApple2State(const char * filename)
873 #ifdef CPU_CLOCK_CHECKING
875 uint32_t totalCPU = 0;
876 uint64_t lastClock = 0;
881 int main(int /*argc*/, char * /*argv*/[])
883 InitLog("./apple2.log");
885 srand(time(NULL)); // Initialize RNG
888 //Need to bankify this stuff for the IIe emulation...
889 memset(ram, 0, 0x10000);
890 memset(rom, 0, 0x10000);
891 memset(ram2, 0, 0x10000);
893 // Set up V65C02 execution context
894 memset(&mainCPU, 0, sizeof(V65C02REGS));
895 mainCPU.RdMem = RdMem;
896 mainCPU.WrMem = WrMem;
897 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
899 if (!LoadImg(settings.BIOSPath, rom + 0xD000, 0x3000))
901 WriteLog("Could not open file '%s'!\n", settings.BIOSPath);
905 //This is now included...
906 /* if (!LoadImg(settings.diskPath, diskRom, 0x100))
908 WriteLog("Could not open file '%s'!\nDisk II will be unavailable!\n", settings.diskPath);
912 //Load up disk image from config file (for now)...
913 floppyDrive.LoadImage(settings.diskImagePath1, 0);
914 floppyDrive.LoadImage(settings.diskImagePath2, 1);
915 // floppyDrive.LoadImage("./disks/temp.nib", 1); // Load temp .nib file into second drive...
917 //Kill the DOS ROM in slot 6 for now...
919 memcpy(rom + 0xC600, diskROM, 0x100);
920 // memcpy(rom + 0xC700, diskROM, 0x100); // Slot 7???
922 WriteLog("About to initialize video...\n");
926 std::cout << "Aborting!" << std::endl;
930 // Have to do this *after* video init but *before* sound init...!
931 //Shouldn't be necessary since we're not doing emulation in the ISR...
932 if (settings.autoStateSaving)
934 // Load last state from file...
935 if (!LoadApple2State(settings.autoStatePath))
936 WriteLog("Unable to use Apple2 state file \"%s\"!\n", settings.autoStatePath);
942 if (!LoadImg("./BT1_6502_RAM_SPACE.bin", ram, 0x10000))
944 cout << "Couldn't load state file!" << endl;
945 cout << "Aborting!!" << endl;
950 //-- -- -- -- ----- -----
951 //00 75 3B 53 FD 01 41 44
953 mainCPU.cpuFlags = 0;
963 displayPage2 = false;
970 memcpy(ram + 0xD000, ram + 0xC000, 0x1000);
973 WriteLog("About to initialize audio...\n");
975 //nope SDL_EnableUNICODE(1); // Needed to do key translation shit
977 // gui = new GUI(surface); // Set up the GUI system object...
978 // gui = new GUI(mainSurface); // Set up the GUI system object...
979 // SDL 2... this will likely cause Apple 2 to crash
980 // gui = new GUI(NULL); // Set up the GUI system object...
982 gui->AddMenuTitle("Apple2");
983 gui->AddMenuItem("Test!", TestWindow/*, hotkey*/);
984 gui->AddMenuItem("");
985 gui->AddMenuItem("Quit", QuitEmulator, SDLK_q);
986 gui->CommitItemsToMenu();
989 SetupBlurTable(); // Set up the color TV emulation blur table
990 running = true; // Set running status...
992 InitializeEventList(); // Clear the event list before we use it...
993 SetCallbackTime(FrameCallback, 16666.66666667); // Set frame to fire at 1/60 s interval
994 SetCallbackTime(BlinkTimer, 250000); // Set up blinking at 1/4 s intervals
995 startTicks = SDL_GetTicks();
997 #ifdef THREADED_65C02
998 cpuCond = SDL_CreateCond();
999 cpuThread = SDL_CreateThread(CPUThreadFunc, NULL, NULL);
1000 //Hmm... CPU does POST (+1), wait, then WAIT (-1)
1001 mainSem = SDL_CreateSemaphore(1);
1002 // SDL_sem * mainMutex = SDL_CreateMutex();
1005 WriteLog("Entering main loop...\n");
1008 double timeToNextEvent = GetTimeToNextEvent();
1009 #ifndef THREADED_65C02
1010 Execute65C02(&mainCPU, USEC_TO_M6502_CYCLES(timeToNextEvent));
1012 //We MUST remove a frame's worth of time in order for the CPU to function... !!! FIX !!!
1013 //(Fix so that this is not a requirement!)
1014 //Fixed, but mainCPU.clock is destroyed in the bargain. Oh well.
1015 // mainCPU.clock -= USEC_TO_M6502_CYCLES(timeToNextEvent);
1017 #ifdef CPU_CLOCK_CHECKING
1018 #ifndef THREADED_65C02
1019 totalCPU += USEC_TO_M6502_CYCLES(timeToNextEvent);
1022 // Handle CPU time delta for sound...
1023 //Don't need this anymore now that we use absolute time...
1024 // AddToSoundTimeBase(USEC_TO_M6502_CYCLES(timeToNextEvent));
1028 #ifdef THREADED_65C02
1029 WriteLog("Main: cpuFinished = true;\n");
1031 //#warning "If sound thread is behind, CPU thread will never wake up... !!! FIX !!!" [DONE]
1032 //What to do? How do you know when the CPU is sleeping???
1033 //USE A CONDITIONAL!!! OF COURSE!!!!!!11!11!11!!!1!
1035 SDL_mutexP(mainMutex);
1036 SDL_CondWait(mainCond, mainMutex); // Wait for CPU thread to get to signal point...
1037 SDL_mutexV(mainMutex);
1039 //Nope, use a semaphore...
1040 WriteLog("Main: SDL_SemWait(mainSem);\n");
1041 SDL_SemWait(mainSem);//should lock until CPU thread is waiting...
1044 WriteLog("Main: SDL_CondSignal(cpuCond);//thread is probably asleep, wake it up\n");
1045 SDL_CondSignal(cpuCond);//thread is probably asleep, wake it up
1046 WriteLog("Main: SDL_WaitThread(cpuThread, NULL);\n");
1047 SDL_WaitThread(cpuThread, NULL);
1048 //nowok:SDL_WaitThread(CPUThreadFunc, NULL);
1049 WriteLog("Main: SDL_DestroyCond(cpuCond);\n");
1050 SDL_DestroyCond(cpuCond);
1052 //SDL_DestroyMutex(mainMutex);
1053 SDL_DestroySemaphore(mainSem);
1056 if (settings.autoStateSaving)
1058 // Save state here...
1059 SaveApple2State(settings.autoStatePath);
1061 floppyDrive.SaveImage();
1076 -----------------------
1077 space $A0 $A0 $A0 $A0 No xlation
1078 RETURN $8D $8D $8D $8D No xlation
1079 0 $B0 $B0 $B0 $B0 Need to screen shift+0 (?)
1080 1! $B1 $B1 $A1 $A1 No xlation
1081 2" $B2 $B2 $A2 $A2 No xlation
1082 3# $B3 $B3 $A3 $A3 No xlation
1083 4$ $B4 $B4 $A4 $A4 No xlation
1084 5% $B5 $B5 $A5 $A5 No xlation
1085 6& $B6 $B6 $A6 $A6 No xlation
1086 7' $B7 $B7 $A7 $A7 No xlation
1087 8( $B8 $B8 $A8 $A8 No xlation
1088 9) $B9 $B9 $A9 $A9 No xlation
1089 :* $BA $BA $AA $AA No xlation
1090 ;+ $BB $BB $AB $AB No xlation
1091 ,< $AC $AC $BC $BC No xlation
1092 -= $AD $AD $BD $BD No xlation
1093 .> $AE $AE $BE $BE No xlation
1094 /? $AF $AF $BF $BF No xlation
1107 M $CD $8D $DD $9D -> ODD
1108 N^ $CE $8E $DE $9E -> ODD
1110 P@ $D0 $90 $C0 $80 Need to xlate CTL+SHFT+P & SHFT+P (?)
1123 ESC $9B $9B $9B $9B No xlation
1126 static void FrameCallback(void)
1130 while (SDL_PollEvent(&event))
1135 //Need to do some key translation here, and screen out non-apple keys as well...
1136 //(really, could do it all in SDL_KEYDOWN, would just have to get symbols &
1137 // everything else done separately. this is slightly easier. :-P)
1138 // if (event.key.keysym.sym == SDLK_TAB) // Prelim key screening...
1139 if (event.edit.text[0] == '\t') // Prelim key screening...
1142 lastKeyPressed = event.edit.text[0];
1145 //kludge: should have a caps lock thingy here...
1146 //or all uppercase for ][+...
1147 if (lastKeyPressed >= 'a' && lastKeyPressed <='z')
1148 lastKeyPressed &= 0xDF; // Convert to upper case...
1152 // CTRL+RESET key emulation (mapped to CTRL+`)
1153 // This doesn't work...
1154 // if (event.key.keysym.sym == SDLK_BREAK && (event.key.keysym.mod & KMOD_CTRL))
1155 // if (event.key.keysym.sym == SDLK_PAUSE && (event.key.keysym.mod & KMOD_CTRL))
1156 if (event.key.keysym.sym == SDLK_BACKQUOTE && (event.key.keysym.mod & KMOD_CTRL))
1157 //NOTE that this shouldn't take place until the key is lifted... !!! FIX !!!
1158 //ALSO it seems to leave the machine in an inconsistent state vis-a-vis the language card...
1159 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
1161 if (event.key.keysym.sym == SDLK_RIGHT)
1162 lastKeyPressed = 0x15, keyDown = true;
1163 else if (event.key.keysym.sym == SDLK_LEFT)
1164 lastKeyPressed = 0x08, keyDown = true;
1165 else if (event.key.keysym.sym == SDLK_RETURN)
1166 lastKeyPressed = 0x0D, keyDown = true;
1167 else if (event.key.keysym.sym == SDLK_ESCAPE)
1168 lastKeyPressed = 0x1B, keyDown = true;
1170 // Fix CTRL+key combo...
1171 if (event.key.keysym.mod & KMOD_CTRL)
1173 if (event.key.keysym.sym >= SDLK_a && event.key.keysym.sym <= SDLK_z)
1175 lastKeyPressed = (event.key.keysym.sym - SDLK_a) + 1;
1177 //printf("Key combo pressed: CTRL+%c\n", lastKeyPressed + 0x40);
1181 // Use ALT+Q to exit, as well as the usual window decoration method
1182 if (event.key.keysym.sym == SDLK_q && (event.key.keysym.mod & KMOD_ALT))
1185 // Paddle buttons 0 & 1
1186 if (event.key.keysym.sym == SDLK_INSERT)
1187 openAppleDown = true;
1188 if (event.key.keysym.sym == SDLK_PAGEUP)
1189 closedAppleDown = true;
1191 if (event.key.keysym.sym == SDLK_F11)
1192 dumpDis = !dumpDis; // Toggle the disassembly process
1193 // else if (event.key.keysym.sym == SDLK_F11)
1194 // floppyDrive.LoadImage("./disks/bt1_char.dsk");//Kludge to load char disk...
1195 else if (event.key.keysym.sym == SDLK_F9)
1197 floppyDrive.CreateBlankImage();
1198 // SpawnMessage("Image cleared...");
1200 else if (event.key.keysym.sym == SDLK_F10)
1202 floppyDrive.SwapImages();
1203 // SpawnMessage("Image swapped...");
1206 if (event.key.keysym.sym == SDLK_F2)// Toggle the palette
1208 else if (event.key.keysym.sym == SDLK_F3)// Cycle through screen types
1211 // if (event.key.keysym.sym == SDLK_F5) // Temp GUI launch key
1212 if (event.key.keysym.sym == SDLK_F1) // GUI launch key
1213 //NOTE: Should parse the output to determine whether or not the user requested
1214 // to quit completely... !!! FIX !!!
1217 if (event.key.keysym.sym == SDLK_F5)
1220 char volStr[19] = "[****************]";
1221 // volStr[GetVolume()] = 0;
1222 for(int i=GetVolume(); i<16; i++)
1223 volStr[1 + i] = '-';
1224 SpawnMessage("Volume: %s", volStr);
1226 else if (event.key.keysym.sym == SDLK_F6)
1229 char volStr[19] = "[****************]";
1230 // volStr[GetVolume()] = 0;
1231 for(int i=GetVolume(); i<16; i++)
1232 volStr[1 + i] = '-';
1233 SpawnMessage("Volume: %s", volStr);
1236 static bool fullscreenDebounce = false;
1238 if (event.key.keysym.sym == SDLK_F12)
1240 if (!fullscreenDebounce)
1243 fullscreenDebounce = true;
1250 if (event.key.keysym.sym == SDLK_F12)
1251 fullscreenDebounce = false;
1253 // Paddle buttons 0 & 1
1254 if (event.key.keysym.sym == SDLK_INSERT)
1255 openAppleDown = false;
1256 if (event.key.keysym.sym == SDLK_PAGEUP)
1257 closedAppleDown = false;
1259 // if (event.key.keysym.sym >= SDLK_a && event.key.keysym.sym <= SDLK_z)
1268 #warning "!!! Taking MAJOR time hit with the video frame rendering !!!"
1270 SetCallbackTime(FrameCallback, 16666.66666667);
1272 #ifdef CPU_CLOCK_CHECKING
1273 //We know it's stopped, so we can get away with this...
1277 uint64_t clock = GetCurrentV65C02Clock();
1278 //totalCPU += (uint32_t)(clock - lastClock);
1280 printf("Executed %u cycles...\n", (uint32_t)(clock - lastClock));
1286 //Instead of this, we should yield remaining time to other processes... !!! FIX !!! [DONE]
1289 //Actually, slows things down too much...
1291 // while (SDL_GetTicks() - startTicks < 16); // Wait for next frame...
1292 while (SDL_GetTicks() - startTicks < 16)
1293 SDL_Delay(1); // Wait for next frame...
1295 startTicks = SDL_GetTicks();
1296 //let's wait, then signal...
1297 //works longer, but then still falls behind...
1298 #ifdef THREADED_65C02
1299 SDL_CondSignal(cpuCond);//OK, let the CPU go another frame...
1303 static void BlinkTimer(void)
1306 SetCallbackTime(BlinkTimer, 250000); // Set up blinking at 1/4 sec intervals
1310 Next problem is this: How to have events occur and synchronize with the rest
1313 o Have the CPU thread manage the timer mechanism? (need to have a method of carrying
1314 remainder CPU cycles over...)
1316 One way would be to use a fractional accumulator, then subtract 1 every
1317 time it overflows. Like so:
1319 double overflow = 0;
1323 Execute6808(&soundCPU, time);
1324 overflow += 0.289115646;