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 //using namespace std;
61 uint8 ram[0x10000], rom[0x10000]; // RAM & ROM spaces
62 uint8 diskRom[0x100]; // Disk ROM space
64 uint8 appleType = APPLE_TYPE_II;
68 static uint8 lastKeyPressed = 0;
69 static bool keyDown = false;
71 static FloppyDrive floppyDrive;
73 enum { LC_BANK_1, LC_BANK_2 };
75 static uint8 visibleBank = LC_BANK_1;
76 static bool readRAM = false;
77 static bool writeRAM = false;
79 static bool running = true; // Machine running state flag...
80 static uint32 startTicks;
82 static GUI * gui = NULL;
84 // Local functions (technically, they're global...)
86 bool LoadImg(char * filename, uint8 * ram, int size);
87 uint8 RdMem(uint16 addr);
88 void WrMem(uint16 addr, uint8 b);
89 static void SaveApple2State(const char * filename);
90 static bool LoadApple2State(const char * filename);
92 // Local timer callback functions
94 static void FrameCallback(void);
95 static void BlinkTimer(void);
99 Element * TestWindow(void)
101 Element * win = new DraggableWindow2(10, 10, 128, 128);
102 // ((DraggableWindow *)win)->AddElement(new TextEdit(4, 16, 92, 0, "u2prog.dsk", win));
107 Element * QuitEmulator(void)
116 Small Apple II memory map:
118 $C010 - Clear bit 7 of keyboard data ($C000)
119 $C030 - Toggle speaker diaphragm
121 $C054 - Select page 1
122 $C056 - Select lo-res
123 $C058 - Set annuciator-0 output to 0
124 $C05A - Set annuciator-0 output to 0
125 $C05D - Set annuciator-0 output to 1
126 $C05F - Set annuciator-0 output to 1
127 $C0E0 - Disk control stepper ($C0E0-7)
128 $C0E9 - Disk control motor (on)
129 $C0EA - Disk enable (drive 1)
131 $C0EE - Disk set read mode
135 // V65C02 read/write memory functions
138 uint8 RdMem(uint16 addr)
143 if (addr >= 0xC000 && addr <= 0xC0FF)
144 WriteLog("\n*** Read at I/O address %04X\n", addr);
147 if (addr >= 0xC080 && addr <= 0xC08F)
148 WriteLog("\n*** Read at I/O address %04X\n", addr);
151 if ((addr & 0xFFF0) == 0xC000)
153 return lastKeyPressed | (keyDown ? 0x80 : 0x00);
155 else if ((addr & 0xFFF0) == 0xC010)
157 //This is bogus: keyDown is set to false, so return val NEVER is set...
159 //Also, this is IIe/IIc only...!
160 uint8 retVal = lastKeyPressed | (keyDown ? 0x80 : 0x00);
164 else if ((addr & 0xFFF0) == 0xC030)
167 This is problematic, mainly because the v65C02 removes actual cycles run after each call.
168 Therefore, we don't really have a reliable method of sending a timestamp to the sound routine.
171 What we need to send is a delta T value but related to the IRQ buffer routine. E.g., if the buffer
172 hasn't had any changes in it then we just fill it with the last sample value and are done. Then
173 we need to adjust our delta T accordingly. What we could do is keep a running total of time since the
174 last change and adjust it accordingly, i.e., whenever a sound IRQ happens.
177 Have deltaT somewhere. Then, whenever there's a toggle, backfill buffer with last spkr state and reset
178 deltaT to zero. In the sound IRQ, if deltaT > buffer size, then subtract buffer size from deltaT. (?)
183 ToggleSpeaker(GetCurrentV65C02Clock());
184 //should it return something else here???
187 else if (addr == 0xC050)
191 else if (addr == 0xC051)
195 else if (addr == 0xC052)
199 else if (addr == 0xC053)
203 else if (addr == 0xC054)
205 displayPage2 = false;
207 else if (addr == 0xC055)
211 else if (addr == 0xC056)
215 else if (addr == 0xC057)
220 //Note that this is a kludge: The $D000-$DFFF 4K space is shared (since $C000-$CFFF is
221 //memory mapped) between TWO banks, and that that $E000-$FFFF RAM space is a single bank.
222 //[SHOULD BE FIXED NOW]
223 //OK! This switch selects bank 2 of the 4K bank at $D000-$DFFF. One access makes it
224 //visible, two makes it R/W.
226 else if ((addr & 0xFFFB) == 0xC080)
228 //$C080 49280 OECG R Read RAM bank 2; no write
229 visibleBank = LC_BANK_2;
233 else if ((addr & 0xFFFB) == 0xC081)
235 //$C081 49281 ROMIN OECG RR Read ROM; write RAM bank 2
236 visibleBank = LC_BANK_2;
240 else if ((addr & 0xFFFB) == 0xC082)
242 //$C082 49282 OECG R Read ROM; no write
243 visibleBank = LC_BANK_2;
247 else if ((addr & 0xFFFB) == 0xC083)
249 //$C083 49283 LCBANK2 OECG RR Read/write RAM bank 2
250 visibleBank = LC_BANK_2;
254 else if ((addr & 0xFFFB) == 0xC088)
256 //$C088 49288 OECG R Read RAM bank 1; no write
257 visibleBank = LC_BANK_1;
261 else if ((addr & 0xFFFB) == 0xC089)
263 //$C089 49289 OECG RR Read ROM; write RAM bank 1
264 visibleBank = LC_BANK_1;
268 else if ((addr & 0xFFFB) == 0xC08A)
270 //$C08A 49290 OECG R Read ROM; no write
271 visibleBank = LC_BANK_1;
275 else if ((addr & 0xFFFB) == 0xC08B)
277 //$C08B 49291 OECG RR Read/write RAM bank 1
278 visibleBank = LC_BANK_1;
282 else if ((addr & 0xFFF8) == 0xC0E0)
284 floppyDrive.ControlStepper(addr & 0x07);
286 else if ((addr & 0xFFFE) == 0xC0E8)
288 floppyDrive.ControlMotor(addr & 0x01);
290 else if ((addr & 0xFFFE) == 0xC0EA)
292 floppyDrive.DriveEnable(addr & 0x01);
294 else if (addr == 0xC0EC)
296 return floppyDrive.ReadWrite();
298 else if (addr == 0xC0ED)
300 return floppyDrive.GetLatchValue();
302 else if (addr == 0xC0EE)
304 floppyDrive.SetReadMode();
306 else if (addr == 0xC0EF)
308 floppyDrive.SetWriteMode();
312 if (addr >= 0xC100 && addr <= 0xCFFF) // The $C000-$CFFF block is *never* RAM
314 else if (addr >= 0xD000)
318 if (addr <= 0xDFFF && visibleBank == LC_BANK_1)
319 b = ram[addr - 0x1000];
334 APPENDIX F Assembly Language Program Listings
340 5 * ;ADDRESSES FOR FIRST 6522
341 6 ORB EQU $C400 ;PORT B
342 7 ORA EQU $C401 ;PORT A
343 8 DDRB EQU $C402 ;DATA DIRECTION REGISTER (A)
344 9 DDRA EQU $C403 ;DATA DIRECTION REGISTER (B)
345 10 * ;ADDRESSES FOR SECOND 6522
346 11 ORB2 EQU $C480 ;PORT B
347 12 ORA2 EQU $C481 ;PORT A
348 13 DDRB2 EQU $C482 ;DATA DIRECTION REGISTER (B)
349 14 DDRA2 EQU $C483 ;DATA DIRECTION REGISTER (A)
351 void WrMem(uint16 addr, uint8 b)
354 //extern V6809REGS regs;
355 //if (addr >= 0xC800 && addr <= 0xCBFE)
356 //if (addr == 0xC80F || addr == 0xC80D)
357 // WriteLog("WrMem: Writing address %04X with %02X [PC=%04X, $CB00=%02X]\n", addr, b, regs.pc, gram[0xCB00]);//*/
360 if (addr >= 0xC000 && addr <= 0xC0FF)
361 WriteLog("\n*** Write at I/O address %04X\n", addr);
364 Check the BIKO version on Asimov to see if it's been cracked or not...
366 7F3D: 29 07 AND #$07 [PC=7F3F, SP=01EA, CC=---B-I--, A=01, X=4B, Y=00]
367 7F3F: C9 06 CMP #$06 [PC=7F41, SP=01EA, CC=N--B-I--, A=01, X=4B, Y=00]
368 7F41: 90 03 BCC $7F46 [PC=7F46, SP=01EA, CC=N--B-I--, A=01, X=4B, Y=00]
369 [7F43: 4C 83 7E JMP $7E83] <- Skipped over... (Prints "THANK YOU VERY MUCH!")
370 7F46: AA TAX [PC=7F47, SP=01EA, CC=---B-I--, A=01, X=01, Y=00]
372 ; INX here *ensures* 1 - 6!!! BUG!!!
373 ; Or is it? Could this be part of a braindead copy protection scheme? It's
374 ; awfully close to NOP ($EA)...
375 ; Nothing else touches it once it's been written... Hmm...
377 7F47: E8 INX [PC=7F48, SP=01EA, CC=---B-I--, A=01, X=02, Y=00]
378 7F48: F8 SED [PC=7F49, SP=01EA, CC=---BDI--, A=01, X=02, Y=00]
379 7F49: 18 CLC [PC=7F4A, SP=01EA, CC=---BDI--, A=01, X=02, Y=00]
380 7F4A: BD 15 4E LDA $4E15,X [PC=7F4D, SP=01EA, CC=---BDI--, A=15, X=02, Y=00]
383 ; 4E15: 25 25 15 15 10 20
384 ; 4E1B: 03 41 99 99 01 00 12
387 7F4D: 65 FC ADC $FC [PC=7F4F, SP=01EA, CC=---BDI--, A=16, X=02, Y=00]
388 7F4F: 65 FC ADC $FC [PC=7F51, SP=01EA, CC=---BDI--, A=17, X=02, Y=00]
389 7F51: 65 FC ADC $FC [PC=7F53, SP=01EA, CC=---BDI--, A=18, X=02, Y=00]
390 7F53: 65 FC ADC $FC [PC=7F55, SP=01EA, CC=---BDI--, A=19, X=02, Y=00]
392 ; NO checking is done on the raised stat! Aarrrgggghhhhh!
394 7F55: 9D 15 4E STA $4E15,X [PC=7F58, SP=01EA, CC=---BDI--, A=19, X=02, Y=00]
395 7F58: D8 CLD [PC=7F59, SP=01EA, CC=---B-I--, A=19, X=02, Y=00]
397 ; Print "ALAKAZAM!" and so on...
399 7F59: 20 2C 40 JSR $402C [PC=402C, SP=01E8, CC=---B-I--, A=19, X=02, Y=00]
403 WriteLog("\n*** Byte %02X written at address %04X\n", b, addr);
406 CLR80STORE=$C000 ;80STORE Off- disable 80-column memory mapping (Write)
407 SET80STORE=$C001 ;80STORE On- enable 80-column memory mapping (WR-only)
409 CLRAUXRD = $C002 ;read from main 48K (WR-only)
410 SETAUXRD = $C003 ;read from aux/alt 48K (WR-only)
412 CLRAUXWR = $C004 ;write to main 48K (WR-only)
413 SETAUXWR = $C005 ;write to aux/alt 48K (WR-only)
415 CLRCXROM = $C006 ;use ROM on cards (WR-only)
416 SETCXROM = $C007 ;use internal ROM (WR-only)
418 CLRAUXZP = $C008 ;use main zero page, stack, & LC (WR-only)
419 SETAUXZP = $C009 ;use alt zero page, stack, & LC (WR-only)
421 CLRC3ROM = $C00A ;use internal Slot 3 ROM (WR-only)
422 SETC3ROM = $C00B ;use external Slot 3 ROM (WR-only)
424 CLR80VID = $C00C ;disable 80-column display mode (WR-only)
425 SET80VID = $C00D ;enable 80-column display mode (WR-only)
427 CLRALTCH = $C00E ;use main char set- norm LC, Flash UC (WR-only)
428 SETALTCH = $C00F ;use alt char set- norm inverse, LC; no Flash (WR-only)
432 alternateCharset = false;
434 else if (addr == 0xC00F)
436 alternateCharset = true;
438 else if ((addr & 0xFFF0) == 0xC010) // Keyboard strobe
442 else if (addr == 0xC050)
446 else if (addr == 0xC051)
450 else if (addr == 0xC052)
454 else if (addr == 0xC053)
458 else if (addr == 0xC054)
460 displayPage2 = false;
462 else if (addr == 0xC055)
466 else if (addr == 0xC056)
470 else if (addr == 0xC057)
474 else if ((addr & 0xFFF8) == 0xC0E0)
476 floppyDrive.ControlStepper(addr & 0x07);
478 else if ((addr & 0xFFFE) == 0xC0E8)
480 floppyDrive.ControlMotor(addr & 0x01);
482 else if ((addr & 0xFFFE) == 0xC0EA)
484 floppyDrive.DriveEnable(addr & 0x01);
486 else if (addr == 0xC0EC)
488 //change this to Write()? (and the other to Read()?) Dunno. Seems to work OK, but still...
489 floppyDrive.ReadWrite();
491 else if (addr == 0xC0ED)
493 floppyDrive.SetLatchValue(b);
495 else if (addr == 0xC0EE)
497 floppyDrive.SetReadMode();
499 else if (addr == 0xC0EF)
501 floppyDrive.SetWriteMode();
503 //Still need to add missing I/O switches here...
509 if (addr <= 0xDFFF && visibleBank == LC_BANK_1)
510 ram[addr - 0x1000] = b;
522 // Load a file into RAM/ROM image space
524 bool LoadImg(char * filename, uint8 * ram, int size)
526 FILE * fp = fopen(filename, "rb");
531 fread(ram, 1, size, fp);
537 static void SaveApple2State(const char * filename)
541 static bool LoadApple2State(const char * filename)
549 int main(int /*argc*/, char * /*argv*/[])
551 InitLog("./apple2.log");
553 srand(time(NULL)); // Initialize RNG
556 //Need to bankify this stuff for the IIe emulation...
557 memset(ram, 0, 0x10000);
558 memset(rom, 0, 0x10000);
560 // Set up V65C02 execution context
561 memset(&mainCPU, 0, sizeof(V65C02REGS));
562 mainCPU.RdMem = RdMem;
563 mainCPU.WrMem = WrMem;
564 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
566 if (!LoadImg(settings.BIOSPath, rom + 0xD000, 0x3000))
568 WriteLog("Could not open file '%s'!\n", settings.BIOSPath);
572 //This is now included...
573 /* if (!LoadImg(settings.diskPath, diskRom, 0x100))
575 WriteLog("Could not open file '%s'!\nDisk II will be unavailable!\n", settings.diskPath);
579 //Load up disk image from config file (for now)...
580 floppyDrive.LoadImage(settings.diskImagePath1, 0);
581 floppyDrive.LoadImage(settings.diskImagePath2, 1);
582 // floppyDrive.LoadImage("./disks/temp.nib", 1); // Load temp .nib file into second drive...
584 //Kill the DOS ROM in slot 6 for now...
586 memcpy(rom + 0xC600, diskROM, 0x100);
588 WriteLog("About to initialize video...\n");
591 std::cout << "Aborting!" << std::endl;
595 // Have to do this *after* video init but *before* sound init...!
596 //Shouldn't be necessary since we're not doing emulation in the ISR...
597 if (settings.autoStateSaving)
599 // Load last state from file...
600 if (!LoadApple2State(settings.autoStatePath))
601 WriteLog("Unable to use Apple2 state file \"%s\"!\n", settings.autoStatePath);
607 if (!LoadImg("./BT1_6502_RAM_SPACE.bin", ram, 0x10000))
609 cout << "Couldn't load state file!" << endl;
610 cout << "Aborting!!" << endl;
615 //-- -- -- -- ----- -----
616 //00 75 3B 53 FD 01 41 44
618 mainCPU.cpuFlags = 0;
628 displayPage2 = false;
635 memcpy(ram + 0xD000, ram + 0xC000, 0x1000);
638 WriteLog("About to initialize audio...\n");
640 SDL_EnableUNICODE(1); // Needed to do key translation shit
642 gui = new GUI(surface); // Set up the GUI system object...
643 gui->AddMenuTitle("Apple2");
644 gui->AddMenuItem("Test!", TestWindow/*, hotkey*/);
645 gui->AddMenuItem("");
646 gui->AddMenuItem("Quit", QuitEmulator, SDLK_q);
647 gui->CommitItemsToMenu();
649 SetupBlurTable(); // Set up the color TV emulation blur table
650 running = true; // Set running status...
652 InitializeEventList(); // Clear the event list before we use it...
653 SetCallbackTime(FrameCallback, 16666.66666667); // Set frame to fire at 1/60 s interval
654 SetCallbackTime(BlinkTimer, 250000); // Set up blinking at 1/4 s intervals
655 startTicks = SDL_GetTicks();
657 WriteLog("Entering main loop...\n");
660 double timeToNextEvent = GetTimeToNextEvent();
661 Execute65C02(&mainCPU, USEC_TO_M6502_CYCLES(timeToNextEvent));
662 //We MUST remove a frame's worth of time in order for the CPU to function... !!! FIX !!!
663 //(Fix so that this is not a requirement!)
664 //Fixed, but mainCPU.clock is destroyed in the bargain. Oh well.
665 // mainCPU.clock -= USEC_TO_M6502_CYCLES(timeToNextEvent);
669 if (settings.autoStateSaving)
671 // Save state here...
672 SaveApple2State(settings.autoStatePath);
674 floppyDrive.SaveImage();
689 -----------------------
690 space $A0 $A0 $A0 $A0 No xlation
691 RETURN $8D $8D $8D $8D No xlation
692 0 $B0 $B0 $B0 $B0 Need to screen shift+0 (?)
693 1! $B1 $B1 $A1 $A1 No xlation
694 2" $B2 $B2 $A2 $A2 No xlation
695 3# $B3 $B3 $A3 $A3 No xlation
696 4$ $B4 $B4 $A4 $A4 No xlation
697 5% $B5 $B5 $A5 $A5 No xlation
698 6& $B6 $B6 $A6 $A6 No xlation
699 7' $B7 $B7 $A7 $A7 No xlation
700 8( $B8 $B8 $A8 $A8 No xlation
701 9) $B9 $B9 $A9 $A9 No xlation
702 :* $BA $BA $AA $AA No xlation
703 ;+ $BB $BB $AB $AB No xlation
704 ,< $AC $AC $BC $BC No xlation
705 -= $AD $AD $BD $BD No xlation
706 .> $AE $AE $BE $BE No xlation
707 /? $AF $AF $BF $BF No xlation
720 M $CD $8D $DD $9D -> ODD
721 N^ $CE $8E $DE $9E -> ODD
723 P@ $D0 $90 $C0 $80 Need to xlate CTL+SHFT+P & SHFT+P (?)
736 ESC $9B $9B $9B $9B No xlation
739 static void FrameCallback(void)
743 while (SDL_PollEvent(&event))
748 if (event.key.keysym.unicode != 0)
750 //Need to do some key translation here, and screen out non-apple keys as well...
751 if (event.key.keysym.sym == SDLK_TAB) // Prelim key screening...
754 lastKeyPressed = event.key.keysym.unicode;
756 //kludge: should have a caps lock thingy here...
757 //or all uppercase for ][+...
758 if (lastKeyPressed >= 'a' && lastKeyPressed <='z')
759 lastKeyPressed &= 0xDF; // Convert to upper case...
762 // CTRL+RESET key emulation (mapped to CTRL+`)
763 // This doesn't work...
764 // if (event.key.keysym.sym == SDLK_BREAK && (event.key.keysym.mod & KMOD_CTRL))
765 // if (event.key.keysym.sym == SDLK_PAUSE && (event.key.keysym.mod & KMOD_CTRL))
766 if (event.key.keysym.sym == SDLK_BACKQUOTE && (event.key.keysym.mod & KMOD_CTRL))
767 //NOTE that this shouldn't take place until the key is lifted... !!! FIX !!!
768 //ALSO it seems to leave the machine in an inconsistent state vis-a-vis the language card...
769 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
771 if (event.key.keysym.sym == SDLK_RIGHT)
772 lastKeyPressed = 0x15, keyDown = true;
773 else if (event.key.keysym.sym == SDLK_LEFT)
774 lastKeyPressed = 0x08, keyDown = true;
776 // Use ALT+Q to exit, as well as the usual window decoration method
777 if (event.key.keysym.sym == SDLK_q && (event.key.keysym.mod & KMOD_ALT))
780 if (event.key.keysym.sym == SDLK_F12)
781 dumpDis = !dumpDis; // Toggle the disassembly process
782 else if (event.key.keysym.sym == SDLK_F11)
783 floppyDrive.LoadImage("./disks/bt1_char.dsk");//Kludge to load char disk...
784 else if (event.key.keysym.sym == SDLK_F9)
786 floppyDrive.CreateBlankImage();
787 // SpawnMessage("Image cleared...");
789 else if (event.key.keysym.sym == SDLK_F10)
791 floppyDrive.SwapImages();
792 // SpawnMessage("Image swapped...");
795 if (event.key.keysym.sym == SDLK_F2)// Toggle the palette
797 else if (event.key.keysym.sym == SDLK_F3)// Cycle through screen types
800 // if (event.key.keysym.sym == SDLK_F5) // Temp GUI launch key
801 if (event.key.keysym.sym == SDLK_F1) // GUI launch key
802 //NOTE: Should parse the output to determine whether or not the user requested
803 // to quit completely... !!! FIX !!!
812 HandleSoundAtFrameEdge(); // Sound stuff... (ick)
814 SetCallbackTime(FrameCallback, 16666.66666667);
816 //Instead of this, we should yield remaining time to other processes... !!! FIX !!!
818 //nope. SDL_Delay(10);
819 while (SDL_GetTicks() - startTicks < 16); // Wait for next frame...
820 startTicks = SDL_GetTicks();
823 static void BlinkTimer(void)
826 SetCallbackTime(BlinkTimer, 250000); // Set up blinking at 1/4 sec intervals