2 // Stargate Emulator (StarGem2) v2.0 SDL
5 // (C) 2006, 2023 Underground Software
7 // JLH = James Hammons <jlhamm@acm.org>
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)
39 #define SOUNDROM "ROMs/sg.snd"
40 #define CMOS "cmos.ram"
41 #define SAVESTATE "sg2.state"
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)
47 // Function prototypes
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);
56 bool LoadMachineState(void);
57 void SaveMachineState(void);
59 // Local timer callback functions
61 static void FrameCallback(void);
62 static void ScanlineCallback(void);
66 uint8_t gram[0x10000], grom[0x10000], sram[0x10000], srom[0x10000]; // RAM & ROM spaces
70 V6821PIA pia2(Handle6809IRQ, Handle6809IRQ);
72 uint32_t palette[256];
73 bool paletteDirty = false;
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
85 int main(int /*argc*/, char * /*argv*/[])
87 InitLog("stargem2.log");
88 WriteLog("StarGem2 - A portable Stargate emulator by James Hammons\n");
89 WriteLog("(C) 2023 Underground Software\n\n");
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++)
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;
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;
107 memset(gram, 0, 0x10000);
108 memset(grom, 0, 0x10000);
109 memset(sram, 0, 0x10000);
110 memset(srom, 0, 0x10000);
112 // Set up V6809 & V6808 execution contexts
114 memset(&mainCPU, 0, sizeof(V6809REGS));
115 mainCPU.RdMem = RdMem6809;
116 mainCPU.WrMem = WrMem6809;
117 mainCPU.cpuFlags |= V6809_LINE_RESET;
119 memset(&soundCPU, 0, sizeof(V6808REGS));
120 soundCPU.RdMem = RdMem6808;
121 soundCPU.WrMem = WrMem6808;
122 soundCPU.cpuFlags |= V6808_LINE_RESET;
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"
129 for(int i=0; i<12; i++)
131 uint32_t baseAddress = i * 0x1000;
134 baseAddress += 0x4000;
136 if (!LoadImg(ROMs[i], grom + baseAddress, 0x1000))
138 WriteLog("Could not open file '%s'!\n", ROMs[i]);
143 if (!LoadImg(SOUNDROM, srom + 0xF800, 0x800))
145 WriteLog("Could not open file '%s'!\n", SOUNDROM);
149 WriteLog("Stargate ROM images loaded...\n");
150 WriteLog("About to initialize video...\n");
154 cout << "Aborting!" << endl;
158 // Have to do this *after* video init but *before* sound init...!
159 WriteLog("About to load machine state...");
161 if (!LoadMachineState())
162 WriteLog("Machine state file not found!\n");
166 if (!LoadImg(CMOS, gram + 0xCC00, 0x400))
167 WriteLog("CMOS RAM not found!\n");
169 WriteLog("About to intialize audio...\n");
171 keys = SDL_GetKeyboardState(NULL);
172 srom[0xF800] = 0x37; // Fix checksum so Self-Test works...
173 running = true; // Set running status...
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();
181 WriteLog("Entering main loop...\n");
185 double timeToNextEvent = GetTimeToNextEvent();
186 Execute6809(&mainCPU, USEC_TO_M6809_CYCLES(timeToNextEvent));
200 // Load a file into RAM/ROM image space
202 bool LoadImg(const char * filename, uint8_t * ram, int size)
204 FILE * fp = fopen(filename, "rb");
209 size_t ignoredResult = fread(ram, 1, size, fp);
220 FILE * fp = fopen(CMOS, "wb");
224 WriteLog("CMOS RAM not saved!\n");
228 size_t ignoredResult = fwrite(gram + 0xCC00, 1, 1024, fp);
233 // Load state save file
235 bool LoadMachineState(void)
237 FILE * fp = fopen(SAVESTATE, "rb");
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);
251 // Set up backbuffer... ;-)
252 for(uint16_t i=0x0006; i<0x97F8; i++) // Screen memory
253 WrMem6809(i, gram[i]);
255 for(uint16_t i=0xC000; i<=0xC00F; i++) // Palette memory
256 WrMem6809(i, gram[i]);
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...
266 mainCPU.clockOverrun = 0; // And overrun values...
267 //notyet soundCPU.clockOverrun = 0;
270 pia2.IRQA = Handle6809IRQ;
271 pia2.IRQB = Handle6809IRQ;
277 // Save state save file
279 void SaveMachineState(void)
281 FILE * fp = fopen(SAVESTATE, "wb");
285 WriteLog("Machine state not saved!\n");
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);
299 // 6809 memory functions
301 uint8_t RdMem6809(uint16_t addr)
305 if (addr >= 0x9000 && addr <= 0xCFFF) // No ROM between $9000 - $CFFF...
309 if (!gram[0xC900] && addr <= 0x8FFF) // Check RAM $C900 bank switch
315 if ((addr >= 0xC804) && (addr <= 0xC807))
317 else if ((addr >= 0xC80C) && (addr <= 0xC80F))
319 // A wee kludge (though I doubt it reads from anywhere other than $CB00)...
320 else if ((addr & 0xFF00) == 0xCB00)
322 double elapsedCycles = (double)(GetCurrentV6809Clock() - clockFrameStart);
323 uint32_t scanline = (uint32_t)(elapsedCycles / SCANLINE_DURATION_IN_CYCLES);
325 b = (uint8_t)scanline & 0xFC; // Only bits 2-7 are connected...
331 void WrMem6809(uint16_t addr, uint8_t b)
335 if (addr >= 0x0006 && addr <= 0x97F6) // 304 pixels 152-128=24-16=8
337 // NOTE: Screen was 304 x 256, but we truncate the vertical dimension here...
338 uint16_t sx = (addr >> 7) & 0x01FE, sy = addr & 0x00FF;
340 if (sy >= 6 && sy <= 245)
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]];
347 else if (addr >= 0xC000 && addr <= 0xC00F)
349 // This approach doesn't take the BG color to the edges of the screen
350 color[addr & 0x0F] = b;
353 else if (addr == 0xC80E)
355 // Only write if not writing the DDR...
356 if ((pia2.crb & 0x04) != 0)
358 sram[0x0402] = b; // Connect PIAs in 6809 & 6808
359 soundCPU.cpuFlags |= V6808_LINE_IRQ; // Start sound IRQ
363 if ((addr >= 0xC804) && (addr <= 0xC807))
365 else if ((addr >= 0xC80C) && (addr <= 0xC80F))
369 void Handle6809IRQ(bool state)
371 // N.B.: The IRQ line is active LOW
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;
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;
387 // 6808 memory functions
389 uint8_t RdMem6808(uint16_t addr)
391 return (addr < 0xF000 ? sram[addr] : srom[addr]);
394 void WrMem6808(uint16_t addr, uint8_t b)
400 // Stargate frame callback
402 static void FrameCallback(void)
404 SDL_PumpEvents(); // Force key events into the buffer.
405 pia1.pa = pia1.pb = pia2.pa = 0; // Clear inputs...
407 // gram[0xC80C] = 0x80;//temp, for testing (Hand Shake from sound board)
409 if (keys[SDL_SCANCODE_ESCAPE])
410 running = false; // ESC to exit...
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;
421 if (keys[settings.keyBindings[S_KEY_UP]]) pia1.pb |= 0x01;
422 if (keys[settings.keyBindings[S_KEY_INVISO]]) pia1.pb |= 0x02;
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;
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;
439 for(uint32_t addr=0x0006; addr<0x97F7; addr++)
441 uint16_t sx = (addr >> 7) & 0x01FE, sy = addr & 0x00FF;
443 if (sy > 5 && sy < 246)
445 uint32_t saddr = 8 + sx + ((sy - 6) * 320); // Calc screen address
446 uint8_t sb = gram[addr];
448 scrBuffer[saddr + 0] = palette[color[sb >> 4]];
449 scrBuffer[saddr + 1] = palette[color[sb & 0x0F]];
453 paletteDirty = false;
456 static bool fullscreenDebounce = false;
458 if (keys[SDL_SCANCODE_F12])
460 if (!fullscreenDebounce)
463 fullscreenDebounce = true;
467 fullscreenDebounce = false;
469 RenderScreenBuffer(); // 1 frame = 1/60 sec ~ 16667 cycles
470 clockFrameStart = mainCPU.clock;
472 // Wait for next frame...
473 while (SDL_GetTicks() - startTicks < 16)
476 startTicks = SDL_GetTicks();
477 SetCallbackTime(FrameCallback, FRAME_DURATION_IN_CYCLES * M6809_CYCLE_IN_USEC);
480 static void ScanlineCallback(void)
483 What we've proven so far:
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
489 double elapsedCycles = (double)(GetCurrentV6809Clock() - clockFrameStart);
490 uint32_t scanline = (uint32_t)((elapsedCycles / SCANLINE_DURATION_IN_CYCLES) + 0.5);
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);
497 SetCallbackTime(ScanlineCallback, SG2_PIA_CALLBACK_DURATION);