changeset 1140:4490c9c12272

Detect system type from filename if header based methods fail. Allow overriding system type from command line.
author Michael Pavone <pavone@retrodev.com>
date Mon, 02 Jan 2017 21:46:26 -0800
parents 160e3f597cec
children 7e199bebde2f
files blastem.c config.c system.c system.h util.c util.h
diffstat 6 files changed, 124 insertions(+), 40 deletions(-) [+]
line wrap: on
line diff
--- a/blastem.c	Mon Jan 02 16:33:03 2017 -0800
+++ b/blastem.c	Mon Jan 02 21:46:26 2017 -0800
@@ -49,7 +49,7 @@
 #define SMD_MAGIC3 0xBB
 #define SMD_BLOCK_SIZE 0x4000
 
-int load_smd_rom(long filesize, FILE * f, uint16_t **buffer)
+int load_smd_rom(long filesize, FILE * f, void **buffer)
 {
 	uint8_t block[SMD_BLOCK_SIZE];
 	filesize -= SMD_HEADER_SIZE;
@@ -67,7 +67,7 @@
 	return rom_size;
 }
 
-int load_rom(char * filename, uint16_t **dst, system_type *stype)
+uint32_t load_rom(char * filename, void **dst, system_type *stype)
 {
 	uint8_t header[10];
 	FILE * f = fopen(filename, "rb");
@@ -102,9 +102,6 @@
 		fatal_error("Error reading from %s\n", filename);
 	}
 	fclose(f);
-	if (stype) {
-		*stype = detect_system_type((uint8_t *)*dst, filesize);
-	}
 	return filesize;
 }
 
@@ -177,12 +174,11 @@
 	int debug = 0;
 	uint32_t opts = 0;
 	int loaded = 0;
-	system_type stype;
+	system_type stype = SYSTEM_UNKNOWN, force_stype = SYSTEM_UNKNOWN;
 	uint8_t force_region = 0;
 	char * romfname = NULL;
 	char * statefile = NULL;
-	int rom_size, lock_on_size;
-	uint16_t *cart = NULL, *lock_on = NULL;
+	system_media cart = {.chain = NULL}, lock_on;
 	debugger_type dtype = DEBUGGER_NATIVE;
 	uint8_t start_in_debugger = 0;
 	uint8_t fullscreen = FULLSCREEN_DEFAULT, use_gl = 1;
@@ -235,6 +231,21 @@
 					fatal_error("'%c' is not a valid region character for the -r option\n", argv[i][0]);
 				}
 				break;
+			case 'm':
+				i++;
+				if (i >= argc) {
+					fatal_error("-r must be followed by a machine type (sms, gen or jag)\n");
+				}
+				if (!strcmp("sms", argv[i])) {
+					stype = force_stype = SYSTEM_SMS;
+				} else if (!strcmp("gen", argv[i])) {
+					stype = force_stype = SYSTEM_GENESIS;
+				} else if (!strcmp("jag", argv[i])) {
+					stype = force_stype = SYSTEM_JAGUAR;
+				} else {
+					fatal_error("Unrecognized machine type %s\n", argv[i]);
+				}
+				break;
 			case 's':
 				i++;
 				if (i >= argc) {
@@ -253,10 +264,12 @@
 				if (i >= argc) {
 					fatal_error("-o must be followed by a lock on cartridge filename\n");
 				}
-				lock_on_size = load_rom(argv[i], &lock_on, NULL);
-				if (!lock_on_size) {
+				lock_on.size = load_rom(argv[i], &lock_on.buffer, NULL);
+				if (!lock_on.size) {
 					fatal_error("Failed to load lock on cartridge %s\n", argv[i]);
 				}
+				lock_on.extension = path_extension(argv[i]);
+				cart.chain = &lock_on;
 				break;
 			}
 			case 'h':
@@ -265,6 +278,10 @@
 					"Options:\n"
 					"	-h          Print this help text\n"
 					"	-r (J|U|E)  Force region to Japan, US or Europe respectively\n"
+					"   -m MACHINE  Force emulated machine type to MACHINE. Valid values are:\n"
+					"                   sms - Sega Master System/Mark III\n"
+					"                   gen - Sega Genesis/Megadrive\n"
+					"                   jag - Atari Jaguar\n"
 					"	-f          Toggles fullscreen mode\n"
 					"	-g          Disable OpenGL rendering\n"
 					"	-s FILE     Load a GST format savestate from FILE\n"
@@ -280,9 +297,10 @@
 				fatal_error("Unrecognized switch %s\n", argv[i]);
 			}
 		} else if (!loaded) {
-			if (!(rom_size = load_rom(argv[i], &cart, &stype))) {
+			if (!(cart.size = load_rom(argv[i], &cart.buffer, stype == SYSTEM_UNKNOWN ? &stype : NULL))) {
 				fatal_error("Failed to open %s for reading\n", argv[i]);
 			}
+			cart.extension = path_extension(argv[i]);
 			romfname = argv[i];
 			loaded = 1;
 		} else if (width < 0) {
@@ -299,21 +317,23 @@
 			romfname = "menu.bin";
 		}
 		if (is_absolute_path(romfname)) {
-			if (!(rom_size = load_rom(romfname, &cart, &stype))) {
+			if (!(cart.size = load_rom(romfname, &cart.buffer, &stype))) {
 				fatal_error("Failed to open UI ROM %s for reading", romfname);
 			}
 		} else {
-			long fsize;
-			cart = (uint16_t *)read_bundled_file(romfname, &fsize);
-			if (!cart) {
+			cart.buffer = (uint16_t *)read_bundled_file(romfname, &cart.size);
+			if (!cart.buffer) {
 				fatal_error("Failed to open UI ROM %s for reading", romfname);
 			}
-			stype = detect_system_type((uint8_t *)cart, fsize);
-			rom_size = nearest_pow2(fsize);
-			if (rom_size > fsize) {
-				cart = realloc(cart, rom_size);
+			uint32_t rom_size = nearest_pow2(cart.size);
+			if (rom_size > cart.size) {
+				cart.buffer = realloc(cart.buffer, rom_size);
+				cart.size = rom_size;
 			}
 		}
+		//force system detection, value on command line is only for games not the menu
+		stype = detect_system_type(&cart);
+		cart.extension = path_extension(romfname);
 
 		loaded = 1;
 	}
@@ -337,10 +357,16 @@
 		render_init(width, height, "BlastEm", fullscreen);
 	}
 
+	if (stype == SYSTEM_UNKNOWN) {
+		stype = detect_system_type(&cart);
+	}
+	if (stype == SYSTEM_UNKNOWN) {
+		fatal_error("Failed to detect system type for %s\n", romfname);
+	}
 	rom_info info;
-	current_system = alloc_config_system(stype, cart, rom_size, lock_on, lock_on_size, menu ? 0 : opts, force_region, &info);
+	current_system = alloc_config_system(stype, &cart, menu ? 0 : opts, force_region, &info);
 	if (!current_system) {
-		fatal_error("Failed to detect system type for %s\n", romfname);
+		fatal_error("Failed to configure emulated machine for %s\n", romfname);
 	}
 	setup_saves(romfname, &info, current_system);
 	update_title(info.name);
@@ -369,13 +395,21 @@
 				//start a new arena and save old one in suspended genesis context
 				current_system->arena = start_new_arena();
 			}
-			if (!(rom_size = load_rom(menu_context->next_rom, &cart, &stype))) {
+			if (!(cart.size = load_rom(menu_context->next_rom, &cart.buffer, &stype))) {
 				fatal_error("Failed to open %s for reading\n", menu_context->next_rom);
 			}
+			cart.extension = path_extension(menu_context->next_rom);
+			stype = force_stype;
+			if (stype == SYSTEM_UNKNOWN) {
+				stype = detect_system_type(&cart);
+			}
+			if (stype == SYSTEM_UNKNOWN) {
+				fatal_error("Failed to detect system type for %s\n", menu_context->next_rom);
+			}
 			//allocate new genesis context
-			game_context = alloc_config_system(stype, cart, rom_size, lock_on, lock_on_size, opts,force_region, &info);
+			game_context = alloc_config_system(stype, &cart, opts,force_region, &info);
 			if (!game_context) {
-				fatal_error("Failed to detect system type for %s\n", menu_context->next_rom);
+				fatal_error("Failed to configure emulated machine for %s\n", menu_context->next_rom);
 			}
 			menu_context->next_context = game_context;
 			game_context->next_context = menu_context;
--- a/config.c	Mon Jan 02 16:33:03 2017 -0800
+++ b/config.c	Mon Jan 02 21:46:26 2017 -0800
@@ -116,7 +116,7 @@
 
 tern_node *parse_bundled_config(char *config_name)
 {
-	long confsize;
+	uint32_t confsize;
 	char *confdata = read_bundled_file(config_name, &confsize);
 	tern_node *ret = NULL;
 	if (confdata) {
--- a/system.c	Mon Jan 02 16:33:03 2017 -0800
+++ b/system.c	Mon Jan 02 21:46:26 2017 -0800
@@ -9,24 +9,38 @@
 	return filesize >= offset+len && !memcmp(str, buffer + offset, len);
 }
 
-system_type detect_system_type(uint8_t *rom, long filesize)
+system_type detect_system_type(system_media *media)
 {
-	if (safe_cmp("SEGA", 0x100, rom, filesize)) {
+	if (safe_cmp("SEGA", 0x100, media->buffer, media->size)) {
 		//TODO: Differentiate between vanilla Genesis and Sega CD/32X games
 		return SYSTEM_GENESIS;
 	}
-	if (safe_cmp("TMR SEGA", 0x1FF0, rom, filesize) 
-		|| safe_cmp("TMR SEGA", 0x3FF0, rom, filesize) 
-		|| safe_cmp("TMR SEGA", 0x7FF0, rom, filesize) 
+	if (safe_cmp("TMR SEGA", 0x1FF0, media->buffer, media->size)
+		|| safe_cmp("TMR SEGA", 0x3FF0, media->buffer, media->size)
+		|| safe_cmp("TMR SEGA", 0x7FF0, media->buffer, media->size)
 	) {
 		return SYSTEM_SMS;
 	}
 	//TODO: Detect Jaguar ROMs here
 	
+	//Header based detection failed, examine filename for clues
+	if (media->extension) {
+		if (!strcmp("md", media->extension) || !strcmp("gen", media->extension)) {
+			return SYSTEM_GENESIS;
+		}
+		if (!strcmp("sms", media->extension)) {
+			return SYSTEM_SMS;
+		}
+		if (!strcmp("j64", media->extension)) {
+			return SYSTEM_JAGUAR;
+		}
+	}
+	
 	//More certain checks failed, look for a valid 68K reset vector
-	if (filesize >= 8) {
+	if (media->size >= 8) {
+		char *rom = media->buffer;
 		uint32_t reset = rom[4] << 24 | rom[5] << 16 | rom[6] << 8 | rom[7];
-		if (!(reset & 1) && reset < filesize) {
+		if (!(reset & 1) && reset < media->size) {
 			//we have a valid looking reset vector, assume it's a Genesis ROM
 			return SYSTEM_GENESIS;
 		}
@@ -34,15 +48,21 @@
 	return SYSTEM_UNKNOWN;
 }
 
-system_header *alloc_config_system(system_type stype, void *rom, uint32_t rom_size, void *lock_on, uint32_t lock_on_size, uint32_t opts, uint8_t force_region, rom_info *info_out)
+system_header *alloc_config_system(system_type stype, system_media *media, uint32_t opts, uint8_t force_region, rom_info *info_out)
 {
+	void *lock_on = NULL;
+	uint32_t lock_on_size = 0;
+	if (media->chain) {
+		lock_on = media->chain->buffer;
+		lock_on_size = media->chain->size;
+	}
 	switch (stype)
 	{
 	case SYSTEM_GENESIS:
-		return &(alloc_config_genesis(rom, rom_size, lock_on, lock_on_size, opts, force_region, info_out))->header;
+		return &(alloc_config_genesis(media->buffer, media->size, lock_on, lock_on_size, opts, force_region, info_out))->header;
 #ifndef NO_Z80
 	case SYSTEM_SMS:
-		return &(alloc_configure_sms(rom, rom_size, lock_on, lock_on_size, opts, force_region, info_out))->header;
+		return &(alloc_configure_sms(media->buffer, media->size, lock_on, lock_on_size, opts, force_region, info_out))->header;
 #endif
 	default:
 		return NULL;
--- a/system.h	Mon Jan 02 16:33:03 2017 -0800
+++ b/system.h	Mon Jan 02 21:46:26 2017 -0800
@@ -5,6 +5,7 @@
 #include "romdb.h"
 
 typedef struct system_header system_header;
+typedef struct system_media system_media;
 
 typedef enum {
 	SYSTEM_UNKNOWN,
@@ -45,9 +46,16 @@
 	system_type       type;
 };
 
+struct system_media {
+	void         *buffer;
+	char         *extension;
+	system_media *chain;
+	uint32_t     size;
+};
+
 #define OPT_ADDRESS_LOG (1U << 31U)
 
-system_type detect_system_type(uint8_t *rom, long filesize);
-system_header *alloc_config_system(system_type stype, void *rom, uint32_t rom_size, void *lock_on, uint32_t lock_on_size, uint32_t opts, uint8_t force_region, rom_info *info_out);
+system_type detect_system_type(system_media *media);
+system_header *alloc_config_system(system_type stype, system_media *media, uint32_t opts, uint8_t force_region, rom_info *info_out);
 
 #endif //SYSTEM_H_
--- a/util.c	Mon Jan 02 16:33:03 2017 -0800
+++ b/util.c	Mon Jan 02 21:46:26 2017 -0800
@@ -149,6 +149,26 @@
 	return barename;
 }
 
+char *path_extension(char *path)
+{
+	char *lastdot = NULL;
+	char *lastslash = NULL;
+	char *cur;
+	for (cur = path; *cur; cur++)
+	{
+		if (*cur == '.') {
+			lastdot = cur;
+		} else if (is_path_sep(*cur)) {
+			lastslash = cur + 1;
+		}
+	}
+	if (!lastdot || (lastslash && lastslash > lastdot)) {
+		//no extension
+		return NULL;
+	}
+	return strdup(lastdot+1);
+}
+
 uint32_t nearest_pow2(uint32_t val)
 {
 	uint32_t ret = 1;
@@ -524,7 +544,7 @@
 #ifdef __ANDROID__
 
 #include <SDL.h>
-char *read_bundled_file(char *name, long *sizeret)
+char *read_bundled_file(char *name, uint32_t *sizeret)
 {
 	SDL_RWops *rw = SDL_RWFromFile(name, "rb");
 	if (!rw) {
@@ -564,7 +584,7 @@
 
 #else
 
-char *read_bundled_file(char *name, long *sizeret)
+char *read_bundled_file(char *name, uint32_t *sizeret)
 {
 	char *exe_dir = get_exe_dir();
 	if (!exe_dir) {
--- a/util.h	Mon Jan 02 16:33:03 2017 -0800
+++ b/util.h	Mon Jan 02 21:46:26 2017 -0800
@@ -35,6 +35,8 @@
 char is_absolute_path(char *path);
 //Returns the basename of a path with th extension (if any) stripped
 char * basename_no_extension(char *path);
+//Returns the extension from a path or NULL if there is no extension
+char *path_extension(char *path);
 //Gets the smallest power of two that is >= a certain value, won't work for values > 0x80000000
 uint32_t nearest_pow2(uint32_t val);
 //Should be called by main with the value of argv[0] for use by get_exe_dir
@@ -48,7 +50,7 @@
 //Returns an appropriate path for saving non-config data like savestates
 char const *get_save_dir();
 //Reads a file bundled with the executable
-char *read_bundled_file(char *name, long *sizeret);
+char *read_bundled_file(char *name, uint32_t *sizeret);
 //Retunrs an array of normal files and directories residing in a directory
 dir_entry *get_dir_list(char *path, size_t *numret);
 //Frees a dir list returned by get_dir_list