]> Shamusworld >> Repos - apple2/blob - src/apple2.cpp
Initial attempt at multithreaded implementation: CPU is now in its own thread.
[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 FloppyDrive floppyDrive;
66
67 // Local variables
68
69 static uint8 lastKeyPressed = 0;
70 static bool keyDown = false;
71
72 //static FloppyDrive floppyDrive;
73
74 enum { LC_BANK_1, LC_BANK_2 };
75
76 static uint8 visibleBank = LC_BANK_1;
77 static bool readRAM = false;
78 static bool writeRAM = false;
79
80 static bool running = true;                                             // Machine running state flag...
81 static uint32 startTicks;
82
83 static GUI * gui = NULL;
84
85 // Local functions (technically, they're global...)
86
87 bool LoadImg(char * filename, uint8 * ram, int size);
88 uint8 RdMem(uint16 addr);
89 void WrMem(uint16 addr, uint8 b);
90 static void SaveApple2State(const char * filename);
91 static bool LoadApple2State(const char * filename);
92
93 // Local timer callback functions
94
95 static void FrameCallback(void);
96 static void BlinkTimer(void);
97
98 #define THREADED_65C02
99 #ifdef THREADED_65C02
100 // Test of threaded execution of 6502
101 static SDL_Thread * cpuThread = NULL;
102 //static SDL_mutex * cpuMutex = NULL;
103 static SDL_cond * cpuCond = NULL;
104 static bool cpuFinished = false;
105 static bool cpuSleep = false;
106
107 // Let's try a thread...
108 /*
109 Here's how it works: Execute 1 frame's worth, then sleep.
110 Other stuff wakes it up
111 */
112 int CPUThreadFunc(void * data)
113 {
114         // Mutex must be locked for conditional to work...
115         // Also, must be created in the thread that uses it...
116         SDL_mutex * cpuMutex = SDL_CreateMutex();
117
118         do
119         {
120                 if (cpuSleep)
121                         SDL_CondWait(cpuCond, cpuMutex);
122
123                 Execute65C02(&mainCPU, 17066); // how much? 1 frame (after 1 s, off by 40 cycles)
124                 SDL_mutexP(cpuMutex);
125                 if (SDL_CondWait(cpuCond, cpuMutex) != 0)
126                 {
127                         printf("SDL_CondWait != 0! (Error: '%s')\n", SDL_GetError());
128                         exit(-1);
129                 }
130                 SDL_mutexV(cpuMutex);
131         }
132         while (!cpuFinished);
133
134         return 0;
135 }
136 #endif
137
138 // Test GUI function
139
140 Element * TestWindow(void)
141 {
142         Element * win = new DraggableWindow2(10, 10, 128, 128);
143 //      ((DraggableWindow *)win)->AddElement(new TextEdit(4, 16, 92, 0, "u2prog.dsk", win));
144
145         return win;
146 }
147
148 Element * QuitEmulator(void)
149 {
150         gui->Stop();
151         running = false;
152
153         return NULL;
154 }
155
156 /*
157  Small Apple II memory map:
158
159 $C010 - Clear bit 7 of keyboard data ($C000)
160 $C030 - Toggle speaker diaphragm
161 $C051 - Display text
162 $C054 - Select page 1
163 $C056 - Select lo-res
164 $C058 - Set annuciator-0 output to 0
165 $C05A - Set annuciator-0 output to 0
166 $C05D - Set annuciator-0 output to 1
167 $C05F - Set annuciator-0 output to 1
168 $C0E0 - Disk control stepper ($C0E0-7)
169 $C0E9 - Disk control motor (on)
170 $C0EA - Disk enable (drive 1)
171 $C0EC - Disk R/W
172 $C0EE - Disk set read mode
173 */
174
175 //
176 // V65C02 read/write memory functions
177 //
178
179 uint8 RdMem(uint16 addr)
180 {
181         uint8 b;
182
183 #if 0
184 if (addr >= 0xC000 && addr <= 0xC0FF)
185         WriteLog("\n*** Read at I/O address %04X\n", addr);
186 #endif
187 #if 0
188 if (addr >= 0xC080 && addr <= 0xC08F)
189         WriteLog("\n*** Read at I/O address %04X\n", addr);
190 #endif
191
192         if ((addr & 0xFFF0) == 0xC000)
193         {
194                 return lastKeyPressed | (keyDown ? 0x80 : 0x00);
195         }
196         else if ((addr & 0xFFF0) == 0xC010)
197         {
198 //This is bogus: keyDown is set to false, so return val NEVER is set...
199 //Fixed...
200 //Also, this is IIe/IIc only...!
201                 uint8 retVal = lastKeyPressed | (keyDown ? 0x80 : 0x00);
202                 keyDown = false;
203                 return retVal;
204         }
205         else if ((addr & 0xFFF0) == 0xC030)
206         {
207 /*
208 This is problematic, mainly because the v65C02 removes actual cycles run after each call.
209 Therefore, we don't really have a reliable method of sending a timestamp to the sound routine.
210 How to fix?
211
212 What we need to send is a delta T value but related to the IRQ buffer routine. E.g., if the buffer
213 hasn't had any changes in it then we just fill it with the last sample value and are done. Then
214 we need to adjust our delta T accordingly. What we could do is keep a running total of time since the
215 last change and adjust it accordingly, i.e., whenever a sound IRQ happens.
216 How to keep track?
217
218 Have deltaT somewhere. Then, whenever there's a toggle, backfill buffer with last spkr state and reset
219 deltaT to zero. In the sound IRQ, if deltaT > buffer size, then subtract buffer size from deltaT. (?)
220
221
222
223 */
224                 ToggleSpeaker(GetCurrentV65C02Clock());
225 //should it return something else here???
226                 return 0x00;
227         }
228         else if (addr == 0xC050)
229         {
230                 textMode = false;
231         }
232         else if (addr == 0xC051)
233         {
234                 textMode = true;
235         }
236         else if (addr == 0xC052)
237         {
238                 mixedMode = false;
239         }
240         else if (addr == 0xC053)
241         {
242                 mixedMode = true;
243         }
244         else if (addr == 0xC054)
245         {
246                 displayPage2 = false;
247         }
248         else if (addr == 0xC055)
249         {
250                 displayPage2 = true;
251         }
252         else if (addr == 0xC056)
253         {
254                 hiRes = false;
255         }
256         else if (addr == 0xC057)
257         {
258                 hiRes = true;
259         }
260
261 //Note that this is a kludge: The $D000-$DFFF 4K space is shared (since $C000-$CFFF is
262 //memory mapped) between TWO banks, and that that $E000-$FFFF RAM space is a single bank.
263 //[SHOULD BE FIXED NOW]
264 //OK! This switch selects bank 2 of the 4K bank at $D000-$DFFF. One access makes it
265 //visible, two makes it R/W.
266
267 /*
268 301  LDA $E000
269 304  PHA
270 305  LDA $C081
271 308  PLA
272 309  PHA
273 30A  CMP $E000
274 30D  BNE $332
275 30F  LDA $C083
276 312  LDA $C083
277 315  LDA #$A5
278 317  STA $D000
279 31A  CMP $D000
280 31D  BNE $332
281 31F  LSR A
282 320  STA $D000
283 323  CMP $D000
284 326  BNE $332
285 328  LDA $C081
286 32B  LDA $C081
287 32E  LDA #$01
288 330  BNE $334
289 332  LDA #$00
290 334  STA $300
291 337  PLA
292 338  CMP $E000
293 33B  BEQ $340
294 33D  LDA $C080
295 340  RTS
296
297 A = PEEK($C082)
298 */
299
300 //#define DEBUG_LC
301         else if ((addr & 0xFFFB) == 0xC080)
302         {
303 #ifdef DEBUG_LC
304 WriteLog("LC(R): $C080 49280 OECG R Read RAM bank 2; no write\n");
305 #endif
306 //$C080 49280              OECG  R   Read RAM bank 2; no write
307                 visibleBank = LC_BANK_2;
308                 readRAM = true;
309                 writeRAM = false;
310         }
311         else if ((addr & 0xFFFB) == 0xC081)
312         {
313 #ifdef DEBUG_LC
314 WriteLog("LC(R): $C081 49281 OECG RR Read ROM; write RAM bank 2\n");
315 #endif
316 //$C081 49281 ROMIN        OECG  RR  Read ROM; write RAM bank 2
317                 visibleBank = LC_BANK_2;
318                 readRAM = false;
319                 writeRAM = true;
320         }
321         else if ((addr & 0xFFFB) == 0xC082)
322         {
323 #ifdef DEBUG_LC
324 WriteLog("LC(R): $C082 49282 OECG R Read ROM; no write\n");
325 #endif
326 //$C082 49282              OECG  R   Read ROM; no write
327                 visibleBank = LC_BANK_2;
328                 readRAM = false;
329                 writeRAM = false;
330         }
331         else if ((addr & 0xFFFB) == 0xC083)
332         {
333 #ifdef DEBUG_LC
334 WriteLog("LC(R): $C083 49283 OECG RR Read/Write RAM bank 2\n");
335 #endif
336 //$C083 49283 LCBANK2      OECG  RR  Read/write RAM bank 2
337                 visibleBank = LC_BANK_2;
338                 readRAM = true;
339                 writeRAM = true;
340         }
341         else if ((addr & 0xFFFB) == 0xC088)
342         {
343 #ifdef DEBUG_LC
344 WriteLog("LC(R): $%04X 49288 OECG R Read RAM bank 1; no write\n", addr);
345 #endif
346 //$C088 49288              OECG  R   Read RAM bank 1; no write
347                 visibleBank = LC_BANK_1;
348                 readRAM = true;
349                 writeRAM = false;
350 //Hm. Some stuff seems to want this.
351 //nope, was looking at $C0E8... return 0xFF;
352         }
353         else if ((addr & 0xFFFB) == 0xC089)
354         {
355 #ifdef DEBUG_LC
356 WriteLog("LC(R): $C089 49289 OECG RR Read ROM; write RAM bank 1\n");
357 #endif
358 //$C089 49289              OECG  RR  Read ROM; write RAM bank 1
359                 visibleBank = LC_BANK_1;
360                 readRAM = false;
361                 writeRAM = true;
362         }
363         else if ((addr & 0xFFFB) == 0xC08A)
364         {
365 #ifdef DEBUG_LC
366 WriteLog("LC(R): $C08A 49290 OECG R Read ROM; no write\n");
367 #endif
368 //$C08A 49290              OECG  R   Read ROM; no write
369                 visibleBank = LC_BANK_1;
370                 readRAM = false;
371                 writeRAM = false;
372         }
373         else if ((addr & 0xFFFB) == 0xC08B)
374         {
375 #ifdef DEBUG_LC
376 WriteLog("LC(R): $C08B 49291 OECG RR Read/Write RAM bank 1\n");
377 #endif
378 //$C08B 49291              OECG  RR  Read/write RAM bank 1
379                 visibleBank = LC_BANK_1;
380                 readRAM = true;
381                 writeRAM = true;
382         }
383         else if ((addr & 0xFFF8) == 0xC0E0)
384         {
385                 floppyDrive.ControlStepper(addr & 0x07);
386         }
387         else if ((addr & 0xFFFE) == 0xC0E8)
388         {
389                 floppyDrive.ControlMotor(addr & 0x01);
390         }
391         else if ((addr & 0xFFFE) == 0xC0EA)
392         {
393                 floppyDrive.DriveEnable(addr & 0x01);
394         }
395         else if (addr == 0xC0EC)
396         {
397                 return floppyDrive.ReadWrite();
398 //Hm, some stuff is looking at the return value. Dunno what it *should* be...
399 // OK, it's from the ReadWrite routine...
400 //return 0xFF;
401         }
402         else if (addr == 0xC0ED)
403         {
404                 return floppyDrive.GetLatchValue();
405         }
406         else if (addr == 0xC0EE)
407         {
408                 floppyDrive.SetReadMode();
409         }
410         else if (addr == 0xC0EF)
411         {
412                 floppyDrive.SetWriteMode();
413         }
414
415 //#define LC_DEBUGGING
416 #ifdef LC_DEBUGGING
417 bool showpath = false;
418 if (addr >= 0xD000 && addr <= 0xD00F)
419         showpath = true;
420 #endif
421 //This sux...
422         if (addr >= 0xC100 && addr <= 0xCFFF)   // The $C000-$CFFF block is *never* RAM
423 #ifdef LC_DEBUGGING
424         {
425 #endif
426                 b = rom[addr];
427 #ifdef LC_DEBUGGING
428 if (showpath)
429         WriteLog("b is from $C100-$CFFF block...\n");
430         }
431 #endif
432         else if (addr >= 0xD000)
433         {
434                 if (readRAM)
435                 {
436                         if (addr <= 0xDFFF && visibleBank == LC_BANK_1)
437 #ifdef LC_DEBUGGING
438         {
439 #endif
440                                 b = ram[addr - 0x1000];
441 #ifdef LC_DEBUGGING
442 if (showpath)
443         WriteLog("b is from LC bank #1 (ram[addr - 0x1000])...\n");
444         }
445 #endif
446                         else
447 #ifdef LC_DEBUGGING
448         {
449 #endif
450                                 b = ram[addr];
451 #ifdef LC_DEBUGGING
452 if (showpath)
453         WriteLog("b is from LC bank #2 (ram[addr])...\n");
454         }
455 #endif
456                 }
457                 else
458 #ifdef LC_DEBUGGING
459         {
460 #endif
461                         b = rom[addr];
462 #ifdef LC_DEBUGGING
463 if (showpath)
464         WriteLog("b is from LC ROM (rom[addr])...\n");
465         }
466 #endif
467         }
468         else
469 #ifdef LC_DEBUGGING
470         {
471 #endif
472                 b = ram[addr];
473 #ifdef LC_DEBUGGING
474 if (showpath)
475         WriteLog("b is from ram[addr]...\n");
476         }
477 #endif
478
479 #ifdef LC_DEBUGGING
480 if (addr >= 0xD000 && addr <= 0xD00F)
481 {
482         WriteLog("*** Read from $%04X: $%02X (readRAM=%s, PC=%04X, ram$D000=%02X)\n", addr, b, (readRAM ? "T" : "F"), mainCPU.pc, ram[0xC000]);
483 }
484 #endif
485
486         return b;
487 }
488
489 /*
490 A-9 (Mockingboard)
491 APPENDIX F Assembly Language Program Listings
492
493         1       *PRIMARY ROUTINES
494         2       *FOR SLOT 4
495         3       *
496         4                       ORG     $9000
497         5       *                               ;ADDRESSES FOR FIRST 6522
498         6       ORB             EQU     $C400           ;PORT B
499         7       ORA             EQU     $C401           ;PORT A
500         8       DDRB            EQU     $C402           ;DATA DIRECTION REGISTER (A)
501         9       DDRA            EQU     $C403           ;DATA DIRECTION REGISTER (B)
502         10      *                                       ;ADDRESSES FOR SECOND 6522
503         11      ORB2            EQU     $C480           ;PORT B
504         12      ORA2            EQU     $C481           ;PORT A
505         13      DDRB2   EQU     $C482           ;DATA DIRECTION REGISTER (B)
506         14      DDRA2   EQU     $C483           ;DATA DIRECTION REGISTER (A)
507 */
508 void WrMem(uint16 addr, uint8 b)
509 {
510 //temp...
511 //extern V6809REGS regs;
512 //if (addr >= 0xC800 && addr <= 0xCBFE)
513 //if (addr == 0xC80F || addr == 0xC80D)
514 //      WriteLog("WrMem: Writing address %04X with %02X [PC=%04X, $CB00=%02X]\n", addr, b, regs.pc, gram[0xCB00]);//*/
515
516 #if 0
517 if (addr >= 0xC000 && addr <= 0xC0FF)
518         WriteLog("\n*** Write at I/O address %04X\n", addr);
519 #endif
520 /*
521 Check the BIKO version on Asimov to see if it's been cracked or not...
522
523 7F3D: 29 07          AND   #$07       [PC=7F3F, SP=01EA, CC=---B-I--, A=01, X=4B, Y=00]
524 7F3F: C9 06          CMP   #$06       [PC=7F41, SP=01EA, CC=N--B-I--, A=01, X=4B, Y=00]
525 7F41: 90 03          BCC   $7F46      [PC=7F46, SP=01EA, CC=N--B-I--, A=01, X=4B, Y=00]
526 [7F43: 4C 83 7E      JMP   $7E83] <- Skipped over... (Prints "THANK YOU VERY MUCH!")
527 7F46: AA             TAX              [PC=7F47, SP=01EA, CC=---B-I--, A=01, X=01, Y=00]
528
529 ; INX here *ensures* 1 - 6!!! BUG!!!
530 ; Or is it? Could this be part of a braindead copy protection scheme? It's
531 ; awfully close to NOP ($EA)...
532 ; Nothing else touches it once it's been written... Hmm...
533
534 7F47: E8             INX              [PC=7F48, SP=01EA, CC=---B-I--, A=01, X=02, Y=00]
535 7F48: F8             SED              [PC=7F49, SP=01EA, CC=---BDI--, A=01, X=02, Y=00]
536 7F49: 18             CLC              [PC=7F4A, SP=01EA, CC=---BDI--, A=01, X=02, Y=00]
537 7F4A: BD 15 4E       LDA   $4E15,X    [PC=7F4D, SP=01EA, CC=---BDI--, A=15, X=02, Y=00]
538
539 ; 4E13: 03 00
540 ; 4E15: 25 25 15 15 10 20
541 ; 4E1B: 03 41 99 99 01 00 12
542 ; 4E22: 99 70
543
544 7F4D: 65 FC          ADC   $FC        [PC=7F4F, SP=01EA, CC=---BDI--, A=16, X=02, Y=00]
545 7F4F: 65 FC          ADC   $FC        [PC=7F51, SP=01EA, CC=---BDI--, A=17, X=02, Y=00]
546 7F51: 65 FC          ADC   $FC        [PC=7F53, SP=01EA, CC=---BDI--, A=18, X=02, Y=00]
547 7F53: 65 FC          ADC   $FC        [PC=7F55, SP=01EA, CC=---BDI--, A=19, X=02, Y=00]
548
549 ; NO checking is done on the raised stat! Aarrrgggghhhhh!
550
551 7F55: 9D 15 4E       STA   $4E15,X    [PC=7F58, SP=01EA, CC=---BDI--, A=19, X=02, Y=00]
552 7F58: D8             CLD              [PC=7F59, SP=01EA, CC=---B-I--, A=19, X=02, Y=00]
553
554 ; Print "ALAKAZAM!" and so on...
555
556 7F59: 20 2C 40       JSR   $402C      [PC=402C, SP=01E8, CC=---B-I--, A=19, X=02, Y=00]
557 */
558 #if 0
559 if (addr == 0x7F47)
560         WriteLog("\n*** Byte %02X written at address %04X\n", b, addr);
561 #endif
562 /*
563 CLR80STORE=$C000 ;80STORE Off- disable 80-column memory mapping (Write)
564 SET80STORE=$C001 ;80STORE On- enable 80-column memory mapping (WR-only)
565
566 CLRAUXRD = $C002 ;read from main 48K (WR-only)
567 SETAUXRD = $C003 ;read from aux/alt 48K (WR-only)
568
569 CLRAUXWR = $C004 ;write to main 48K (WR-only)
570 SETAUXWR = $C005 ;write to aux/alt 48K (WR-only)
571
572 CLRCXROM = $C006 ;use ROM on cards (WR-only)
573 SETCXROM = $C007 ;use internal ROM (WR-only)
574
575 CLRAUXZP = $C008 ;use main zero page, stack, & LC (WR-only)
576 SETAUXZP = $C009 ;use alt zero page, stack, & LC (WR-only)
577
578 CLRC3ROM = $C00A ;use internal Slot 3 ROM (WR-only)
579 SETC3ROM = $C00B ;use external Slot 3 ROM (WR-only)
580
581 CLR80VID = $C00C ;disable 80-column display mode (WR-only)
582 SET80VID = $C00D ;enable 80-column display mode (WR-only)
583
584 CLRALTCH = $C00E ;use main char set- norm LC, Flash UC (WR-only)
585 SETALTCH = $C00F ;use alt char set- norm inverse, LC; no Flash (WR-only)
586 */
587         if (addr == 0xC00E)
588         {
589                 alternateCharset = false;
590         }
591         else if (addr == 0xC00F)
592         {
593                 alternateCharset = true;
594         }
595         else if ((addr & 0xFFF0) == 0xC010)             // Keyboard strobe
596         {
597 //Actually, according to the A2 ref, this should do nothing since a write
598 //is immediately preceded by a read leaving it in the same state it was...
599 //But leaving this out seems to fuck up the key handling of some games...
600                 keyDown = false;
601         }
602         else if (addr == 0xC050)
603         {
604                 textMode = false;
605         }
606         else if (addr == 0xC051)
607         {
608                 textMode = true;
609         }
610         else if (addr == 0xC052)
611         {
612                 mixedMode = false;
613         }
614         else if (addr == 0xC053)
615         {
616                 mixedMode = true;
617         }
618         else if (addr == 0xC054)
619         {
620                 displayPage2 = false;
621         }
622         else if (addr == 0xC055)
623         {
624                 displayPage2 = true;
625         }
626         else if (addr == 0xC056)
627         {
628                 hiRes = false;
629         }
630         else if (addr == 0xC057)
631         {
632                 hiRes = true;
633         }
634         else if ((addr & 0xFFFB) == 0xC080)
635         {
636 #ifdef DEBUG_LC
637 WriteLog("LC(R): $C080 49280 OECG R Read RAM bank 2; no write\n");
638 #endif
639 //$C080 49280              OECG  R   Read RAM bank 2; no write
640                 visibleBank = LC_BANK_2;
641                 readRAM = true;
642                 writeRAM = false;
643         }
644         else if ((addr & 0xFFFB) == 0xC081)
645         {
646 #ifdef DEBUG_LC
647 WriteLog("LC(R): $C081 49281 OECG RR Read ROM; write RAM bank 2\n");
648 #endif
649 //$C081 49281 ROMIN        OECG  RR  Read ROM; write RAM bank 2
650                 visibleBank = LC_BANK_2;
651                 readRAM = false;
652                 writeRAM = true;
653         }
654         else if ((addr & 0xFFFB) == 0xC082)
655         {
656 #ifdef DEBUG_LC
657 WriteLog("LC(R): $C082 49282 OECG R Read ROM; no write\n");
658 #endif
659 //$C082 49282              OECG  R   Read ROM; no write
660                 visibleBank = LC_BANK_2;
661                 readRAM = false;
662                 writeRAM = false;
663         }
664         else if ((addr & 0xFFFB) == 0xC083)
665         {
666 #ifdef DEBUG_LC
667 WriteLog("LC(R): $C083 49283 OECG RR Read/Write RAM bank 2\n");
668 #endif
669 //$C083 49283 LCBANK2      OECG  RR  Read/write RAM bank 2
670                 visibleBank = LC_BANK_2;
671                 readRAM = true;
672                 writeRAM = true;
673         }
674         else if ((addr & 0xFFFB) == 0xC088)
675         {
676 #ifdef DEBUG_LC
677 WriteLog("LC(R): $C088 49288 OECG R Read RAM bank 1; no write\n");
678 #endif
679 //$C088 49288              OECG  R   Read RAM bank 1; no write
680                 visibleBank = LC_BANK_1;
681                 readRAM = true;
682                 writeRAM = false;
683         }
684         else if ((addr & 0xFFFB) == 0xC089)
685         {
686 #ifdef DEBUG_LC
687 WriteLog("LC(R): $C089 49289 OECG RR Read ROM; write RAM bank 1\n");
688 #endif
689 //$C089 49289              OECG  RR  Read ROM; write RAM bank 1
690                 visibleBank = LC_BANK_1;
691                 readRAM = false;
692                 writeRAM = true;
693         }
694         else if ((addr & 0xFFFB) == 0xC08A)
695         {
696 #ifdef DEBUG_LC
697 WriteLog("LC(R): $C08A 49290 OECG R Read ROM; no write\n");
698 #endif
699 //$C08A 49290              OECG  R   Read ROM; no write
700                 visibleBank = LC_BANK_1;
701                 readRAM = false;
702                 writeRAM = false;
703         }
704         else if ((addr & 0xFFFB) == 0xC08B)
705         {
706 #ifdef DEBUG_LC
707 WriteLog("LC(R): $C08B 49291 OECG RR Read/Write RAM bank 1\n");
708 #endif
709 //$C08B 49291              OECG  RR  Read/write RAM bank 1
710                 visibleBank = LC_BANK_1;
711                 readRAM = true;
712                 writeRAM = true;
713         }
714 //This is determined by which slot it is in--this assumes slot 6. !!! FIX !!!
715         else if ((addr & 0xFFF8) == 0xC0E0)
716         {
717                 floppyDrive.ControlStepper(addr & 0x07);
718         }
719         else if ((addr & 0xFFFE) == 0xC0E8)
720         {
721                 floppyDrive.ControlMotor(addr & 0x01);
722         }
723         else if ((addr & 0xFFFE) == 0xC0EA)
724         {
725                 floppyDrive.DriveEnable(addr & 0x01);
726         }
727         else if (addr == 0xC0EC)
728         {
729 //change this to Write()? (and the other to Read()?) Dunno. Seems to work OK, but still...
730                 floppyDrive.ReadWrite();
731         }
732         else if (addr == 0xC0ED)
733         {
734                 floppyDrive.SetLatchValue(b);
735         }
736         else if (addr == 0xC0EE)
737         {
738                 floppyDrive.SetReadMode();
739         }
740         else if (addr == 0xC0EF)
741         {
742                 floppyDrive.SetWriteMode();
743         }
744 //Still need to add missing I/O switches here...
745
746 //DEEE: BD 10 BF       LDA   $BF10,X    [PC=DEF1, SP=01F4, CC=--.B-IZ-, A=00, X=0C, Y=07]
747 #if 0
748 if (addr >= 0xD000 && addr <= 0xD00F)
749 {
750         WriteLog("*** Write to $%04X: $%02X (writeRAM=%s, PC=%04X, ram$D000=%02X)\n", addr, b, (writeRAM ? "T" : "F"), mainCPU.pc, ram[0xC000]);
751 }
752 #endif
753         if (addr >= 0xC000 && addr <= 0xCFFF)
754                 return; // Protect LC bank #1 from being written to!
755
756         if (addr >= 0xD000)
757         {
758                 if (writeRAM)
759                 {
760                         if (addr <= 0xDFFF && visibleBank == LC_BANK_1)
761                                 ram[addr - 0x1000] = b;
762                         else
763                                 ram[addr] = b;
764                 }
765
766                 return;
767         }
768
769         ram[addr] = b;
770 }
771
772 //
773 // Load a file into RAM/ROM image space
774 //
775 bool LoadImg(char * filename, uint8 * ram, int size)
776 {
777         FILE * fp = fopen(filename, "rb");
778
779         if (fp == NULL)
780                 return false;
781
782         fread(ram, 1, size, fp);
783         fclose(fp);
784
785         return true;
786 }
787
788 static void SaveApple2State(const char * filename)
789 {
790 }
791
792 static bool LoadApple2State(const char * filename)
793 {
794         return false;
795 }
796
797 //#define CPU_CLOCK_CHECKING
798 #ifdef CPU_CLOCK_CHECKING
799 uint8 counter = 0;
800 uint32 totalCPU = 0;
801 #endif
802 //
803 // Main loop
804 //
805 int main(int /*argc*/, char * /*argv*/[])
806 {
807         InitLog("./apple2.log");
808         LoadSettings();
809         srand(time(NULL));                                                                      // Initialize RNG
810
811         // Zero out memory
812 //Need to bankify this stuff for the IIe emulation...
813         memset(ram, 0, 0x10000);
814         memset(rom, 0, 0x10000);
815
816         // Set up V65C02 execution context
817         memset(&mainCPU, 0, sizeof(V65C02REGS));
818         mainCPU.RdMem = RdMem;
819         mainCPU.WrMem = WrMem;
820         mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
821
822         if (!LoadImg(settings.BIOSPath, rom + 0xD000, 0x3000))
823         {
824                 WriteLog("Could not open file '%s'!\n", settings.BIOSPath);
825                 return -1;
826         }
827
828 //This is now included...
829 /*      if (!LoadImg(settings.diskPath, diskRom, 0x100))
830         {
831                 WriteLog("Could not open file '%s'!\nDisk II will be unavailable!\n", settings.diskPath);
832 //              return -1;
833         }//*/
834
835 //Load up disk image from config file (for now)...
836         floppyDrive.LoadImage(settings.diskImagePath1, 0);
837         floppyDrive.LoadImage(settings.diskImagePath2, 1);
838 //      floppyDrive.LoadImage("./disks/temp.nib", 1);   // Load temp .nib file into second drive...
839
840 //Kill the DOS ROM in slot 6 for now...
841 //not
842         memcpy(rom + 0xC600, diskROM, 0x100);
843
844         WriteLog("About to initialize video...\n");
845         if (!InitVideo())
846         {
847                 std::cout << "Aborting!" << std::endl;
848                 return -1;
849         }
850
851         // Have to do this *after* video init but *before* sound init...!
852 //Shouldn't be necessary since we're not doing emulation in the ISR...
853         if (settings.autoStateSaving)
854         {
855                 // Load last state from file...
856                 if (!LoadApple2State(settings.autoStatePath))
857                         WriteLog("Unable to use Apple2 state file \"%s\"!\n", settings.autoStatePath);
858         }
859
860
861 #if 0
862 // State loading!
863 if (!LoadImg("./BT1_6502_RAM_SPACE.bin", ram, 0x10000))
864 {
865         cout << "Couldn't load state file!" << endl;
866         cout << "Aborting!!" << endl;
867         return -1;
868 }
869
870 //A  P  Y  X  S     PC
871 //-- -- -- -- ----- -----
872 //00 75 3B 53 FD 01 41 44
873
874 mainCPU.cpuFlags = 0;
875 mainCPU.a = 0x00;
876 mainCPU.x = 0x53;
877 mainCPU.y = 0x3B;
878 mainCPU.cc = 0x75;
879 mainCPU.sp = 0xFD;
880 mainCPU.pc = 0x4441;
881
882 textMode = false;
883 mixedMode = false;
884 displayPage2 = false;
885 hiRes = true;
886
887 //kludge...
888 readHiRam = true;
889 //dumpDis=true;
890 //kludge II...
891 memcpy(ram + 0xD000, ram + 0xC000, 0x1000);
892 #endif
893
894         WriteLog("About to initialize audio...\n");
895         SoundInit();
896         SDL_EnableUNICODE(1);                                           // Needed to do key translation shit
897
898 //      gui = new GUI(surface);                                         // Set up the GUI system object...
899         gui = new GUI(mainSurface);                                     // Set up the GUI system object...
900 #if 0
901         gui->AddMenuTitle("Apple2");
902         gui->AddMenuItem("Test!", TestWindow/*, hotkey*/);
903         gui->AddMenuItem("");
904         gui->AddMenuItem("Quit", QuitEmulator, SDLK_q);
905         gui->CommitItemsToMenu();
906 #endif
907
908         SetupBlurTable();                                                       // Set up the color TV emulation blur table
909         running = true;                                                         // Set running status...
910
911         InitializeEventList();                                          // Clear the event list before we use it...
912         SetCallbackTime(FrameCallback, 16666.66666667); // Set frame to fire at 1/60 s interval
913         SetCallbackTime(BlinkTimer, 250000);            // Set up blinking at 1/4 s intervals
914         startTicks = SDL_GetTicks();
915
916 #ifdef THREADED_65C02
917 //cpuMutex = SDL_CreateMutex();
918 //mutex must be locked for conditional to work...
919 //if (SDL_mutexP(cpuMutex) == -1)
920 //{
921 //      printf("Couldn't lock CPU mutex!\n");
922 //      exit(-1);
923 //}
924 cpuCond = SDL_CreateCond();
925 //printf("mutex=$%08X, cond=$%08X\n", cpuMutex, cpuCond);
926 //cpuSleep = true;
927 cpuThread = SDL_CreateThread(CPUThreadFunc, NULL);
928 //cpuSleep = false;
929 //SDL_CondSignal(cpuCond);
930 #endif
931
932         WriteLog("Entering main loop...\n");
933         while (running)
934         {
935                 double timeToNextEvent = GetTimeToNextEvent();
936 #ifndef THREADED_65C02
937                 Execute65C02(&mainCPU, USEC_TO_M6502_CYCLES(timeToNextEvent));
938 #endif
939 //We MUST remove a frame's worth of time in order for the CPU to function... !!! FIX !!!
940 //(Fix so that this is not a requirement!)
941 //Fixed, but mainCPU.clock is destroyed in the bargain. Oh well.
942 //              mainCPU.clock -= USEC_TO_M6502_CYCLES(timeToNextEvent);
943 #ifdef CPU_CLOCK_CHECKING
944 totalCPU += USEC_TO_M6502_CYCLES(timeToNextEvent);
945 #endif
946                 // Handle CPU time delta for sound...
947 //Don't need this anymore now that we use absolute time...
948 //              AddToSoundTimeBase(USEC_TO_M6502_CYCLES(timeToNextEvent));
949                 HandleNextEvent();
950         }
951
952 #ifdef THREADED_65C02
953 cpuFinished = true;
954 SDL_CondSignal(cpuCond);//thread is asleep, wake it up
955 SDL_WaitThread(cpuThread, NULL);
956 SDL_DestroyCond(cpuCond);
957 //SDL_DestroyMutex(cpuMutex);
958 #endif
959
960         if (settings.autoStateSaving)
961         {
962                 // Save state here...
963                 SaveApple2State(settings.autoStatePath);
964         }
965 floppyDrive.SaveImage();
966
967         SoundDone();
968         VideoDone();
969         SaveSettings();
970         LogDone();
971
972         return 0;
973 }
974
975 /*
976 Apple II keycodes
977 -----------------
978
979 Key     Aln CTL SHF BTH
980 -----------------------
981 space   $A0     $A0     $A0 $A0         No xlation
982 RETURN  $8D     $8D     $8D     $8D             No xlation
983 0               $B0     $B0     $B0     $B0             Need to screen shift+0 (?)
984 1!              $B1 $B1 $A1 $A1         No xlation
985 2"              $B2     $B2     $A2     $A2             No xlation
986 3#              $B3     $B3     $A3     $A3             No xlation
987 4$              $B4     $B4     $A4     $A4             No xlation
988 5%              $B5     $B5     $A5     $A5             No xlation
989 6&              $B6     $B6     $A6     $A6             No xlation
990 7'              $B7     $B7     $A7     $A7             No xlation
991 8(              $B8     $B8     $A8     $A8             No xlation
992 9)              $B9     $B9     $A9     $A9             No xlation
993 :*              $BA     $BA     $AA     $AA             No xlation
994 ;+              $BB     $BB     $AB     $AB             No xlation
995 ,<              $AC     $AC     $BC     $BC             No xlation
996 -=              $AD     $AD     $BD     $BD             No xlation
997 .>              $AE     $AE     $BE     $BE             No xlation
998 /?              $AF     $AF     $BF     $BF             No xlation
999 A               $C1     $81     $C1     $81
1000 B               $C2     $82     $C2     $82
1001 C               $C3     $83     $C3     $83
1002 D               $C4     $84     $C4     $84
1003 E               $C5     $85     $C5     $85
1004 F               $C6     $86     $C6     $86
1005 G               $C7     $87     $C7     $87
1006 H               $C8     $88     $C8     $88
1007 I               $C9     $89     $C9     $89
1008 J               $CA     $8A     $CA     $8A
1009 K               $CB     $8B     $CB     $8B
1010 L               $CC     $8C     $CC     $8C
1011 M               $CD     $8D     $DD     $9D             -> ODD
1012 N^              $CE     $8E     $DE     $9E             -> ODD
1013 O               $CF     $8F     $CF     $8F
1014 P@              $D0     $90     $C0     $80             Need to xlate CTL+SHFT+P & SHFT+P (?)
1015 Q               $D1     $91     $D1     $91
1016 R               $D2     $92     $D2     $92
1017 S               $D3     $93     $D3     $93
1018 T               $D4     $94     $D4     $94
1019 U               $D5     $95     $D5     $95
1020 V               $D6     $96     $D6     $96
1021 W               $D7     $97     $D7     $97
1022 X               $D8     $98     $D8     $98
1023 Y               $D9     $99     $D9     $99
1024 Z               $DA     $9A     $DA     $9A
1025 <-              $88     $88     $88     $88
1026 ->              $95     $95     $95     $95
1027 ESC             $9B     $9B     $9B     $9B             No xlation
1028
1029 */
1030 static void FrameCallback(void)
1031 {
1032         SDL_Event event;
1033
1034         while (SDL_PollEvent(&event))
1035         {
1036                 switch (event.type)
1037                 {
1038                 case SDL_KEYDOWN:
1039                         if (event.key.keysym.unicode != 0)
1040                         {
1041 //Need to do some key translation here, and screen out non-apple keys as well...
1042                                 if (event.key.keysym.sym == SDLK_TAB)   // Prelim key screening...
1043                                         break;
1044
1045                                 lastKeyPressed = event.key.keysym.unicode;
1046                                 keyDown = true;
1047                                 //kludge: should have a caps lock thingy here...
1048                                 //or all uppercase for ][+...
1049                                 if (lastKeyPressed >= 'a' && lastKeyPressed <='z')
1050                                         lastKeyPressed &= 0xDF;         // Convert to upper case...
1051                         }
1052
1053                         // CTRL+RESET key emulation (mapped to CTRL+`)
1054 // This doesn't work...
1055 //                      if (event.key.keysym.sym == SDLK_BREAK && (event.key.keysym.mod & KMOD_CTRL))
1056 //                      if (event.key.keysym.sym == SDLK_PAUSE && (event.key.keysym.mod & KMOD_CTRL))
1057                         if (event.key.keysym.sym == SDLK_BACKQUOTE && (event.key.keysym.mod & KMOD_CTRL))
1058 //NOTE that this shouldn't take place until the key is lifted... !!! FIX !!!
1059 //ALSO it seems to leave the machine in an inconsistent state vis-a-vis the language card...
1060                                 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
1061
1062                         if (event.key.keysym.sym == SDLK_RIGHT)
1063                                 lastKeyPressed = 0x15, keyDown = true;
1064                         else if (event.key.keysym.sym == SDLK_LEFT)
1065                                 lastKeyPressed = 0x08, keyDown = true;
1066
1067                         // Use ALT+Q to exit, as well as the usual window decoration method
1068                         if (event.key.keysym.sym == SDLK_q && (event.key.keysym.mod & KMOD_ALT))
1069                                 running = false;
1070
1071                         if (event.key.keysym.sym == SDLK_F12)
1072                                 dumpDis = !dumpDis;                             // Toggle the disassembly process
1073 //                      else if (event.key.keysym.sym == SDLK_F11)
1074 //                              floppyDrive.LoadImage("./disks/bt1_char.dsk");//Kludge to load char disk...
1075 else if (event.key.keysym.sym == SDLK_F9)
1076 {
1077         floppyDrive.CreateBlankImage();
1078 //      SpawnMessage("Image cleared...");
1079 }//*/
1080 else if (event.key.keysym.sym == SDLK_F10)
1081 {
1082         floppyDrive.SwapImages();
1083 //      SpawnMessage("Image swapped...");
1084 }//*/
1085
1086                         if (event.key.keysym.sym == SDLK_F2)// Toggle the palette
1087                                 TogglePalette();
1088                         else if (event.key.keysym.sym == SDLK_F3)// Cycle through screen types
1089                                 CycleScreenTypes();
1090
1091 //                      if (event.key.keysym.sym == SDLK_F5)    // Temp GUI launch key
1092                         if (event.key.keysym.sym == SDLK_F1)    // GUI launch key
1093 //NOTE: Should parse the output to determine whether or not the user requested
1094 //      to quit completely... !!! FIX !!!
1095                                 gui->Run();
1096
1097                         if (event.key.keysym.sym == SDLK_F5)
1098                         {
1099                                 VolumeDown();
1100                                 char volStr[10] = "*********";
1101                                 volStr[GetVolume()] = 0;
1102                                 SpawnMessage("Volume: %s", volStr);
1103                         }
1104                         else if (event.key.keysym.sym == SDLK_F6)
1105                         {
1106                                 VolumeUp();
1107                                 char volStr[10] = "*********";
1108                                 volStr[GetVolume()] = 0;
1109                                 SpawnMessage("Volume: %s", volStr);
1110                         }
1111
1112                         break;
1113                 case SDL_QUIT:
1114                         running = false;
1115                 }
1116         }
1117
1118         RenderVideoFrame();
1119         SetCallbackTime(FrameCallback, 16666.66666667);
1120
1121 #ifdef CPU_CLOCK_CHECKING
1122 counter++;
1123 if (counter == 60)
1124 {
1125         printf("Executed %u cycles...\n", totalCPU);
1126         totalCPU = 0;
1127         counter = 0;
1128 }
1129 #endif
1130 #ifdef THREADED_65C02
1131         SDL_CondSignal(cpuCond);//OK, let the CPU go another frame...
1132 #endif
1133 //Instead of this, we should yield remaining time to other processes... !!! FIX !!!
1134 //lessee...
1135 //nope.
1136 //Actually, slows things down too much...
1137 //SDL_Delay(10);
1138         while (SDL_GetTicks() - startTicks < 16);       // Wait for next frame...
1139         startTicks = SDL_GetTicks();
1140 }
1141
1142 static void BlinkTimer(void)
1143 {
1144         flash = !flash;
1145         SetCallbackTime(BlinkTimer, 250000);            // Set up blinking at 1/4 sec intervals
1146 }
1147
1148 /*
1149 Next problem is this: How to have events occur and synchronize with the rest
1150 of the threads?
1151
1152   o Have the CPU thread manage the timer mechanism? (need to have a method of carrying
1153     remainder CPU cycles over...)
1154
1155 */