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 ;)
10 #include <sys/types.h>
35 static long dgen_mingw_detach
= 1;
38 // Defined in ras.cpp, and set to true if the Genesis palette's changed.
41 FILE *debug_log
= NULL
;
43 // Do a demo frame, if active
50 static inline void do_demo(md
& megad
, FILE* demo
, enum demo_status
* status
)
58 pad
[0] = h2be32(megad
.pad
[0]);
59 pad
[1] = h2be32(megad
.pad
[1]);
60 fwrite(&pad
, sizeof(pad
), 1, demo
);
63 if (fread(&pad
, sizeof(pad
), 1, demo
) == 1) {
64 megad
.pad
[0] = be2h32(pad
[0]);
65 megad
.pad
[1] = be2h32(pad
[1]);
69 pd_message("Demo finished.");
71 pd_message("Demo finished (read error).");
78 // Temporary garbage can string :)
79 static char temp
[65536] = "";
81 // Show help and exit with code 2
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"
106 " -m Do not detach from console.\n"
109 // Display platform-specific options
115 // It is externed from your implementation to change the current slot
116 // (I know this is a hack :)
118 void md_save(md
& megad
)
123 if (!megad
.plugged
) {
124 pd_message("Cannot save state when no ROM is loaded.");
127 if (((size_t)snprintf(file
,
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
);
138 megad
.export_gst(save
);
140 snprintf(temp
, sizeof(temp
), "Saved state to slot %d.", slot
);
144 void md_load(md
& megad
)
149 if (!megad
.plugged
) {
150 pd_message("Cannot restore state when no ROM is loaded.");
153 if (((size_t)snprintf(file
,
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
);
164 megad
.import_gst(load
);
166 snprintf(temp
, sizeof(temp
), "Loaded state from slot %d.", slot
);
170 // Load/save states from file
171 void ram_save(md
& megad
)
176 if (!megad
.has_save_ram())
178 save
= dgen_fopen("ram", megad
.romname
, DGEN_WRITE
);
181 ret
= megad
.put_save_ram(save
);
186 fprintf(stderr
, "Couldn't save battery RAM to `%s'\n", megad
.romname
);
189 void ram_load(md
& megad
)
194 if (!megad
.has_save_ram())
196 load
= dgen_fopen("ram", megad
.romname
, DGEN_READ
);
199 ret
= megad
.get_save_ram(load
);
204 fprintf(stderr
, "Couldn't load battery RAM from `%s'\n",
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
;
215 enum demo_status demo_status
= DEMO_OFF
;
216 unsigned int samples
;
219 bool forced_hz
= false;
220 bool forced_pal
= false;
223 if ((dgen_autoconf
) &&
224 ((file
= dgen_fopen_autorc(DGEN_READ
)) != NULL
)) {
225 parse_rc(file
, DGEN_AUTORC
);
228 if ((file
= dgen_fopen_rc(DGEN_READ
)) != NULL
) {
229 parse_rc(file
, DGEN_RC
);
234 else if (errno
== ENOENT
) {
235 if ((file
= dgen_fopen_rc(DGEN_APPEND
)) != NULL
) {
237 "# DGen " VER
" configuration file.\n"
238 "# See dgenrc(5) for more information.\n");
244 fprintf(stderr
, "rc: %s: %s\n", DGEN_RC
, strerror(errno
));
246 // Check all our options
247 snprintf(temp
, sizeof(temp
), "%s%s",
251 "s:hvr:n:p:R:NPH:d:D:",
253 while((c
= getopt(argc
, argv
, temp
)) != EOF
)
258 // Show version and exit
259 printf("DGen/SDL version "VER
"\n");
262 // Parse another RC file or stdin
263 if ((strcmp(optarg
, "-") == 0) ||
264 ((file
= dgen_fopen(NULL
, optarg
,
265 (DGEN_READ
| DGEN_CURRENT
))) != NULL
)) {
267 parse_rc(stdin
, "(stdin)");
269 parse_rc(file
, optarg
);
276 fprintf(stderr
, "rc: %s: %s\n", optarg
, strerror(errno
));
279 // Sleep for n microseconds
280 dgen_nice
= atoi(optarg
);
283 // Game Genie patches
288 if (strlen(optarg
) != 1)
290 switch (optarg
[0] | 0x20) {
299 fprintf(stderr
, "main: invalid region `%s'.\n",
303 dgen_region
= (optarg
[0] & ~(0x20));
304 // Override PAL and Hz settings if region is specified.
309 md::region_info(dgen_region
, &pal
, &hz
, 0, 0, 0);
330 dgen_hz
= atoi(optarg
);
331 if ((dgen_hz
<= 0) || (dgen_hz
> 1000)) {
332 fprintf(stderr
, "main: invalid frame rate (%ld).\n",
334 dgen_hz
= (dgen_pal
? 50 : 60);
342 dgen_mingw_detach
= 0;
349 fprintf(stderr
,"main: Can't record and play at the same time!\n");
352 if(!(file
= dgen_fopen("demos", optarg
, DGEN_WRITE
)))
354 fprintf(stderr
, "main: Can't record demo file %s!\n", optarg
);
357 demo_status
= DEMO_RECORD
;
363 fprintf(stderr
,"main: Can't record and play at the same time!\n");
366 if(!(file
= dgen_fopen("demos", optarg
, (DGEN_READ
| DGEN_CURRENT
))))
368 fprintf(stderr
, "main: Can't play demo file %s!\n", optarg
);
371 demo_status
= DEMO_PLAY
;
373 case '?': // Bad option!
374 case 'h': // A cry for help :)
377 // Pick a savestate to autoload
378 start_slot
= atoi(optarg
);
381 // Pass it on to platform-dependent stuff
382 pd_option(c
, optarg
);
388 // BeOS snooze() sleeps in milliseconds, not microseconds
393 if (dgen_mingw_detach
) {
397 "main: Detaching from console, use -m to prevent"
399 // Console isn't needed anymore. Redirect output to log file.
400 cons
= dgen_fopen(NULL
, "log.txt", (DGEN_WRITE
| DGEN_TEXT
));
404 dup2(fileno(cons
), fileno(stdout
));
405 dup2(fileno(cons
), fileno(stderr
));
407 setvbuf(stdout
, NULL
, _IONBF
, 0);
408 setvbuf(stderr
, NULL
, _IONBF
, 0);
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");
423 long rate
= dgen_soundrate
;
425 if (dgen_soundsegs
< 0)
427 samples
= (dgen_soundsegs
* (rate
/ dgen_hz
));
428 pd_sound_init(rate
, samples
);
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");
439 // Load the requested ROM.
441 if (megad
->load(rom
)) {
442 pd_message("Unable to load \"%s\".", rom
);
443 if ((first
) && ((optind
+ 1) == argc
))
447 pd_message("Loaded \"%s\".", rom
);
450 pd_message("No cartridge.");
452 // Set untouched pads.
453 megad
->pad
[0] = MD_PAD_UNTOUCHED
;
454 megad
->pad
[1] = MD_PAD_UNTOUCHED
;
457 megad
->init_joysticks();
459 // Load patches, if given.
461 printf("main: Using patch codes \"%s\".\n", patches
);
462 megad
->patch(patches
, NULL
, NULL
, NULL
);
463 // Use them only once.
469 // Automatic region settings from ROM header.
471 uint8_t c
= megad
->region_guess();
475 md::region_info(c
, &pal
, &hz
, 0, 0, 0);
480 if ((hz
!= dgen_hz
) || (pal
!= dgen_pal
) ||
481 (c
!= megad
->region
)) {
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
);
489 long rate
= dgen_soundrate
;
492 samples
= (dgen_soundsegs
* (rate
/ dgen_hz
));
493 pd_sound_init(rate
, samples
);
503 // If -s option was given, load the requested slot
504 if (start_slot
>= 0) {
508 // If autoload is on, load save state 0
509 else if (dgen_autoload
) {
514 // Start the timing refs
515 startclk
= pd_usecs();
519 // Show cartridge header
520 if (dgen_show_carthead
)
521 pd_show_carthead(*megad
);
523 // Go around, and around, and around, and around... ;)
528 const unsigned int usec_frame
= (1000000 / dgen_hz
);
536 tmp
= (newclk
- oldclk
);
543 tmp
= ((newclk
- fpsclk
) & 0x3fffff);
544 if (tmp
>= 1000000) {
546 if (frames_old
> frames
)
547 fps
= (frames_old
- frames
);
549 fps
= (frames
- frames_old
);
553 if (dgen_frameskip
== 0) {
554 // Check whether megad->one_frame() must be called.
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
);
566 if (frames_todo
== 0) {
567 // No frame to do yet, relax the CPU until next one.
568 tmp
= (usec_frame
- usec
);
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);
583 // Check whether megad->one_frame() must be called.
588 while (frames_todo
!= 1) {
589 do_demo(*megad
, file
, &demo_status
);
591 // Skip this frame, keep sound going.
592 megad
->one_frame(NULL
, NULL
, &sndi
);
596 megad
->one_frame(NULL
, NULL
, NULL
);
598 stop
|= (pd_handle_events(*megad
) ^ 1);
602 do_demo(*megad
, file
, &demo_status
);
604 megad
->one_frame(&mdscr
, mdpal
, &sndi
);
608 megad
->one_frame(&mdscr
, mdpal
, NULL
);
610 if ((mdpal
) && (pal_dirty
)) {
611 pd_graphics_palette_update();
614 pd_graphics_update(megad
->plugged
);
618 stop
|= (pd_handle_events(*megad
) ^ 1);
631 megad
->deinit_joysticks();
635 fpsclk
= ((pd_usecs() - startclk
) / 1000000);
639 megad
->debug_leave();
641 printf("%lu frames per second (average %lu, optimal %ld)\n",
642 fps
, (frames
/ fpsclk
), (long)dgen_hz
);
654 if ((++optind
) < argc
) {
664 // Save configuration.
666 if ((file
= dgen_fopen_autorc(DGEN_WRITE
)) == NULL
)
667 fputs("main: can't write " DGEN_AUTORC
".\n", stderr
);
670 "# DGen/SDL v" VER
"\n"
671 "# This file is automatically overwritten.\n"
678 // Come back anytime :)