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>
12 #include <sys/resource.h>
14 #include <netinet/in.h>
29 // Ideal usec/frame for 60Hz
30 #define USEC_FRAME_NTSC 16667 // 1000000/60
32 // Ideal usec/frame for 50Hz
33 #define USEC_FRAME_PAL 20000 // 1000000/50
35 // Neat little macro to pick which one of the above :)
36 #define USEC_FRAME (pal_mode? USEC_FRAME_PAL : USEC_FRAME_NTSC)
38 // Defined in ras.cpp, and set to true if the Genesis palette's changed.
41 int sound_is_okay
= 0;
42 FILE *debug_log
= NULL
;
44 // Do a demo frame, if active
47 foo = htonl(megad.pad[0]); \
48 fwrite(&foo, sizeof(foo), 1, demo); \
49 foo = htonl(megad.pad[1]); \
50 fwrite(&foo, sizeof(foo), 1, demo); \
52 fread(&foo, sizeof(foo), 1, demo); \
53 megad.pad[0] = ntohl(foo); \
54 fread(&foo, sizeof(foo), 1, demo); \
55 megad.pad[1] = ntohl(foo); \
58 pd_message("Demo finished."); \
63 // Convenience, so I don't have to type this constantly
64 #define DO_FRAME(scr, pal) \
65 running = pd_handle_events(megad); \
68 megad.one_frame((scr), (pal), &sndi); \
69 } else megad.one_frame((scr), (pal), NULL);
71 // Directory to put savestates in
72 static char saves
[2048] = "";
74 // Directory to put battery RAM in
75 static char ramdir
[2048] = "";
77 // Temporary garbage can string :)
78 static char temp
[65536] = "";
80 // Get the basename from the ROM filename
81 // An equivalent perl one-liner would be perl -pe 's@.*/([^.]*?)\..*@\1@' :)
82 /* Modified: 20-11-1999 Dylan_G@bigfoot.com
83 Made very much less evil. */
84 static char *gst_name(char *fn
)
86 char buf
[1024]; /* strtok modifies its arguments */
87 char *p
= NULL
, *p1
= NULL
;
89 memset(buf
, 0, sizeof(buf
));
90 strncpy(buf
, fn
, sizeof(buf
));
92 { /* Need to strip extension */
97 { /* Need to strip /path/name */
99 /* We have to walk through until we hit NULL, then use N-1 pointer :-). */
103 p
= strtok(NULL
, "/");
106 /* Fix in case there is no / in the filename */
111 // Show help and exit with code 2
116 "Usage: dgen [options] romname\n\n"
117 "Where options are:\n"
118 " -v Print version number and exit.\n"
119 " -r RCFILE Read in the file RCFILE after parsing\n"
120 " $HOME/.dgen/dgenrc.\n"
121 " -n USEC Cuses dgen to sleep USEC microseconds per frame, to be\n"
122 " nice to other processes.\n"
123 " -p CODE,CODE... Takes a comma-delimited list of Game Genie (ABCD-EFGH)\n"
124 " or Hex (123456:ABCD) codes to patch the ROM with.\n"
125 " -R Set realtime priority -20, so no other processes may\n"
126 " interrupt. dgen definitely needs root priviledges for\n"
128 " -P Use PAL mode (50Hz) instead of normal NTSC (60Hz).\n"
129 " -d DEMONAME Record a demo of the game you are playing.\n"
130 " -D DEMONAME Play back a previously recorded demo.\n"
131 " -s SLOT Load the saved state from the given slot at startup.\n"
132 #ifdef JOYSTICK_SUPPORT
133 " -j Use joystick if detected.\n"
136 // Display platform-specific options
141 // Create the .dgen directory structure in the user's home directory
142 static void mk_dgendir()
144 strcpy(temp
, getenv("HOME"));
145 strcat(temp
, "/.dgen");
149 strcat(saves
, "/saves");
152 strcpy(ramdir
, temp
);
153 strcat(ramdir
, "/ram");
158 // It is externed from your implementation to change the current slot
159 // (I know this is a hack :)
161 void md_save(md
& megad
)
164 sprintf(temp
, "%s/%s.gs%d", saves
, gst_name(megad
.romfilename
), slot
);
165 if((save
= fopen(temp
, "wb")))
167 megad
.export_gst(save
);
169 sprintf(temp
, "Saved state to slot %d.", slot
);
174 sprintf(temp
, "Couldn't save state to slot %d!", slot
);
179 void md_load(md
& megad
)
182 sprintf(temp
, "%s/%s.gs%d", saves
, gst_name(megad
.romfilename
), slot
);
183 if((load
= fopen(temp
, "rb")))
185 megad
.import_gst(load
);
187 sprintf(temp
, "Loaded state from slot %d.", slot
);
192 sprintf(temp
, "Couldn't load state from slot %d!", slot
);
197 // Load/save states from file
198 static void ram_save(md
& megad
)
201 if(!megad
.has_save_ram()) return;
202 sprintf(temp
, "%s/%s", ramdir
, gst_name(megad
.romfilename
));
203 if((save
= fopen(temp
, "wb")))
205 megad
.put_save_ram(save
);
210 fprintf(stderr
, "Couldn't save battery RAM to %s!\n", temp
);
214 static void ram_load(md
& megad
)
217 if(!megad
.has_save_ram()) return;
218 sprintf(temp
, "%s/%s", ramdir
, gst_name(megad
.romfilename
));
219 if((load
= fopen(temp
, "rb")))
221 megad
.get_save_ram(load
);
226 int main(int argc
, char *argv
[])
228 int c
= 0, pal_mode
= 0, running
= 1, usec
= 0,
229 wp
= 0, rp
= 0, start_slot
= -1;
230 unsigned long long f
= 0;
231 char *patches
= NULL
, *rom
= NULL
;
232 struct timeval oldclk
, newclk
, startclk
, endclk
;
234 int demo_record
= 0, demo_play
= 0, foo
;
241 // Check all our options
242 strcpy(temp
, "s:hvr:n:p:RPjd:D:");
243 strcat(temp
, pd_options
);
244 while((c
= getopt(argc
, argv
, temp
)) != EOF
)
249 // Show version and exit
250 printf("DGen/SDL version "VER
"\n");
253 // Parse another RC file
257 // Sleep for n microseconds
258 dgen_nice
= atoi(optarg
);
261 // Game Genie patches
266 // Try to set realtime priority
268 fprintf(stderr
, "main: Only root can set lower priorities!\n");
271 if(setpriority(PRIO_PROCESS
, 0, -20) == -1)
272 perror("main: setpriority");
279 #ifdef JOYSTICK_SUPPORT
281 // Phil's joystick code
289 fprintf(stderr
,"main: Can't record and play at the same time!\n");
292 if(!(demo
= fopen(optarg
, "wb")))
294 fprintf(stderr
, "main: Can't record demo file %s!\n", optarg
);
303 fprintf(stderr
,"main: Can't record and play at the same time!\n");
306 if(!(demo
= fopen(optarg
, "rb")))
308 fprintf(stderr
, "main: Can't play demo file %s!\n", optarg
);
313 case '?': // Bad option!
314 case 'h': // A cry for help :)
317 // Pick a savestate to autoload
318 start_slot
= atoi(optarg
);
321 // Pass it on to platform-dependent stuff
322 pd_option(c
, optarg
);
328 // BeOS snooze() sleeps in milliseconds, not microseconds
332 // There should be a romname after all those options. If not, show help and
337 // Initialize the platform-dependent stuff.
338 if(!pd_graphics_init(dgen_sound
, pal_mode
))
340 fprintf(stderr
, "main: Couldn't initialize graphics!\n");
345 dgen_16bit
= dgen_16bit
? PD_SND_16
: PD_SND_8
;
346 dgen_sound
= pd_sound_init(dgen_16bit
, dgen_soundrate
, dgen_soundsegs
);
348 // If sound fared OK, start up the sound chips
351 if(YM2612Init(1, 7520000L, dgen_soundrate
, NULL
, NULL
) ||
352 SN76496_init(0, 3478000L, dgen_soundrate
, 16))
353 fprintf(stderr
, "main: Couldn't start sound chipset emulators!\n");
357 // Decrement the sound seg count. This makes it a nice AND mask :)
362 // Create the megadrive object
366 fprintf(stderr
, "main: Megadrive init failed!\n");
369 // Load the requested ROM
372 fprintf(stderr
, "main: Couldn't load ROM file %s!\n", rom
);
375 // Set untouched pads
376 megad
.pad
[0] = megad
.pad
[1] = 0xF303F;
377 #ifdef JOYSTICK_SUPPORT
379 megad
.init_joysticks();
381 // Load patches, if given
384 printf("main: Using patch codes %s\n", patches
);
385 megad
.patch(patches
);
388 megad
.fix_rom_checksum();
392 megad
.pal
= pal_mode
;
394 // Make sure the .dgen hierarchy is setup
398 // If autoload is on, load save state 0
404 // If -s option was given, load the requested slot
411 // Start the timing refs
412 gettimeofday(&oldclk
, NULL
);
413 gettimeofday(&startclk
, NULL
);
415 if(dgen_sound
) pd_sound_start();
417 // Show cartridge header
418 if(dgen_show_carthead
) pd_show_carthead(megad
);
420 // Go around, and around, and around, and around... ;)
426 // Measure how many frames to do this round
427 if(!dgen_sound
&& dgen_frameskip
)
429 gettimeofday(&newclk
, NULL
);
430 if(newclk
.tv_usec
< oldclk
.tv_usec
)
431 usec
+= 1000000 + newclk
.tv_usec
- oldclk
.tv_usec
;
433 usec
+= newclk
.tv_usec
- oldclk
.tv_usec
;
434 frames_todo
= usec
/ USEC_FRAME
;
437 // We don't want to skip too many frames - this isn't Unreal ;)
438 if(frames_todo
> 8) frames_todo
= 8;
440 for(;frames_todo
> 1; --frames_todo
)
443 megad
.one_frame(NULL
, NULL
, NULL
);
445 } else if(dgen_sound
) {
446 // We can use the sound buffer for timing, instead of the above loop
447 // If we are already caught up, wait for the read pointer to advance
448 while((rp
= pd_sound_rp()) == wp
);
452 ++wp
; wp
&= dgen_soundsegs
;
453 // Skip a frame to keep the sound going, until we hit the read
458 megad
.one_frame(NULL
, NULL
, &sndi
);
462 // If there are frames to do, do them! :)
465 DO_FRAME(&mdscr
, mdpal
);
467 if(mdpal
&& pal_dirty
)
469 pd_graphics_palette_update();
474 pd_graphics_update();
479 if(dgen_nice
) snooze(dgen_nice
);
481 if(dgen_nice
) usleep(dgen_nice
);
485 gettimeofday(&endclk
, NULL
);
486 printf("%d frames per second (optimal %d)\n",
487 (unsigned)(f
/ (endclk
.tv_sec
- startclk
.tv_sec
)), (pal_mode
? 50 : 60));
490 if(demo
) fclose(demo
);
492 if(dgen_autosave
) { slot
= 0; md_save(megad
); }
497 // Come back anytime :)