]> Shamusworld >> Repos - apple2/blob - src/apple2.cpp
More work on sound.cpp, especially definition of time constants
[apple2] / src / apple2.cpp
1 //
2 // Apple 2 SDL Portable Apple Emulator
3 //
4 // by James L. Hammons
5 // (C) 2005 Underground Software
6 //
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.
11 //
12 // JLH = James L. Hammons <jlhamm@acm.org>
13 //
14 // WHO  WHEN        WHAT
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
20 //
21
22 // STILL TO DO:
23 //
24 // - Port to SDL [DONE]
25 // - GUI goodies
26 // - Weed out unneeded functions [DONE]
27 // - Disk I/O [DONE]
28 // - 128K IIe related stuff
29 // - State loading/saving
30 //
31
32 #include "apple2.h"
33
34 #include <SDL.h>
35 #include <fstream>
36 #include <string>
37 #include <iomanip>
38 #include <iostream>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <time.h>
42 #include "log.h"
43 #include "video.h"
44 #include "sound.h"
45 #include "settings.h"
46 #include "v65c02.h"
47 #include "applevideo.h"
48 #include "timing.h"
49 #include "floppy.h"
50 #include "firmware.h"
51
52 #include "gui/gui.h"
53 #include "gui/window.h"
54 #include "gui/draggablewindow2.h"
55 #include "gui/textedit.h"
56
57 //using namespace std;
58
59 // Global variables
60
61 uint8 ram[0x10000], rom[0x10000];                               // RAM & ROM spaces
62 uint8 diskRom[0x100];                                                   // Disk ROM space
63 V65C02REGS mainCPU;
64 uint8 appleType = APPLE_TYPE_II;
65
66 // Local variables
67
68 static uint8 lastKeyPressed = 0;
69 static bool keyDown = false;
70
71 static FloppyDrive floppyDrive;
72
73 enum { LC_BANK_1, LC_BANK_2 };
74
75 static uint8 visibleBank = LC_BANK_1;
76 static bool readRAM = false;
77 static bool writeRAM = false;
78
79 static bool running = true;                                             // Machine running state flag...
80 static uint32 startTicks;
81
82 static GUI * gui = NULL;
83
84 // Local functions (technically, they're global...)
85
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);
91
92 // Local timer callback functions
93
94 static void FrameCallback(void);
95 static void BlinkTimer(void);
96
97 // Test GUI function
98
99 Element * TestWindow(void)
100 {
101         Element * win = new DraggableWindow2(10, 10, 128, 128);
102 //      ((DraggableWindow *)win)->AddElement(new TextEdit(4, 16, 92, 0, "u2prog.dsk", win));
103
104         return win;
105 }
106
107 Element * QuitEmulator(void)
108 {
109         gui->Stop();
110         running = false;
111
112         return NULL;
113 }
114
115 /*
116  Small Apple II memory map:
117
118 $C010 - Clear bit 7 of keyboard data ($C000)
119 $C030 - Toggle speaker diaphragm
120 $C051 - Display text
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)
130 $C0EC - Disk R/W
131 $C0EE - Disk set read mode
132 */
133
134 //
135 // V65C02 read/write memory functions
136 //
137
138 uint8 RdMem(uint16 addr)
139 {
140         uint8 b;
141
142 #if 0
143 if (addr >= 0xC000 && addr <= 0xC0FF)
144         WriteLog("\n*** Read at I/O address %04X\n", addr);
145 #endif
146 #if 0
147 if (addr >= 0xC080 && addr <= 0xC08F)
148         WriteLog("\n*** Read at I/O address %04X\n", addr);
149 #endif
150
151         if ((addr & 0xFFF0) == 0xC000)
152         {
153                 return lastKeyPressed | (keyDown ? 0x80 : 0x00);
154         }
155         else if ((addr & 0xFFF0) == 0xC010)
156         {
157 //This is bogus: keyDown is set to false, so return val NEVER is set...
158 //Fixed...
159 //Also, this is IIe/IIc only...!
160                 uint8 retVal = lastKeyPressed | (keyDown ? 0x80 : 0x00);
161                 keyDown = false;
162                 return retVal;
163         }
164         else if ((addr & 0xFFF0) == 0xC030)
165         {
166 /*
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.
169 How to fix?
170
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.
175 How to keep track?
176
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. (?)
179
180
181
182 */
183                 ToggleSpeaker(GetCurrentV65C02Clock());
184 //should it return something else here???
185                 return 0x00;
186         }
187         else if (addr == 0xC050)
188         {
189                 textMode = false;
190         }
191         else if (addr == 0xC051)
192         {
193                 textMode = true;
194         }
195         else if (addr == 0xC052)
196         {
197                 mixedMode = false;
198         }
199         else if (addr == 0xC053)
200         {
201                 mixedMode = true;
202         }
203         else if (addr == 0xC054)
204         {
205                 displayPage2 = false;
206         }
207         else if (addr == 0xC055)
208         {
209                 displayPage2 = true;
210         }
211         else if (addr == 0xC056)
212         {
213                 hiRes = false;
214         }
215         else if (addr == 0xC057)
216         {
217                 hiRes = true;
218         }
219
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.
225
226         else if ((addr & 0xFFFB) == 0xC080)
227         {
228 //$C080 49280              OECG  R   Read RAM bank 2; no write
229                 visibleBank = LC_BANK_2;
230                 readRAM = true;
231                 writeRAM = false;
232         }
233         else if ((addr & 0xFFFB) == 0xC081)
234         {
235 //$C081 49281 ROMIN        OECG  RR  Read ROM; write RAM bank 2
236                 visibleBank = LC_BANK_2;
237                 readRAM = false;
238                 writeRAM = true;
239         }
240         else if ((addr & 0xFFFB) == 0xC082)
241         {
242 //$C082 49282              OECG  R   Read ROM; no write
243                 visibleBank = LC_BANK_2;
244                 readRAM = false;
245                 writeRAM = false;
246         }
247         else if ((addr & 0xFFFB) == 0xC083)
248         {
249 //$C083 49283 LCBANK2      OECG  RR  Read/write RAM bank 2
250                 visibleBank = LC_BANK_2;
251                 readRAM = true;
252                 writeRAM = true;
253         }
254         else if ((addr & 0xFFFB) == 0xC088)
255         {
256 //$C088 49288              OECG  R   Read RAM bank 1; no write
257                 visibleBank = LC_BANK_1;
258                 readRAM = true;
259                 writeRAM = false;
260         }
261         else if ((addr & 0xFFFB) == 0xC089)
262         {
263 //$C089 49289              OECG  RR  Read ROM; write RAM bank 1
264                 visibleBank = LC_BANK_1;
265                 readRAM = false;
266                 writeRAM = true;
267         }
268         else if ((addr & 0xFFFB) == 0xC08A)
269         {
270 //$C08A 49290              OECG  R   Read ROM; no write
271                 visibleBank = LC_BANK_1;
272                 readRAM = false;
273                 writeRAM = false;
274         }
275         else if ((addr & 0xFFFB) == 0xC08B)
276         {
277 //$C08B 49291              OECG  RR  Read/write RAM bank 1
278                 visibleBank = LC_BANK_1;
279                 readRAM = true;
280                 writeRAM = true;
281         }
282         else if ((addr & 0xFFF8) == 0xC0E0)
283         {
284                 floppyDrive.ControlStepper(addr & 0x07);
285         }
286         else if ((addr & 0xFFFE) == 0xC0E8)
287         {
288                 floppyDrive.ControlMotor(addr & 0x01);
289         }
290         else if ((addr & 0xFFFE) == 0xC0EA)
291         {
292                 floppyDrive.DriveEnable(addr & 0x01);
293         }
294         else if (addr == 0xC0EC)
295         {
296                 return floppyDrive.ReadWrite();
297         }
298         else if (addr == 0xC0ED)
299         {
300                 return floppyDrive.GetLatchValue();
301         }
302         else if (addr == 0xC0EE)
303         {
304                 floppyDrive.SetReadMode();
305         }
306         else if (addr == 0xC0EF)
307         {
308                 floppyDrive.SetWriteMode();
309         }
310
311 //This sux...
312         if (addr >= 0xC100 && addr <= 0xCFFF)   // The $C000-$CFFF block is *never* RAM
313                 b = rom[addr];
314         else if (addr >= 0xD000)
315         {
316                 if (readRAM)
317                 {
318                         if (addr <= 0xDFFF && visibleBank == LC_BANK_1)
319                                 b = ram[addr - 0x1000];
320                         else
321                                 b = ram[addr];
322                 }
323                 else
324                         b = rom[addr];
325         }
326         else
327                 b = ram[addr];
328
329         return b;
330 }
331
332 /*
333 A-9 (Mockingboard)
334 APPENDIX F Assembly Language Program Listings
335
336         1       *PRIMARY ROUTINES
337         2       *FOR SLOT 4
338         3       *
339         4                       ORG     $9000
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)
350 */
351 void WrMem(uint16 addr, uint8 b)
352 {
353 //temp...
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]);//*/
358
359 #if 0
360 if (addr >= 0xC000 && addr <= 0xC0FF)
361         WriteLog("\n*** Write at I/O address %04X\n", addr);
362 #endif
363 /*
364 Check the BIKO version on Asimov to see if it's been cracked or not...
365
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]
371
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...
376
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]
381
382 ; 4E13: 03 00
383 ; 4E15: 25 25 15 15 10 20
384 ; 4E1B: 03 41 99 99 01 00 12
385 ; 4E22: 99 70
386
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]
391
392 ; NO checking is done on the raised stat! Aarrrgggghhhhh!
393
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]
396
397 ; Print "ALAKAZAM!" and so on...
398
399 7F59: 20 2C 40       JSR   $402C      [PC=402C, SP=01E8, CC=---B-I--, A=19, X=02, Y=00]
400 */
401 #if 0
402 if (addr == 0x7F47)
403         WriteLog("\n*** Byte %02X written at address %04X\n", b, addr);
404 #endif
405 /*
406 CLR80STORE=$C000 ;80STORE Off- disable 80-column memory mapping (Write)
407 SET80STORE=$C001 ;80STORE On- enable 80-column memory mapping (WR-only)
408
409 CLRAUXRD = $C002 ;read from main 48K (WR-only)
410 SETAUXRD = $C003 ;read from aux/alt 48K (WR-only)
411
412 CLRAUXWR = $C004 ;write to main 48K (WR-only)
413 SETAUXWR = $C005 ;write to aux/alt 48K (WR-only)
414
415 CLRCXROM = $C006 ;use ROM on cards (WR-only)
416 SETCXROM = $C007 ;use internal ROM (WR-only)
417
418 CLRAUXZP = $C008 ;use main zero page, stack, & LC (WR-only)
419 SETAUXZP = $C009 ;use alt zero page, stack, & LC (WR-only)
420
421 CLRC3ROM = $C00A ;use internal Slot 3 ROM (WR-only)
422 SETC3ROM = $C00B ;use external Slot 3 ROM (WR-only)
423
424 CLR80VID = $C00C ;disable 80-column display mode (WR-only)
425 SET80VID = $C00D ;enable 80-column display mode (WR-only)
426
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)
429 */
430         if (addr == 0xC00E)
431         {
432                 alternateCharset = false;
433         }
434         else if (addr == 0xC00F)
435         {
436                 alternateCharset = true;
437         }
438         else if ((addr & 0xFFF0) == 0xC010)             // Keyboard strobe
439         {
440                 keyDown = false;
441         }
442         else if (addr == 0xC050)
443         {
444                 textMode = false;
445         }
446         else if (addr == 0xC051)
447         {
448                 textMode = true;
449         }
450         else if (addr == 0xC052)
451         {
452                 mixedMode = false;
453         }
454         else if (addr == 0xC053)
455         {
456                 mixedMode = true;
457         }
458         else if (addr == 0xC054)
459         {
460                 displayPage2 = false;
461         }
462         else if (addr == 0xC055)
463         {
464                 displayPage2 = true;
465         }
466         else if (addr == 0xC056)
467         {
468                 hiRes = false;
469         }
470         else if (addr == 0xC057)
471         {
472                 hiRes = true;
473         }
474         else if ((addr & 0xFFF8) == 0xC0E0)
475         {
476                 floppyDrive.ControlStepper(addr & 0x07);
477         }
478         else if ((addr & 0xFFFE) == 0xC0E8)
479         {
480                 floppyDrive.ControlMotor(addr & 0x01);
481         }
482         else if ((addr & 0xFFFE) == 0xC0EA)
483         {
484                 floppyDrive.DriveEnable(addr & 0x01);
485         }
486         else if (addr == 0xC0EC)
487         {
488 //change this to Write()? (and the other to Read()?) Dunno. Seems to work OK, but still...
489                 floppyDrive.ReadWrite();
490         }
491         else if (addr == 0xC0ED)
492         {
493                 floppyDrive.SetLatchValue(b);
494         }
495         else if (addr == 0xC0EE)
496         {
497                 floppyDrive.SetReadMode();
498         }
499         else if (addr == 0xC0EF)
500         {
501                 floppyDrive.SetWriteMode();
502         }
503 //Still need to add missing I/O switches here...
504
505         if (addr >= 0xD000)
506         {
507                 if (writeRAM)
508                 {
509                         if (addr <= 0xDFFF && visibleBank == LC_BANK_1)
510                                 ram[addr - 0x1000] = b;
511                         else
512                                 ram[addr] = b;
513                 }
514
515                 return;
516         }
517
518         ram[addr] = b;
519 }
520
521 //
522 // Load a file into RAM/ROM image space
523 //
524 bool LoadImg(char * filename, uint8 * ram, int size)
525 {
526         FILE * fp = fopen(filename, "rb");
527
528         if (fp == NULL)
529                 return false;
530
531         fread(ram, 1, size, fp);
532         fclose(fp);
533
534         return true;
535 }
536
537 static void SaveApple2State(const char * filename)
538 {
539 }
540
541 static bool LoadApple2State(const char * filename)
542 {
543         return false;
544 }
545
546 //
547 // Main loop
548 //
549 int main(int /*argc*/, char * /*argv*/[])
550 {
551         InitLog("./apple2.log");
552         LoadSettings();
553         srand(time(NULL));                                                                      // Initialize RNG
554
555         // Zero out memory
556 //Need to bankify this stuff for the IIe emulation...
557         memset(ram, 0, 0x10000);
558         memset(rom, 0, 0x10000);
559
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;
565
566         if (!LoadImg(settings.BIOSPath, rom + 0xD000, 0x3000))
567         {
568                 WriteLog("Could not open file '%s'!\n", settings.BIOSPath);
569                 return -1;
570         }
571
572 //This is now included...
573 /*      if (!LoadImg(settings.diskPath, diskRom, 0x100))
574         {
575                 WriteLog("Could not open file '%s'!\nDisk II will be unavailable!\n", settings.diskPath);
576 //              return -1;
577         }//*/
578
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...
583
584 //Kill the DOS ROM in slot 6 for now...
585 //not
586         memcpy(rom + 0xC600, diskROM, 0x100);
587
588         WriteLog("About to initialize video...\n");
589         if (!InitVideo())
590         {
591                 std::cout << "Aborting!" << std::endl;
592                 return -1;
593         }
594
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)
598         {
599                 // Load last state from file...
600                 if (!LoadApple2State(settings.autoStatePath))
601                         WriteLog("Unable to use Apple2 state file \"%s\"!\n", settings.autoStatePath);
602         }
603
604
605 #if 0
606 // State loading!
607 if (!LoadImg("./BT1_6502_RAM_SPACE.bin", ram, 0x10000))
608 {
609         cout << "Couldn't load state file!" << endl;
610         cout << "Aborting!!" << endl;
611         return -1;
612 }
613
614 //A  P  Y  X  S     PC
615 //-- -- -- -- ----- -----
616 //00 75 3B 53 FD 01 41 44
617
618 mainCPU.cpuFlags = 0;
619 mainCPU.a = 0x00;
620 mainCPU.x = 0x53;
621 mainCPU.y = 0x3B;
622 mainCPU.cc = 0x75;
623 mainCPU.sp = 0xFD;
624 mainCPU.pc = 0x4441;
625
626 textMode = false;
627 mixedMode = false;
628 displayPage2 = false;
629 hiRes = true;
630
631 //kludge...
632 readHiRam = true;
633 //dumpDis=true;
634 //kludge II...
635 memcpy(ram + 0xD000, ram + 0xC000, 0x1000);
636 #endif
637
638         WriteLog("About to initialize audio...\n");
639         SoundInit();
640         SDL_EnableUNICODE(1);                                           // Needed to do key translation shit
641
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();
648
649         SetupBlurTable();                                                       // Set up the color TV emulation blur table
650         running = true;                                                         // Set running status...
651
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();
656
657         WriteLog("Entering main loop...\n");
658         while (running)
659         {
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);
666                 HandleNextEvent();
667         }
668
669         if (settings.autoStateSaving)
670         {
671                 // Save state here...
672                 SaveApple2State(settings.autoStatePath);
673         }
674 floppyDrive.SaveImage();
675
676         SoundDone();
677         VideoDone();
678         SaveSettings();
679         LogDone();
680
681         return 0;
682 }
683
684 /*
685 Apple II keycodes
686 -----------------
687
688 Key     Aln CTL SHF BTH
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
708 A               $C1     $81     $C1     $81
709 B               $C2     $82     $C2     $82
710 C               $C3     $83     $C3     $83
711 D               $C4     $84     $C4     $84
712 E               $C5     $85     $C5     $85
713 F               $C6     $86     $C6     $86
714 G               $C7     $87     $C7     $87
715 H               $C8     $88     $C8     $88
716 I               $C9     $89     $C9     $89
717 J               $CA     $8A     $CA     $8A
718 K               $CB     $8B     $CB     $8B
719 L               $CC     $8C     $CC     $8C
720 M               $CD     $8D     $DD     $9D             -> ODD
721 N^              $CE     $8E     $DE     $9E             -> ODD
722 O               $CF     $8F     $CF     $8F
723 P@              $D0     $90     $C0     $80             Need to xlate CTL+SHFT+P & SHFT+P (?)
724 Q               $D1     $91     $D1     $91
725 R               $D2     $92     $D2     $92
726 S               $D3     $93     $D3     $93
727 T               $D4     $94     $D4     $94
728 U               $D5     $95     $D5     $95
729 V               $D6     $96     $D6     $96
730 W               $D7     $97     $D7     $97
731 X               $D8     $98     $D8     $98
732 Y               $D9     $99     $D9     $99
733 Z               $DA     $9A     $DA     $9A
734 <-              $88     $88     $88     $88
735 ->              $95     $95     $95     $95
736 ESC             $9B     $9B     $9B     $9B             No xlation
737
738 */
739 static void FrameCallback(void)
740 {
741         SDL_Event event;
742
743         while (SDL_PollEvent(&event))
744         {
745                 switch (event.type)
746                 {
747                 case SDL_KEYDOWN:
748                         if (event.key.keysym.unicode != 0)
749                         {
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...
752                                         break;
753
754                                 lastKeyPressed = event.key.keysym.unicode;
755                                 keyDown = true;
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...
760                         }
761
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;
770
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;
775
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))
778                                 running = false;
779
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)
785 {
786         floppyDrive.CreateBlankImage();
787 //      SpawnMessage("Image cleared...");
788 }//*/
789 else if (event.key.keysym.sym == SDLK_F10)
790 {
791         floppyDrive.SwapImages();
792 //      SpawnMessage("Image swapped...");
793 }//*/
794
795                         if (event.key.keysym.sym == SDLK_F2)// Toggle the palette
796                                 TogglePalette();
797                         else if (event.key.keysym.sym == SDLK_F3)// Cycle through screen types
798                                 CycleScreenTypes();
799
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 !!!
804                                 gui->Run();
805
806                         break;
807                 case SDL_QUIT:
808                         running = false;
809                 }
810         }
811
812         HandleSoundAtFrameEdge();                                       // Sound stuff... (ick)
813         RenderVideoFrame();
814         SetCallbackTime(FrameCallback, 16666.66666667);
815
816 //Instead of this, we should yield remaining time to other processes... !!! FIX !!!
817 //lessee...
818 //nope. SDL_Delay(10);
819         while (SDL_GetTicks() - startTicks < 16);       // Wait for next frame...
820         startTicks = SDL_GetTicks();
821 }
822
823 static void BlinkTimer(void)
824 {
825         flash = !flash;
826         SetCallbackTime(BlinkTimer, 250000);            // Set up blinking at 1/4 sec intervals
827 }