Import Debian changes 1.23-8
[debian-dgen.git] / main.cpp
blob76539f597e071f866064831026e793a9ab3a67dc
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 <sys/time.h>
12 #include <sys/resource.h>
13 #include <sys/wait.h>
14 #include <netinet/in.h>
15 #include <fcntl.h>
16 #include <string.h>
18 #define IS_MAIN_CPP
19 #include "md.h"
20 #include "pd.h"
21 #include "pd-defs.h"
22 #include "rc.h"
23 #include "rc-vars.h"
25 #ifdef __BEOS__
26 #include <OS.h>
27 #endif
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.
39 extern int pal_dirty;
41 int sound_is_okay = 0;
42 FILE *debug_log = NULL;
44 // Do a demo frame, if active
45 #define DO_DEMO \
46 if(demo_record) { \
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); \
51 } if(demo_play) { \
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); \
56 if(feof(demo)) \
57 { \
58 pd_message("Demo finished."); \
59 demo_play = 0; \
60 } \
63 // Convenience, so I don't have to type this constantly
64 #define DO_FRAME(scr, pal) \
65 running = pd_handle_events(megad); \
66 DO_DEMO \
67 if (dgen_sound) { \
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));
91 if(strchr(buf, '.'))
92 { /* Need to strip extension */
93 p = strtok(buf, ".");
96 if(strchr(buf, '/'))
97 { /* Need to strip /path/name */
98 p = strtok(buf, "/");
99 /* We have to walk through until we hit NULL, then use N-1 pointer :-). */
100 while(p != NULL)
102 p1 = p;
103 p = strtok(NULL, "/");
106 /* Fix in case there is no / in the filename */
107 if(p) p1 = p;
108 return(p1);
111 // Show help and exit with code 2
112 static void help()
114 printf(
115 "DGen/SDL v"VER"\n"
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"
127 " this.\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"
134 #endif
136 // Display platform-specific options
137 pd_help();
138 exit(2);
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");
146 mkdir(temp, 0777);
147 // Make save dir
148 strcpy(saves, temp);
149 strcat(saves, "/saves");
150 mkdir(saves, 0777);
151 // Make ram dir
152 strcpy(ramdir, temp);
153 strcat(ramdir, "/ram");
154 mkdir(ramdir, 0777);
157 // Save/load states
158 // It is externed from your implementation to change the current slot
159 // (I know this is a hack :)
160 int slot = 0;
161 void md_save(md& megad)
163 FILE *save = NULL;
164 sprintf(temp, "%s/%s.gs%d", saves, gst_name(megad.romfilename), slot);
165 if((save = fopen(temp, "wb")))
167 megad.export_gst(save);
168 fclose(save);
169 sprintf(temp, "Saved state to slot %d.", slot);
170 pd_message(temp);
172 else
174 sprintf(temp, "Couldn't save state to slot %d!", slot);
175 pd_message(temp);
179 void md_load(md& megad)
181 FILE *load = NULL;
182 sprintf(temp, "%s/%s.gs%d", saves, gst_name(megad.romfilename), slot);
183 if((load = fopen(temp, "rb")))
185 megad.import_gst(load);
186 fclose(load);
187 sprintf(temp, "Loaded state from slot %d.", slot);
188 pd_message(temp);
190 else
192 sprintf(temp, "Couldn't load state from slot %d!", slot);
193 pd_message(temp);
197 // Load/save states from file
198 static void ram_save(md& megad)
200 FILE *save = NULL;
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);
206 fclose(save);
208 else
210 fprintf(stderr, "Couldn't save battery RAM to %s!\n", temp);
214 static void ram_load(md& megad)
216 FILE *load = NULL;
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);
222 fclose(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;
233 FILE *demo = NULL;
234 int demo_record = 0, demo_play = 0, foo;
236 // Parse the RC file
237 parse_rc(NULL);
239 pd_initoptions();
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)
246 switch(c)
248 case 'v':
249 // Show version and exit
250 printf("DGen/SDL version "VER"\n");
251 return 0;
252 case 'r':
253 // Parse another RC file
254 parse_rc(optarg);
255 break;
256 case 'n':
257 // Sleep for n microseconds
258 dgen_nice = atoi(optarg);
259 break;
260 case 'p':
261 // Game Genie patches
262 patches = optarg;
263 break;
264 #ifndef __BEOS__
265 case 'R':
266 // Try to set realtime priority
267 if(geteuid()) {
268 fprintf(stderr, "main: Only root can set lower priorities!\n");
269 break;
271 if(setpriority(PRIO_PROCESS, 0, -20) == -1)
272 perror("main: setpriority");
273 break;
274 #endif
275 case 'P':
276 // PAL mode
277 pal_mode = 1;
278 break;
279 #ifdef JOYSTICK_SUPPORT
280 case 'j':
281 // Phil's joystick code
282 dgen_joystick = 1;
283 break;
284 #endif
285 case 'd':
286 // Record demo
287 if(demo)
289 fprintf(stderr,"main: Can't record and play at the same time!\n");
290 break;
292 if(!(demo = fopen(optarg, "wb")))
294 fprintf(stderr, "main: Can't record demo file %s!\n", optarg);
295 break;
297 demo_record = 1;
298 break;
299 case 'D':
300 // Play demo
301 if(demo)
303 fprintf(stderr,"main: Can't record and play at the same time!\n");
304 break;
306 if(!(demo = fopen(optarg, "rb")))
308 fprintf(stderr, "main: Can't play demo file %s!\n", optarg);
309 break;
311 demo_play = 1;
312 break;
313 case '?': // Bad option!
314 case 'h': // A cry for help :)
315 help();
316 case 's':
317 // Pick a savestate to autoload
318 start_slot = atoi(optarg);
319 break;
320 default:
321 // Pass it on to platform-dependent stuff
322 pd_option(c, optarg);
323 break;
327 #ifdef __BEOS__
328 // BeOS snooze() sleeps in milliseconds, not microseconds
329 dgen_nice /= 1000;
330 #endif
332 // There should be a romname after all those options. If not, show help and
333 // exit.
334 if(optind >= argc)
335 help();
337 // Initialize the platform-dependent stuff.
338 if(!pd_graphics_init(dgen_sound, pal_mode))
340 fprintf(stderr, "main: Couldn't initialize graphics!\n");
341 return 1;
343 if(dgen_sound)
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
349 if(dgen_sound)
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");
354 else
355 sound_is_okay = 1;
357 // Decrement the sound seg count. This makes it a nice AND mask :)
358 --dgen_soundsegs;
360 rom = argv[optind];
362 // Create the megadrive object
363 md megad;
364 if(!megad.okay())
366 fprintf(stderr, "main: Megadrive init failed!\n");
367 return 1;
369 // Load the requested ROM
370 if(megad.load(rom))
372 fprintf(stderr, "main: Couldn't load ROM file %s!\n", rom);
373 return 1;
375 // Set untouched pads
376 megad.pad[0] = megad.pad[1] = 0xF303F;
377 #ifdef JOYSTICK_SUPPORT
378 if(dgen_joystick)
379 megad.init_joysticks();
380 #endif
381 // Load patches, if given
382 if(patches)
384 printf("main: Using patch codes %s\n", patches);
385 megad.patch(patches);
387 // Fix checksum
388 megad.fix_rom_checksum();
389 // Reset
390 megad.reset();
391 // Set PAL mode
392 megad.pal = pal_mode;
394 // Make sure the .dgen hierarchy is setup
395 mk_dgendir();
396 // Load up save RAM
397 ram_load(megad);
398 // If autoload is on, load save state 0
399 if(dgen_autoload)
401 slot = 0;
402 md_load(megad);
404 // If -s option was given, load the requested slot
405 if(start_slot >= 0)
407 slot = start_slot;
408 md_load(megad);
411 // Start the timing refs
412 gettimeofday(&oldclk, NULL);
413 gettimeofday(&startclk, NULL);
414 // Start audio
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... ;)
421 while(running)
423 int frames_todo;
424 frames_todo = 1;
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;
432 else
433 usec += newclk.tv_usec - oldclk.tv_usec;
434 frames_todo = usec / USEC_FRAME;
435 usec %= USEC_FRAME;
436 oldclk = newclk;
437 // We don't want to skip too many frames - this isn't Unreal ;)
438 if(frames_todo > 8) frames_todo = 8;
439 // Skip these frames
440 for(;frames_todo > 1; --frames_todo)
442 DO_DEMO
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);
449 while(wp != rp)
451 pd_sound_write(wp);
452 ++wp; wp &= dgen_soundsegs;
453 // Skip a frame to keep the sound going, until we hit the read
454 // point.
455 if(wp != rp)
457 DO_DEMO
458 megad.one_frame(NULL, NULL, &sndi);
462 // If there are frames to do, do them! :)
463 if(frames_todo)
465 DO_FRAME(&mdscr, mdpal);
466 // Update palette
467 if(mdpal && pal_dirty)
469 pd_graphics_palette_update();
470 pal_dirty = 0;
473 // Update screen
474 pd_graphics_update();
475 ++f;
477 // Sleep a bit
478 #ifdef __BEOS__
479 if(dgen_nice) snooze(dgen_nice);
480 #else
481 if(dgen_nice) usleep(dgen_nice);
482 #endif
484 // Print fps
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));
489 // Cleanup
490 if(demo) fclose(demo);
491 ram_save(megad);
492 if(dgen_autosave) { slot = 0; md_save(megad); }
493 megad.unplug();
494 pd_quit();
495 YM2612Shutdown();
497 // Come back anytime :)
498 return 0;