Subversion Repositories Kolibri OS

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
9837 turbocat 1
// DGen/SDL 1.17
2
// by Joe Groff 
3
// Read LICENSE for copyright etc., but if you've seen one BSDish license,
4
// you've seen them all ;)
5
 
6
#include 
7
#include 
8
#include 
9
#include 
10
#include 
11
#include 
12
#include 
13
#include 
14
#include 
15
#include 
16
 
17
#ifdef __MINGW32__
18
#include 
19
#include 
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 
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
 
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
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;
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
 
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
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
 
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
 
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
}