Add VCS links
[debian-dgen.git] / main.cpp
blob69a71236f2f3c13317f99ba52b1ee38461f26697
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 ;)
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>
17 #ifdef __MINGW32__
18 #include <windows.h>
19 #include <wincon.h>
20 #endif
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"
30 #ifdef __BEOS__
31 #include <OS.h>
32 #endif
34 #ifdef __MINGW32__
35 static long dgen_mingw_detach = 1;
36 #endif
38 // Defined in ras.cpp, and set to true if the Genesis palette's changed.
39 extern int pal_dirty;
41 FILE *debug_log = NULL;
43 // Do a demo frame, if active
44 enum demo_status {
45 DEMO_OFF,
46 DEMO_RECORD,
47 DEMO_PLAY
50 static inline void do_demo(md& megad, FILE* demo, enum demo_status* status)
52 uint32_t pad[2];
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]);
67 else {
68 if (feof(demo))
69 pd_message("Demo finished.");
70 else
71 pd_message("Demo finished (read error).");
72 *status = DEMO_OFF;
74 break;
78 // Temporary garbage can string :)
79 static char temp[65536] = "";
81 // Show help and exit with code 2
82 static void help()
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
109 // Display platform-specific options
110 pd_help();
111 exit(2);
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;
118 void md_save(md& megad)
120 FILE *save;
121 char file[64];
123 if (!megad.plugged) {
124 pd_message("Cannot save state when no ROM is loaded.");
125 return;
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;
138 megad.export_gst(save);
139 fclose(save);
140 snprintf(temp, sizeof(temp), "Saved state to slot %d.", slot);
141 pd_message(temp);
144 void md_load(md& megad)
146 FILE *load;
147 char file[64];
149 if (!megad.plugged) {
150 pd_message("Cannot restore state when no ROM is loaded.");
151 return;
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;
164 megad.import_gst(load);
165 fclose(load);
166 snprintf(temp, sizeof(temp), "Loaded state from slot %d.", slot);
167 pd_message(temp);
170 // Load/save states from file
171 void ram_save(md& megad)
173 FILE *save;
174 int ret;
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);
189 void ram_load(md& megad)
191 FILE *load;
192 int ret;
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);
208 int main(int argc, char *argv[])
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;
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);
228 if ((file = dgen_fopen_rc(DGEN_READ)) != NULL) {
229 parse_rc(file, DGEN_RC);
230 fclose(file);
231 file = NULL;
232 pd_rc();
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;
243 else
244 fprintf(stderr, "rc: %s: %s\n", DGEN_RC, strerror(errno));
246 // Check all our options
247 snprintf(temp, sizeof(temp), "%s%s",
248 #ifdef __MINGW32__
250 #endif
251 "s:hvr:n:p:R:NPH:d:D:",
252 pd_options);
253 while((c = getopt(argc, argv, temp)) != EOF)
255 switch(c)
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;
273 pd_rc();
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;
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;
309 md::region_info(dgen_region, &pal, &hz, 0, 0, 0);
310 dgen_hz = hz;
311 dgen_pal = pal;
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;
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)
349 fprintf(stderr,"main: Can't record and play at the same time!\n");
350 break;
352 if(!(file = dgen_fopen("demos", optarg, DGEN_WRITE)))
354 fprintf(stderr, "main: Can't record demo file %s!\n", optarg);
355 break;
357 demo_status = DEMO_RECORD;
358 break;
359 case 'D':
360 // Play demo
361 if(file)
363 fprintf(stderr,"main: Can't record and play at the same time!\n");
364 break;
366 if(!(file = dgen_fopen("demos", optarg, (DGEN_READ | DGEN_CURRENT))))
368 fprintf(stderr, "main: Can't play demo file %s!\n", optarg);
369 break;
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;
387 #ifdef __BEOS__
388 // BeOS snooze() sleeps in milliseconds, not microseconds
389 dgen_nice /= 1000;
390 #endif
392 #ifdef __MINGW32__
393 if (dgen_mingw_detach) {
394 FILE *cons;
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;
411 FreeConsole();
413 #endif
415 // Initialize the platform-dependent stuff.
416 if (!pd_graphics_init(dgen_sound, dgen_pal, dgen_hz))
418 fprintf(stderr, "main: Couldn't initialize graphics!\n");
419 return 1;
421 if(dgen_sound)
423 long rate = dgen_soundrate;
425 if (dgen_soundsegs < 0)
426 dgen_soundsegs = 0;
427 samples = (dgen_soundsegs * (rate / dgen_hz));
428 pd_sound_init(rate, samples);
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;
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;
446 else
447 pd_message("Loaded \"%s\".", rom);
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;
466 // Reset
467 megad->reset();
469 // Automatic region settings from ROM header.
470 if (!dgen_region) {
471 uint8_t c = megad->region_guess();
472 int hz;
473 int pal;
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;
491 pd_sound_deinit();
492 samples = (dgen_soundsegs * (rate / dgen_hz));
493 pd_sound_init(rate, samples);
495 megad->pal = pal;
496 megad->init_pal();
497 megad->init_sound();
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);
508 // If autoload is on, load save state 0
509 else if (dgen_autoload) {
510 slot = 0;
511 md_load(*megad);
514 // Start the timing refs
515 startclk = pd_usecs();
516 oldclk = startclk;
517 fpsclk = startclk;
519 // Show cartridge header
520 if (dgen_show_carthead)
521 pd_show_carthead(*megad);
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;
532 newclk = pd_usecs();
534 if (pd_stopped()) {
535 // Fix FPS count.
536 tmp = (newclk - oldclk);
537 startclk += tmp;
538 fpsclk += tmp;
539 oldclk = newclk;
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;
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;
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;
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
582 else {
583 // Check whether megad->one_frame() must be called.
584 if (pd_freeze)
585 goto frozen;
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();
595 else
596 megad->one_frame(NULL, NULL, NULL);
597 --frames_todo;
598 stop |= (pd_handle_events(*megad) ^ 1);
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();
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;
614 pd_graphics_update(megad->plugged);
615 ++frames;
618 stop |= (pd_handle_events(*megad) ^ 1);
620 if (dgen_nice) {
621 #ifdef __BEOS__
622 snooze(dgen_nice);
623 #else
624 usleep(dgen_nice);
625 #endif
629 #ifdef WITH_JOYSTICK
630 if (dgen_joystick)
631 megad->deinit_joysticks();
632 #endif
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);
644 ram_save(*megad);
645 if (dgen_autosave) {
646 slot = 0;
647 md_save(*megad);
649 megad->unplug();
650 if (file) {
651 fclose(file);
652 file = NULL;
654 if ((++optind) < argc) {
655 rom = argv[optind];
656 stop = 0;
657 goto next_rom;
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;
678 // Come back anytime :)
679 return 0;