1 // DGen/SDL 1.17
2 // by Joe Groff <joe@pknet.com>
3 // Read LICENSE for copyright etc., but if you've seen one BSDish license,
4 // you've seen them all ;)
5 
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <unistd.h>
9 #include <sys/stat.h>
10 #include <sys/types.h>
11 #include <fcntl.h>
12 #include <string.h>
13 #include <stdint.h>
14 #include <limits.h>
15 #include <errno.h>
16 
17 #ifdef __MINGW32__
18 #include <windows.h>
19 #include <wincon.h>
20 #endif
21 
22 #define IS_MAIN_CPP
23 #include "system.h"
24 #include "md.h"
25 #include "pd.h"
26 #include "pd-defs.h"
27 #include "rc.h"
28 #include "rc-vars.h"
29 
30 #ifdef __BEOS__
31 #include <OS.h>
32 #endif
33 
34 #ifdef __MINGW32__
35 static long dgen_mingw_detach = 1;
36 #endif
37 
38 // Defined in ras.cpp, and set to true if the Genesis palette's changed.
39 extern int pal_dirty;
40 
41 FILE *debug_log = NULL;
42 
43 // Do a demo frame, if active
44 enum demo_status {
45 	DEMO_OFF,
46 	DEMO_RECORD,
47 	DEMO_PLAY
48 };
49 
do_demo(md & megad,FILE * demo,enum demo_status * status)50 static inline void do_demo(md& megad, FILE* demo, enum demo_status* status)
51 {
52 	uint32_t pad[2];
53 
54 	switch (*status) {
55 	case DEMO_OFF:
56 		break;
57 	case DEMO_RECORD:
58 		pad[0] = h2be32(megad.pad[0]);
59 		pad[1] = h2be32(megad.pad[1]);
60 		fwrite(&pad, sizeof(pad), 1, demo);
61 		break;
62 	case DEMO_PLAY:
63 		if (fread(&pad, sizeof(pad), 1, demo) == 1) {
64 			megad.pad[0] = be2h32(pad[0]);
65 			megad.pad[1] = be2h32(pad[1]);
66 		}
67 		else {
68 			if (feof(demo))
69 				pd_message("Demo finished.");
70 			else
71 				pd_message("Demo finished (read error).");
72 			*status = DEMO_OFF;
73 		}
74 		break;
75 	}
76 }
77 
78 // Temporary garbage can string :)
79 static char temp[65536] = "";
80 
81 // Show help and exit with code 2
help()82 static void help()
83 {
84   printf(
85   "DGen/SDL v" VER "\n"
86   "Usage: dgen [options] [romname [...]]\n\n"
87   "Where options are:\n"
88   "    -v              Print version number and exit.\n"
89   "    -r RCFILE       Read in the file RCFILE after parsing\n"
90   "                    $HOME/.dgen/dgenrc.\n"
91   "    -n USEC         Causes DGen to sleep USEC microseconds per frame, to\n"
92   "                    be nice to other processes.\n"
93   "    -p CODE,CODE... Takes a comma-delimited list of Game Genie (ABCD-EFGH)\n"
94   "                    or Hex (123456:ABCD) codes to patch the ROM with.\n"
95   "    -R (J|X|U|E| )  Force emulator region. Affects vertical resolution,\n"
96   "                    frame rate and ROM operation.\n"
97   "                    J: Japan (NTSC), X: Japan (PAL), U: America (NTSC)\n"
98   "                    E: Europe (PAL), ' ': auto (default).\n"
99   "    -N              Use NTSC mode (60Hz).\n"
100   "    -P              Use PAL mode (50Hz).\n"
101   "    -H HZ           Use a custom frame rate.\n"
102   "    -d DEMONAME     Record a demo of the game you are playing.\n"
103   "    -D DEMONAME     Play back a previously recorded demo.\n"
104   "    -s SLOT         Load the saved state from the given slot at startup.\n"
105 #ifdef __MINGW32__
106   "    -m              Do not detach from console.\n"
107 #endif
108   );
109   // Display platform-specific options
110   pd_help();
111   exit(2);
112 }
113 
114 // Save/load states
115 // It is externed from your implementation to change the current slot
116 // (I know this is a hack :)
117 int slot = 0;
md_save(md & megad)118 void md_save(md& megad)
119 {
120 	FILE *save;
121 	char file[64];
122 
123 	if (!megad.plugged) {
124 		pd_message("Cannot save state when no ROM is loaded.");
125 		return;
126 	}
127 	if (((size_t)snprintf(file,
128 			      sizeof(file),
129 			      "%s.gs%d",
130 			      megad.romname,
131 			      slot) >= sizeof(file)) ||
132 	    ((save = dgen_fopen("saves", file, DGEN_WRITE)) == NULL)) {
133 		snprintf(temp, sizeof(temp),
134 			 "Couldn't save state to slot %d!", slot);
135 		pd_message(temp);
136 		return;
137 	}
138 	megad.export_gst(save);
139 	fclose(save);
140 	snprintf(temp, sizeof(temp), "Saved state to slot %d.", slot);
141 	pd_message(temp);
142 }
143 
md_load(md & megad)144 void md_load(md& megad)
145 {
146 	FILE *load;
147 	char file[64];
148 
149 	if (!megad.plugged) {
150 		pd_message("Cannot restore state when no ROM is loaded.");
151 		return;
152 	}
153 	if (((size_t)snprintf(file,
154 			      sizeof(file),
155 			      "%s.gs%d",
156 			      megad.romname,
157 			      slot) >= sizeof(file)) ||
158 	    ((load = dgen_fopen("saves", file, DGEN_READ)) == NULL)) {
159 		snprintf(temp, sizeof(temp),
160 			 "Couldn't load state from slot %d!", slot);
161 		pd_message(temp);
162 		return;
163 	}
164 	megad.import_gst(load);
165 	fclose(load);
166 	snprintf(temp, sizeof(temp), "Loaded state from slot %d.", slot);
167 	pd_message(temp);
168 }
169 
170 // Load/save states from file
ram_save(md & megad)171 void ram_save(md& megad)
172 {
173 	FILE *save;
174 	int ret;
175 
176 	if (!megad.has_save_ram())
177 		return;
178 	save = dgen_fopen("ram", megad.romname, DGEN_WRITE);
179 	if (save == NULL)
180 		goto fail;
181 	ret = megad.put_save_ram(save);
182 	fclose(save);
183 	if (ret == 0)
184 		return;
185 fail:
186 	fprintf(stderr, "Couldn't save battery RAM to `%s'\n", megad.romname);
187 }
188 
ram_load(md & megad)189 void ram_load(md& megad)
190 {
191 	FILE *load;
192 	int ret;
193 
194 	if (!megad.has_save_ram())
195 		return;
196 	load = dgen_fopen("ram", megad.romname, DGEN_READ);
197 	if (load == NULL)
198 		goto fail;
199 	ret = megad.get_save_ram(load);
200 	fclose(load);
201 	if (ret == 0)
202 		return;
203 fail:
204 	fprintf(stderr, "Couldn't load battery RAM from `%s'\n",
205 		megad.romname);
206 }
207 
main(int argc,char * argv[])208 int main(int argc, char *argv[])
209 {
210   int c = 0, stop = 0, usec = 0, start_slot = -1;
211   unsigned long frames, frames_old, fps;
212   char *patches = NULL, *rom = NULL;
213   unsigned long oldclk, newclk, startclk, fpsclk;
214   FILE *file = NULL;
215   enum demo_status demo_status = DEMO_OFF;
216   unsigned int samples;
217   class md *megad;
218   bool first = true;
219   bool forced_hz = false;
220   bool forced_pal = false;
221 
222 	// Parse the RC file
223 	if ((dgen_autoconf) &&
224 	    ((file = dgen_fopen_autorc(DGEN_READ)) != NULL)) {
225 		parse_rc(file, DGEN_AUTORC);
226 		fclose(file);
227 	}
228 	if ((file = dgen_fopen_rc(DGEN_READ)) != NULL) {
229 		parse_rc(file, DGEN_RC);
230 		fclose(file);
231 		file = NULL;
232 		pd_rc();
233 	}
234 	else if (errno == ENOENT) {
235 		if ((file = dgen_fopen_rc(DGEN_APPEND)) != NULL) {
236 			fprintf(file,
237 				"# DGen " VER " configuration file.\n"
238 				"# See dgenrc(5) for more information.\n");
239 			fclose(file);
240 			file = NULL;
241 		}
242 	}
243 	else
244 		fprintf(stderr, "rc: %s: %s\n", DGEN_RC, strerror(errno));
245 
246   // Check all our options
247   snprintf(temp, sizeof(temp), "%s%s",
248 #ifdef __MINGW32__
249 	   "m"
250 #endif
251 	   "s:hvr:n:p:R:NPH:d:D:",
252 	   pd_options);
253   while((c = getopt(argc, argv, temp)) != EOF)
254     {
255       switch(c)
256 	{
257 	case 'v':
258 	  // Show version and exit
259 	  printf("DGen/SDL version " VER "\n");
260 	  return 0;
261 	case 'r':
262 	  // Parse another RC file or stdin
263 	  if ((strcmp(optarg, "-") == 0) ||
264 	      ((file = dgen_fopen(NULL, optarg,
265 				  (DGEN_READ | DGEN_CURRENT))) != NULL)) {
266 	    if (file == NULL)
267 	      parse_rc(stdin, "(stdin)");
268 	    else {
269 	      parse_rc(file, optarg);
270 	      fclose(file);
271 	      file = NULL;
272 	    }
273 	    pd_rc();
274 	  }
275 	  else
276 	    fprintf(stderr, "rc: %s: %s\n", optarg, strerror(errno));
277 	  break;
278 	case 'n':
279 	  // Sleep for n microseconds
280 	  dgen_nice = atoi(optarg);
281 	  break;
282 	case 'p':
283 	  // Game Genie patches
284 	  patches = optarg;
285 	  break;
286 	case 'R':
287 		// Region selection
288 		if (strlen(optarg) != 1)
289 			goto bad_region;
290 		switch (optarg[0] | 0x20) {
291 		case 'j':
292 		case 'x':
293 		case 'u':
294 		case 'e':
295 		case ' ':
296 			break;
297 		default:
298 		bad_region:
299 			fprintf(stderr, "main: invalid region `%s'.\n",
300 				optarg);
301 			return EXIT_FAILURE;
302 		}
303 		dgen_region = (optarg[0] & ~(0x20));
304 		// Override PAL and Hz settings if region is specified.
305 		if (dgen_region) {
306 			int hz;
307 			int pal;
308 
309 			md::region_info(dgen_region, &pal, &hz, 0, 0, 0);
310 			dgen_hz = hz;
311 			dgen_pal = pal;
312 		}
313 		forced_pal = false;
314 		forced_hz = false;
315 		break;
316 	case 'N':
317 		// NTSC mode
318 		dgen_hz = NTSC_HZ;
319 		dgen_pal = 0;
320 		forced_pal = true;
321 		break;
322 	case 'P':
323 		// PAL mode
324 		dgen_hz = PAL_HZ;
325 		dgen_pal = 1;
326 		forced_pal = true;
327 		break;
328 	case 'H':
329 		// Custom frame rate
330 		dgen_hz = atoi(optarg);
331 		if ((dgen_hz <= 0) || (dgen_hz > 1000)) {
332 			fprintf(stderr, "main: invalid frame rate (%ld).\n",
333 				(long)dgen_hz);
334 			dgen_hz = (dgen_pal ? 50 : 60);
335 			forced_hz = false;
336 		}
337 		else
338 			forced_hz = true;
339 		break;
340 #ifdef __MINGW32__
341 	case 'm':
342 		dgen_mingw_detach = 0;
343 		break;
344 #endif
345 	case 'd':
346 	  // Record demo
347 	  if(file)
348 	    {
349 	      fprintf(stderr,"main: Can't record and play at the same time!\n");
350 	      break;
351 	    }
352 	  if(!(file = dgen_fopen("demos", optarg, DGEN_WRITE)))
353 	    {
354 	      fprintf(stderr, "main: Can't record demo file %s!\n", optarg);
355 	      break;
356 	    }
357 	  demo_status = DEMO_RECORD;
358 	  break;
359 	case 'D':
360 	  // Play demo
361 	  if(file)
362 	    {
363 	      fprintf(stderr,"main: Can't record and play at the same time!\n");
364 	      break;
365 	    }
366 	  if(!(file = dgen_fopen("demos", optarg, (DGEN_READ | DGEN_CURRENT))))
367 	    {
368 	      fprintf(stderr, "main: Can't play demo file %s!\n", optarg);
369 	      break;
370 	    }
371 	  demo_status = DEMO_PLAY;
372 	  break;
373 	case '?': // Bad option!
374 	case 'h': // A cry for help :)
375 	  help();
376         case 's':
377           // Pick a savestate to autoload
378           start_slot = atoi(optarg);
379           break;
380 	default:
381 	  // Pass it on to platform-dependent stuff
382 	  pd_option(c, optarg);
383 	  break;
384 	}
385     }
386 
387 #ifdef __BEOS__
388   // BeOS snooze() sleeps in milliseconds, not microseconds
389   dgen_nice /= 1000;
390 #endif
391 
392 #ifdef __MINGW32__
393 	if (dgen_mingw_detach) {
394 		FILE *cons;
395 
396 		fprintf(stderr,
397 			"main: Detaching from console, use -m to prevent"
398 			" this.\n");
399 		// Console isn't needed anymore. Redirect output to log file.
400 		cons = dgen_fopen(NULL, "log.txt", (DGEN_WRITE | DGEN_TEXT));
401 		if (cons != NULL) {
402 			fflush(stdout);
403 			fflush(stderr);
404 			dup2(fileno(cons), fileno(stdout));
405 			dup2(fileno(cons), fileno(stderr));
406 			fclose(cons);
407 			setvbuf(stdout, NULL, _IONBF, 0);
408 			setvbuf(stderr, NULL, _IONBF, 0);
409 			cons = NULL;
410 		}
411 		FreeConsole();
412 	}
413 #endif
414 
415   // Initialize the platform-dependent stuff.
416   if (!pd_graphics_init(dgen_sound, dgen_pal, dgen_hz))
417     {
418       fprintf(stderr, "main: Couldn't initialize graphics!\n");
419       return 1;
420     }
421   if(dgen_sound)
422     {
423       long rate = dgen_soundrate;
424 
425       if (dgen_soundsegs < 0)
426 	      dgen_soundsegs = 0;
427       samples = (dgen_soundsegs * (rate / dgen_hz));
428       pd_sound_init(rate, samples);
429     }
430 
431 	rom = argv[optind];
432 	// Create the megadrive object.
433 	megad = new md(dgen_pal, dgen_region);
434 	if ((megad == NULL) || (!megad->okay())) {
435 		fprintf(stderr, "main: Mega Drive initialization failed.\n");
436 		goto clean_up;
437 	}
438 next_rom:
439 	// Load the requested ROM.
440 	if (rom != NULL) {
441 		if (megad->load(rom)) {
442 			pd_message("Unable to load \"%s\".", rom);
443 			if ((first) && ((optind + 1) == argc))
444 				goto clean_up;
445 		}
446 		else
447 			pd_message("Loaded \"%s\".", rom);
448 	}
449 	else
450 		pd_message("No cartridge.");
451 	first = false;
452 	// Set untouched pads.
453 	megad->pad[0] = MD_PAD_UNTOUCHED;
454 	megad->pad[1] = MD_PAD_UNTOUCHED;
455 #ifdef WITH_JOYSTICK
456 	if (dgen_joystick)
457 		megad->init_joysticks();
458 #endif
459 	// Load patches, if given.
460 	if (patches) {
461 		printf("main: Using patch codes \"%s\".\n", patches);
462 		megad->patch(patches, NULL, NULL, NULL);
463 		// Use them only once.
464 		patches = NULL;
465 	}
466 	// Reset
467 	megad->reset();
468 
469 	// Automatic region settings from ROM header.
470 	if (!dgen_region) {
471 		uint8_t c = megad->region_guess();
472 		int hz;
473 		int pal;
474 
475 		md::region_info(c, &pal, &hz, 0, 0, 0);
476 		if (forced_hz)
477 			hz = dgen_hz;
478 		if (forced_pal)
479 			pal = dgen_pal;
480 		if ((hz != dgen_hz) || (pal != dgen_pal) ||
481 		    (c != megad->region)) {
482 			megad->region = c;
483 			dgen_hz = hz;
484 			dgen_pal = pal;
485 			printf("main: reconfiguring for region \"%c\": "
486 			       "%dHz (%s)\n", c, hz, (pal ? "PAL" : "NTSC"));
487 			pd_graphics_reinit(dgen_sound, dgen_pal, dgen_hz);
488 			if (dgen_sound) {
489 				long rate = dgen_soundrate;
490 
491 				pd_sound_deinit();
492 				samples = (dgen_soundsegs * (rate / dgen_hz));
493 				pd_sound_init(rate, samples);
494 			}
495 			megad->pal = pal;
496 			megad->init_pal();
497 			megad->init_sound();
498 		}
499 	}
500 
501 	// Load up save RAM
502 	ram_load(*megad);
503 	// If -s option was given, load the requested slot
504 	if (start_slot >= 0) {
505 		slot = start_slot;
506 		md_load(*megad);
507 	}
508 	// If autoload is on, load save state 0
509 	else if (dgen_autoload) {
510 		slot = 0;
511 		md_load(*megad);
512 	}
513 
514 	// Start the timing refs
515 	startclk = pd_usecs();
516 	oldclk = startclk;
517 	fpsclk = startclk;
518 
519 	// Show cartridge header
520 	if (dgen_show_carthead)
521 		pd_show_carthead(*megad);
522 
523 	// Go around, and around, and around, and around... ;)
524 	frames = 0;
525 	frames_old = 0;
526 	fps = 0;
527 	while (!stop) {
528 		const unsigned int usec_frame = (1000000 / dgen_hz);
529 		unsigned long tmp;
530 		int frames_todo;
531 
532 		newclk = pd_usecs();
533 
534 		if (pd_stopped()) {
535 			// Fix FPS count.
536 			tmp = (newclk - oldclk);
537 			startclk += tmp;
538 			fpsclk += tmp;
539 			oldclk = newclk;
540 		}
541 
542 		// Update FPS count.
543 		tmp = ((newclk - fpsclk) & 0x3fffff);
544 		if (tmp >= 1000000) {
545 			fpsclk = newclk;
546 			if (frames_old > frames)
547 				fps = (frames_old - frames);
548 			else
549 				fps = (frames - frames_old);
550 			frames_old = frames;
551 		}
552 
553 		if (dgen_frameskip == 0) {
554 			// Check whether megad->one_frame() must be called.
555 			if (pd_freeze)
556 				goto frozen;
557 			goto do_not_skip;
558 		}
559 
560 		// Measure how many frames to do this round.
561 		usec += ((newclk - oldclk) & 0x3fffff); // no more than 4 secs
562 		frames_todo = (usec / usec_frame);
563 		usec %= usec_frame;
564 		oldclk = newclk;
565 
566 		if (frames_todo == 0) {
567 			// No frame to do yet, relax the CPU until next one.
568 			tmp = (usec_frame - usec);
569 			if (tmp > 1000) {
570 				// Never sleep for longer than the 50Hz value
571 				// so events are checked often enough.
572 				if (tmp > (1000000 / 50))
573 					tmp = (1000000 / 50);
574 				tmp -= 1000;
575 #ifdef __BEOS__
576 				snooze(tmp / 1000);
577 #else
578 				usleep(tmp);
579 #endif
580 			}
581 		}
582 		else {
583 			// Check whether megad->one_frame() must be called.
584 			if (pd_freeze)
585 				goto frozen;
586 
587 			// Draw frames.
588 			while (frames_todo != 1) {
589 				do_demo(*megad, file, &demo_status);
590 				if (dgen_sound) {
591 					// Skip this frame, keep sound going.
592 					megad->one_frame(NULL, NULL, &sndi);
593 					pd_sound_write();
594 				}
595 				else
596 					megad->one_frame(NULL, NULL, NULL);
597 				--frames_todo;
598 				stop |= (pd_handle_events(*megad) ^ 1);
599 			}
600 			--frames_todo;
601 		do_not_skip:
602 			do_demo(*megad, file, &demo_status);
603 			if (dgen_sound) {
604 				megad->one_frame(&mdscr, mdpal, &sndi);
605 				pd_sound_write();
606 			}
607 			else
608 				megad->one_frame(&mdscr, mdpal, NULL);
609 		frozen:
610 			if ((mdpal) && (pal_dirty)) {
611 				pd_graphics_palette_update();
612 				pal_dirty = 0;
613 			}
614 			pd_graphics_update(megad->plugged);
615 			++frames;
616 		}
617 
618 		stop |= (pd_handle_events(*megad) ^ 1);
619 
620 		if (dgen_nice) {
621 #ifdef __BEOS__
622 			snooze(dgen_nice);
623 #else
624 			usleep(dgen_nice);
625 #endif
626 		}
627 	}
628 
629 #ifdef WITH_JOYSTICK
630 	if (dgen_joystick)
631 		megad->deinit_joysticks();
632 #endif
633 
634 	// Print fps
635 	fpsclk = ((pd_usecs() - startclk) / 1000000);
636 	if (fpsclk == 0)
637 		fpsclk = 1;
638 #ifdef WITH_DEBUGGER
639 	megad->debug_leave();
640 #endif
641 	printf("%lu frames per second (average %lu, optimal %ld)\n",
642 	       fps, (frames / fpsclk), (long)dgen_hz);
643 
644 	ram_save(*megad);
645 	if (dgen_autosave) {
646 		slot = 0;
647 		md_save(*megad);
648 	}
649 	megad->unplug();
650 	if (file) {
651 		fclose(file);
652 		file = NULL;
653 	}
654 	if ((++optind) < argc) {
655 		rom = argv[optind];
656 		stop = 0;
657 		goto next_rom;
658 	}
659 clean_up:
660 	// Cleanup
661 	delete megad;
662 	pd_sound_deinit();
663 	pd_quit();
664 	// Save configuration.
665 	if (dgen_autoconf) {
666 		if ((file = dgen_fopen_autorc(DGEN_WRITE)) == NULL)
667 			fputs("main: can't write " DGEN_AUTORC ".\n", stderr);
668 		else {
669 			fprintf(file,
670 				"# DGen/SDL v" VER "\n"
671 				"# This file is automatically overwritten.\n"
672 				"\n");
673 			dump_rc(file);
674 			fclose(file);
675 			file = NULL;
676 		}
677 	}
678 	// Come back anytime :)
679 	return 0;
680 }
681