]> Shamusworld >> Repos - stargem2/blob - src/stargem2.cpp
Finally fixed problems with demo mode.
[stargem2] / src / stargem2.cpp
1 //
2 // Stargate Emulator (StarGem2) v2.0 SDL
3 //
4 // by James Hammons
5 // (C) 2006, 2023 Underground Software
6 //
7 // JLH = James Hammons <jlhamm@acm.org>
8 //
9 // WHO  WHEN        WHAT
10 // ---  ----------  ------------------------------------------------------------
11 // JLH  06/15/2006  Added changelog ;-)
12 // JLH  06/15/2006  Switched over to timeslice execution code
13 // JLH  07/15/2009  Solved problem with DEMO mode (IRQ handling) (No, didn't)
14 // JLH  01/03/2023  FINALLY solved problem with DEMO mode (v6809 timing)
15 //
16
17 #include <SDL2/SDL.h>
18 #include <fstream>
19 #include <string>
20 #include <iomanip>
21 #include <iostream>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <time.h>
25 #include <stdint.h>
26 #include "dis6808.h"
27 #include "dis6809.h"
28 #include "log.h"
29 #include "settings.h"
30 #include "sound.h"
31 #include "timing.h"
32 #include "v6808.h"
33 #include "v6809.h"
34 #include "v6821.h"
35 #include "video.h"
36
37 using namespace std;
38
39 #define SOUNDROM        "ROMs/sg.snd"
40 #define CMOS            "cmos.ram"
41 #define SAVESTATE       "sg2.state"
42
43 #define FRAME_DURATION_IN_CYCLES                (M6809_CLOCK_SPEED_IN_HZ / 60.0)
44 #define SCANLINE_DURATION_IN_CYCLES             (FRAME_DURATION_IN_CYCLES / 256.0)
45 #define SG2_PIA_CALLBACK_DURATION               (SCANLINE_DURATION_IN_CYCLES * M6809_CYCLE_IN_USEC * 16.0)
46
47 // Function prototypes
48
49 uint8_t RdMem6809(uint16_t addr);
50 void WrMem6809(uint16_t addr, uint8_t b);
51 void Handle6809IRQ(bool);
52 uint8_t RdMem6808(uint16_t addr);
53 void WrMem6808(uint16_t addr, uint8_t b);
54 bool LoadImg(const char * filename, uint8_t * ram, int size);
55 void SaveCMOS(void);
56 bool LoadMachineState(void);
57 void SaveMachineState(void);
58
59 // Local timer callback functions
60
61 static void FrameCallback(void);
62 static void ScanlineCallback(void);
63
64 // Global variables
65
66 uint8_t gram[0x10000], grom[0x10000], sram[0x10000], srom[0x10000]; // RAM & ROM spaces
67 V6809REGS mainCPU;
68 V6808REGS soundCPU;
69 V6821PIA pia1;
70 V6821PIA pia2(Handle6809IRQ, Handle6809IRQ);
71 uint8_t color[16];
72 uint32_t palette[256];
73 bool paletteDirty = false;
74
75 // Local variables
76
77 static bool running = true;                     // Machine running state flag...
78 static uint32_t startTicks;
79 static const uint8_t * keys;            // SDL raw keyboard matrix
80 uint64_t clockFrameStart;                       // V6809 clock at the start of the frame
81
82 //
83 // Main loop
84 //
85 int main(int /*argc*/, char * /*argv*/[])
86 {
87         InitLog("stargem2.log");
88         WriteLog("StarGem2 - A portable Stargate emulator by James Hammons\n");
89         WriteLog("(C) 2023 Underground Software\n\n");
90
91         LoadSettings();
92
93         // Initialize Williams' palette (RGB coded as: 3 bits red, 3 bits green, 2 bits blue)
94         for(uint32_t i=0; i<256; i++)
95                 palette[i] =
96 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
97                 (((i & 0x01) * 33 + ((i & 0x02) >> 1) * 71 + ((i & 0x04) >> 2) * 151) << 24)
98                         | ((((i & 0x08) >> 3) * 33 + ((i & 0x10) >> 4) * 71 + ((i & 0x20) >> 5) * 151) << 16)
99                         | ((((i & 0x40) >> 6) * 71 + ((i & 0x80) >> 7) * 151) << 8) | 0xFF;
100 #else
101                 ((i & 0x01) * 33 + ((i & 0x02) >> 1) * 71 + ((i & 0x04) >> 2) * 151)
102                         | ((((i & 0x08) >> 3) * 33 + ((i & 0x10) >> 4) * 71 + ((i & 0x20) >> 5) * 151) << 8)
103                         | ((((i & 0x40) >> 6) * 71 + ((i & 0x80) >> 7) * 151) << 16) | 0xFF000000;
104 #endif
105
106         // Zero out memory
107         memset(gram, 0, 0x10000);
108         memset(grom, 0, 0x10000);
109         memset(sram, 0, 0x10000);
110         memset(srom, 0, 0x10000);
111
112         // Set up V6809 & V6808 execution contexts
113
114         memset(&mainCPU, 0, sizeof(V6809REGS));
115         mainCPU.RdMem = RdMem6809;
116         mainCPU.WrMem = WrMem6809;
117         mainCPU.cpuFlags |= V6809_LINE_RESET;
118
119         memset(&soundCPU, 0, sizeof(V6808REGS));
120         soundCPU.RdMem = RdMem6808;
121         soundCPU.WrMem = WrMem6808;
122         soundCPU.cpuFlags |= V6808_LINE_RESET;
123
124         char ROMs[12][8] = {
125                 "ROMs/01", "ROMs/02", "ROMs/03", "ROMs/04", "ROMs/05", "ROMs/06",
126                 "ROMs/07", "ROMs/08", "ROMs/09", "ROMs/10", "ROMs/11", "ROMs/12"
127         };
128
129         for(int i=0; i<12; i++)
130         {
131                 uint32_t baseAddress = i * 0x1000;
132
133                 if (i > 8)
134                         baseAddress += 0x4000;
135
136                 if (!LoadImg(ROMs[i], grom + baseAddress, 0x1000))
137                 {
138                         WriteLog("Could not open file '%s'!\n", ROMs[i]);
139                         return -1;
140                 }
141         }
142
143         if (!LoadImg(SOUNDROM, srom + 0xF800, 0x800))
144         {
145                 WriteLog("Could not open file '%s'!\n", SOUNDROM);
146                 return -1;
147         }
148
149         WriteLog("Stargate ROM images loaded...\n");
150         WriteLog("About to initialize video...\n");
151
152         if (!InitVideo())
153         {
154                 cout << "Aborting!" << endl;
155                 return -1;
156         }
157
158         // Have to do this *after* video init but *before* sound init...!
159         WriteLog("About to load machine state...");
160
161         if (!LoadMachineState())
162                 WriteLog("Machine state file not found!\n");
163         else
164                 WriteLog("done!\n");
165
166         if (!LoadImg(CMOS, gram + 0xCC00, 0x400))
167                 WriteLog("CMOS RAM not found!\n");
168
169         WriteLog("About to intialize audio...\n");
170         SoundInit();
171         keys = SDL_GetKeyboardState(NULL);
172         srom[0xF800] = 0x37;                    // Fix checksum so Self-Test works...
173         running = true;                                 // Set running status...
174
175         InitializeEventList();                  // Clear the event list before we use it...
176         SetCallbackTime(FrameCallback, FRAME_DURATION_IN_CYCLES * M6809_CYCLE_IN_USEC);
177         SetCallbackTime(ScanlineCallback, SG2_PIA_CALLBACK_DURATION);
178         clockFrameStart = mainCPU.clock;
179         startTicks = SDL_GetTicks();
180
181         WriteLog("Entering main loop...\n");
182
183         while (running)
184         {
185                 double timeToNextEvent = GetTimeToNextEvent();
186                 Execute6809(&mainCPU, USEC_TO_M6809_CYCLES(timeToNextEvent));
187                 HandleNextEvent();
188         }
189
190         SoundDone();
191         VideoDone();
192         SaveCMOS();
193         SaveMachineState();
194         LogDone();
195
196         return 0;
197 }
198
199 //
200 // Load a file into RAM/ROM image space
201 //
202 bool LoadImg(const char * filename, uint8_t * ram, int size)
203 {
204         FILE * fp = fopen(filename, "rb");
205
206         if (fp == NULL)
207                 return false;
208
209         size_t ignoredResult = fread(ram, 1, size, fp);
210         fclose(fp);
211
212         return true;
213 }
214
215 //
216 // Save CMOS ram
217 //
218 void SaveCMOS(void)
219 {
220         FILE * fp = fopen(CMOS, "wb");
221
222         if (fp == NULL)
223         {
224                 WriteLog("CMOS RAM not saved!\n");
225                 return;
226         }
227
228         size_t ignoredResult = fwrite(gram + 0xCC00, 1, 1024, fp);
229         fclose(fp);
230 }
231
232 //
233 // Load state save file
234 //
235 bool LoadMachineState(void)
236 {
237         FILE * fp = fopen(SAVESTATE, "rb");
238
239         if (fp == NULL)
240                 return false;
241
242         // This is kinda crappy--we don't do any sanity checking here!!!
243         size_t ignored = fread(gram, 1, 0x10000, fp);
244         ignored = fread(sram, 1, 0x10000, fp);
245         ignored = fread(&mainCPU, 1, sizeof(V6809REGS), fp);
246         ignored = fread(&soundCPU, 1, sizeof(V6808REGS), fp);
247         ignored = fread(&pia1, 1, sizeof(V6821PIA), fp);
248         ignored = fread(&pia2, 1, sizeof(V6821PIA), fp);
249         fclose(fp);
250
251         // Set up backbuffer...  ;-)
252         for(uint16_t i=0x0006; i<0x97F8; i++)   // Screen memory
253                 WrMem6809(i, gram[i]);
254
255         for(uint16_t i=0xC000; i<=0xC00F; i++)  // Palette memory
256                 WrMem6809(i, gram[i]);
257
258         paletteDirty = true;
259
260         mainCPU.RdMem = RdMem6809;                              // Make sure our function pointers are
261         mainCPU.WrMem = WrMem6809;                              // pointing to the right places!
262         soundCPU.RdMem = RdMem6808;
263         soundCPU.WrMem = WrMem6808;
264         mainCPU.clock = 0;                                              // Zero out our clocks...
265         soundCPU.clock = 0;
266         mainCPU.clockOverrun = 0;                               // And overrun values...
267 //notyet        soundCPU.clockOverrun = 0;
268         pia1.IRQA = NULL;
269         pia1.IRQB = NULL;
270         pia2.IRQA = Handle6809IRQ;
271         pia2.IRQB = Handle6809IRQ;
272
273         return true;
274 }
275
276 //
277 // Save state save file
278 //
279 void SaveMachineState(void)
280 {
281         FILE * fp = fopen(SAVESTATE, "wb");
282
283         if (fp == NULL)
284         {
285                 WriteLog("Machine state not saved!\n");
286                 return;
287         }
288
289         size_t ignored = fwrite(gram, 1, 0x10000, fp);
290         ignored = fwrite(sram, 1, 0x10000, fp);
291         ignored = fwrite(&mainCPU, 1, sizeof(V6809REGS), fp);
292         ignored = fwrite(&soundCPU, 1, sizeof(V6808REGS), fp);
293         ignored = fwrite(&pia1, 1, sizeof(V6821PIA), fp);
294         ignored = fwrite(&pia2, 1, sizeof(V6821PIA), fp);
295         fclose(fp);
296 }
297
298 //
299 // 6809 memory functions
300 //
301 uint8_t RdMem6809(uint16_t addr)
302 {
303         uint8_t b;
304
305         if (addr >= 0x9000 && addr <= 0xCFFF)           // No ROM between $9000 - $CFFF...
306                 b = gram[addr];
307         else
308         {
309                 if (!gram[0xC900] && addr <= 0x8FFF)    // Check RAM $C900 bank switch
310                         b = gram[addr];
311                 else
312                         b = grom[addr];
313         }
314
315         if ((addr >= 0xC804) && (addr <= 0xC807))
316                 b = pia1.Read(addr);
317         else if ((addr >= 0xC80C) && (addr <= 0xC80F))
318                 b = pia2.Read(addr);
319         // A wee kludge (though I doubt it reads from anywhere other than $CB00)...
320         else if ((addr & 0xFF00) == 0xCB00)
321         {
322                 double elapsedCycles = (double)(GetCurrentV6809Clock() - clockFrameStart);
323                 uint32_t scanline = (uint32_t)(elapsedCycles / SCANLINE_DURATION_IN_CYCLES);
324
325                 b = (uint8_t)scanline & 0xFC;                   // Only bits 2-7 are connected...
326         }
327
328         return b;
329 }
330
331 void WrMem6809(uint16_t addr, uint8_t b)
332 {
333         gram[addr] = b;
334
335         if (addr >= 0x0006 && addr <= 0x97F6)           // 304 pixels  152-128=24-16=8
336         {
337                 // NOTE: Screen was 304 x 256, but we truncate the vertical dimension here...
338                 uint16_t sx = (addr >> 7) & 0x01FE, sy = addr & 0x00FF;
339
340                 if (sy >= 6 && sy <= 245)
341                 {
342                         uint32_t saddr = 8 + sx + ((sy - 6) * 320);     // Calc screen address
343                         scrBuffer[saddr + 0] = palette[color[b >> 4]];
344                         scrBuffer[saddr + 1] = palette[color[b & 0x0F]];
345                 }
346         }
347         else if (addr >= 0xC000 && addr <= 0xC00F)
348         {
349                 // This approach doesn't take the BG color to the edges of the screen
350                 color[addr & 0x0F] = b;
351                 paletteDirty = true;
352         }
353         else if (addr == 0xC80E)
354         {
355                 // Only write if not writing the DDR...
356                 if ((pia2.crb & 0x04) != 0)
357                 {
358                         sram[0x0402] = b;                                               // Connect PIAs in 6809 & 6808
359                         soundCPU.cpuFlags |= V6808_LINE_IRQ;    // Start sound IRQ
360                 }
361         }
362
363         if ((addr >= 0xC804) && (addr <= 0xC807))
364                 pia1.Write(addr, b);
365         else if ((addr >= 0xC80C) && (addr <= 0xC80F))
366                 pia2.Write(addr, b);
367 }
368
369 void Handle6809IRQ(bool state)
370 {
371         // N.B.: The IRQ line is active LOW
372         if (state)
373         {
374                 // We do both, because we don't know if we're in an execution context or not...  :-P
375                 SetLineOfCurrentV6809(V6809_LINE_IRQ);
376                 mainCPU.cpuFlags |= V6809_LINE_IRQ;
377         }
378         else
379         {
380                 // We do both, because we don't know if we're in an execution context or not...  :-P
381                 ClearLineOfCurrentV6809(V6809_LINE_IRQ);
382                 mainCPU.cpuFlags &= ~V6809_LINE_IRQ;
383         }
384 }
385
386 //
387 // 6808 memory functions
388 //
389 uint8_t RdMem6808(uint16_t addr)
390 {
391         return (addr < 0xF000 ? sram[addr] : srom[addr]);
392 }
393
394 void WrMem6808(uint16_t addr, uint8_t b)
395 {
396         sram[addr] = b;
397 }
398
399 //
400 // Stargate frame callback
401 //
402 static void FrameCallback(void)
403 {
404         SDL_PumpEvents();                                                       // Force key events into the buffer.
405         pia1.pa = pia1.pb = pia2.pa = 0;                        // Clear inputs...
406 //don't do nuthin'
407 //      gram[0xC80C] = 0x80;//temp, for testing (Hand Shake from sound board)
408
409         if (keys[SDL_SCANCODE_ESCAPE])
410                 running = false;                                                // ESC to exit...
411
412         if (keys[settings.keyBindings[S_KEY_FIRE]])                     pia1.pa |= 0x01;
413         if (keys[settings.keyBindings[S_KEY_THRUST]])           pia1.pa |= 0x02;
414         if (keys[settings.keyBindings[S_KEY_SMARTBOMB]])        pia1.pa |= 0x04;
415         if (keys[settings.keyBindings[S_KEY_HYPERSPACE]])       pia1.pa |= 0x08;
416         if (keys[settings.keyBindings[S_KEY_2P_START]])         pia1.pa |= 0x10;
417         if (keys[settings.keyBindings[S_KEY_1P_START]])         pia1.pa |= 0x20;
418         if (keys[settings.keyBindings[S_KEY_REVERSE]])          pia1.pa |= 0x40;
419         if (keys[settings.keyBindings[S_KEY_DOWN]])                     pia1.pa |= 0x80;
420
421         if (keys[settings.keyBindings[S_KEY_UP]])                       pia1.pb |= 0x01;
422         if (keys[settings.keyBindings[S_KEY_INVISO]])           pia1.pb |= 0x02;
423
424         if (keys[settings.keyBindings[S_KEY_AUTO_UP]])          pia2.pa |= 0x01;
425         if (keys[settings.keyBindings[S_KEY_ADVANCE]])          pia2.pa |= 0x02;
426         if (keys[settings.keyBindings[S_KEY_RIGHT_COIN]])       pia2.pa |= 0x04;
427         if (keys[settings.keyBindings[S_KEY_HS_RESET]])         pia2.pa |= 0x08;
428         if (keys[settings.keyBindings[S_KEY_LEFT_COIN]])        pia2.pa |= 0x10;
429         if (keys[settings.keyBindings[S_KEY_CENTER_COIN]])      pia2.pa |= 0x20;
430         if (keys[settings.keyBindings[S_KEY_SLAM_SWITCH]])      pia2.pa |= 0x40;
431
432         if (keys[SDL_SCANCODE_F5])                                      // Sound CPU self-test (F5)
433                 soundCPU.cpuFlags |= V6808_LINE_NMI;
434         if (keys[SDL_SCANCODE_F6])                                      // Reset the 6808 (F6)
435                 soundCPU.cpuFlags |= V6808_LINE_RESET;
436
437         if (paletteDirty)
438         {
439                 for(uint32_t addr=0x0006; addr<0x97F7; addr++)
440                 {
441                         uint16_t sx = (addr >> 7) & 0x01FE, sy = addr & 0x00FF;
442
443                         if (sy > 5 && sy < 246)
444                         {
445                                 uint32_t saddr = 8 + sx + ((sy - 6) * 320);     // Calc screen address
446                                 uint8_t sb = gram[addr];
447
448                                 scrBuffer[saddr + 0] = palette[color[sb >> 4]];
449                                 scrBuffer[saddr + 1] = palette[color[sb & 0x0F]];
450                         }
451                 }
452
453                 paletteDirty = false;
454         }
455
456         static bool fullscreenDebounce = false;
457
458         if (keys[SDL_SCANCODE_F12])
459         {
460                 if (!fullscreenDebounce)
461                 {
462                         ToggleFullScreen();
463                         fullscreenDebounce = true;
464                 }
465         }
466         else
467                 fullscreenDebounce = false;
468
469         RenderScreenBuffer();                                   // 1 frame = 1/60 sec ~ 16667 cycles
470         clockFrameStart = mainCPU.clock;
471
472         // Wait for next frame...
473         while (SDL_GetTicks() - startTicks < 16)
474                 SDL_Delay(1);
475
476         startTicks = SDL_GetTicks();
477         SetCallbackTime(FrameCallback, FRAME_DURATION_IN_CYCLES * M6809_CYCLE_IN_USEC);
478 }
479
480 static void ScanlineCallback(void)
481 {
482 /*
483 What we've proven so far:
484
485  - The COUNT240 IRQ *NEVER* fires on MAME driver (it can't, PACTL is never set to allow it and an IRQ never fires anywhere around scanline 240)
486  - In the demo, the IRQs fire on 0, 82 (sometimes, 87, 90-112), 128, & 192
487  - In the HS screen the IRQs fire 0, 64, 128, & 192 exactly
488 */
489         double elapsedCycles = (double)(GetCurrentV6809Clock() - clockFrameStart);
490         uint32_t scanline = (uint32_t)((elapsedCycles / SCANLINE_DURATION_IN_CYCLES) + 0.5);
491
492         // N.B.: The 4 MS line toggles every 2.083 ms
493         //       Also: CA1 *never* asserts because PACTL is set to never allow IRQs
494         pia2.CB1(scanline & 0x20 ? true : false);
495         pia2.CA1(scanline >= 240 ? true : false);
496
497         SetCallbackTime(ScanlineCallback, SG2_PIA_CALLBACK_DURATION);
498 }