main blastem.c
  1/*
  2 Copyright 2013-2016 Michael Pavone
  3 This file is part of BlastEm.
  4 BlastEm is free software distributed under the terms of the GNU General Public License version 3 or greater. See COPYING for full license text.
  5*/
  6#include <stdio.h>
  7#include <stdlib.h>
  8#include <string.h>
  9#include <ctype.h>
 10
 11#include "system.h"
 12#include "68kinst.h"
 13#include "m68k_core.h"
 14#include "z80_to_x86.h"
 15#include "mem.h"
 16#include "vdp.h"
 17#include "render.h"
 18#include "genesis.h"
 19#include "gdb_remote.h"
 20#include "gst.h"
 21#include "util.h"
 22#include "romdb.h"
 23#include "terminal.h"
 24#include "arena.h"
 25#include "config.h"
 26#include "bindings.h"
 27#include "menu.h"
 28#include "zip.h"
 29#define BLASTEM_VERSION "0.6.2"
 30
 31int headless = 0;
 32int exit_after = 0;
 33int z80_enabled = 1;
 34int frame_limit = 0;
 35uint8_t use_native_states = 1;
 36
 37tern_node * config;
 38
 39#define SMD_HEADER_SIZE 512
 40#define SMD_MAGIC1 0x03
 41#define SMD_MAGIC2 0xAA
 42#define SMD_MAGIC3 0xBB
 43#define SMD_BLOCK_SIZE 0x4000
 44
 45#include <zlib.h>
 46#define ROMFILE gzFile
 47#define romopen gzopen
 48#define romread gzfread
 49#define romseek gzseek
 50#define romgetc gzgetc
 51#define romclose gzclose
 52
 53uint16_t *process_smd_block(uint16_t *dst, uint8_t *src, size_t bytes)
 54{
 55	for (uint8_t *low = src, *high = (src+bytes/2), *end = src+bytes; high < end; high++, low++) {
 56		*(dst++) = *low << 8 | *high;
 57	}
 58	return dst;
 59}
 60
 61int load_smd_rom(ROMFILE f, void **buffer)
 62{
 63	uint8_t block[SMD_BLOCK_SIZE];
 64	romseek(f, SMD_HEADER_SIZE, SEEK_SET);
 65
 66	size_t filesize = 512 * 1024;
 67	size_t readsize = 0;
 68	uint16_t *dst, *buf;
 69	dst = buf = malloc(filesize);
 70	
 71
 72	size_t read;
 73	do {
 74		if ((readsize + SMD_BLOCK_SIZE > filesize)) {
 75			filesize *= 2;
 76			buf = realloc(buf, filesize);
 77			dst = buf + readsize/sizeof(uint16_t);
 78		}
 79		read = romread(block, 1, SMD_BLOCK_SIZE, f);
 80		if (read > 0) {
 81			dst = process_smd_block(dst, block, read);
 82			readsize += read;
 83		}
 84	} while(read > 0);
 85	romclose(f);
 86	
 87	*buffer = buf;
 88	
 89	return readsize;
 90}
 91
 92uint8_t is_smd_format(const char *filename, uint8_t *header)
 93{
 94	if (header[1] == SMD_MAGIC1 && header[8] == SMD_MAGIC2 && header[9] == SMD_MAGIC3) {
 95		int i;
 96		for (i = 3; i < 8; i++) {
 97			if (header[i] != 0) {
 98				return 0;
 99			}
100		}
101		if (i == 8) {
102			if (header[2]) {
103				fatal_error("%s is a split SMD ROM which is not currently supported", filename);
104			}
105			return 1;
106		}
107	}
108	return 0;
109}
110
111uint32_t load_rom_zip(const char *filename, void **dst)
112{
113	static const char *valid_exts[] = {"bin", "md", "gen", "sms", "rom", "smd"};
114	const uint32_t num_exts = sizeof(valid_exts)/sizeof(*valid_exts);
115	zip_file *z = zip_open(filename);
116	if (!z) {
117		return 0;
118	}
119	
120	for (uint32_t i = 0; i < z->num_entries; i++)
121	{
122		char *ext = path_extension(z->entries[i].name);
123		if (!ext) {
124			continue;
125		}
126		for (uint32_t j = 0; j < num_exts; j++)
127		{
128			if (!strcasecmp(ext, valid_exts[j])) {
129				size_t out_size = nearest_pow2(z->entries[i].size);
130				*dst = zip_read(z, i, &out_size);
131				if (*dst) {
132					if (is_smd_format(z->entries[i].name, *dst)) {
133						size_t offset;
134						for (offset = 0; offset + SMD_BLOCK_SIZE + SMD_HEADER_SIZE <= out_size; offset += SMD_BLOCK_SIZE)
135						{
136							uint8_t tmp[SMD_BLOCK_SIZE];
137							memcpy(tmp, *dst + offset + SMD_HEADER_SIZE, SMD_BLOCK_SIZE);
138							process_smd_block(*dst + offset, tmp, SMD_BLOCK_SIZE);
139						}
140						out_size = offset;
141					}
142					free(ext);
143					zip_close(z);
144					return out_size;
145				}
146			}
147		}
148		free(ext);
149	}
150	zip_close(z);
151	return 0;
152}
153
154uint32_t load_rom(const char * filename, void **dst, system_type *stype)
155{
156	uint8_t header[10];
157	char *ext = path_extension(filename);
158	if (ext && !strcasecmp(ext, "zip")) {
159		free(ext);
160		return load_rom_zip(filename, dst);
161	}
162	free(ext);
163	ROMFILE f = romopen(filename, "rb");
164	if (!f) {
165		return 0;
166	}
167	if (sizeof(header) != romread(header, 1, sizeof(header), f)) {
168		fatal_error("Error reading from %s\n", filename);
169	}
170	
171	if (is_smd_format(filename, header)) {
172		if (stype) {
173			*stype = SYSTEM_GENESIS;
174		}
175		return load_smd_rom(f, dst);
176	}
177	
178	size_t filesize = 512 * 1024;
179	size_t readsize = sizeof(header);
180		
181	char *buf = malloc(filesize);
182	memcpy(buf, header, readsize);
183	
184	size_t read;
185	do {
186		read = romread(buf + readsize, 1, filesize - readsize, f);
187		if (read > 0) {
188			readsize += read;
189			if (readsize == filesize) {
190				int one_more = romgetc(f);
191				if (one_more >= 0) {
192					filesize *= 2;
193					buf = realloc(buf, filesize);
194					buf[readsize++] = one_more;
195				} else {
196					read = 0;
197				}
198			}
199		}
200	} while (read > 0);
201	
202	*dst = buf;
203	
204	romclose(f);
205	return readsize;
206}
207
208
209
210int break_on_sync = 0;
211char *save_state_path;
212
213
214
215
216
217char * save_filename;
218system_header *current_system;
219system_header *menu_system;
220system_header *game_system;
221void persist_save()
222{
223	if (!game_system) {
224		return;
225	}
226	game_system->persist_save(game_system);
227}
228
229char *title;
230void update_title(char *rom_name)
231{
232	if (title) {
233		free(title);
234		title = NULL;
235	}
236	title = alloc_concat(rom_name, " - BlastEm");
237	render_update_caption(title);
238}
239
240static char *get_save_dir(system_media *media)
241{
242	char *savedir_template = tern_find_path(config, "ui\0save_path\0", TVAL_PTR).ptrval;
243	if (!savedir_template) {
244		savedir_template = "$USERDATA/blastem/$ROMNAME";
245	}
246	tern_node *vars = tern_insert_ptr(NULL, "ROMNAME", media->name);
247	vars = tern_insert_ptr(vars, "HOME", get_home_dir());
248	vars = tern_insert_ptr(vars, "EXEDIR", get_exe_dir());
249	vars = tern_insert_ptr(vars, "USERDATA", (char *)get_userdata_dir());
250	char *save_dir = replace_vars(savedir_template, vars, 1);
251	tern_free(vars);
252	if (!ensure_dir_exists(save_dir)) {
253		warning("Failed to create save directory %s\n", save_dir);
254	}
255	return save_dir;
256}
257
258void setup_saves(system_media *media, system_header *context)
259{
260	static uint8_t persist_save_registered;
261	rom_info *info = &context->info;
262	char *save_dir = get_save_dir(info->is_save_lock_on ? media->chain : media);
263	char const *parts[] = {save_dir, PATH_SEP, info->save_type == SAVE_I2C ? "save.eeprom" : info->save_type == SAVE_NOR ? "save.nor" : "save.sram"};
264	free(save_filename);
265	save_filename = alloc_concat_m(3, parts);
266	if (info->is_save_lock_on) {
267		//initial save dir was calculated based on lock-on cartridge because that's where the save device is
268		//save directory used for save states should still be located in the normal place
269		free(save_dir);
270		parts[0] = save_dir = get_save_dir(media);
271	}
272	if (use_native_states || context->type != SYSTEM_GENESIS) {
273		parts[2] = "quicksave.state";
274	} else {
275		parts[2] = "quicksave.gst";
276	}
277	free(save_state_path);
278	save_state_path = alloc_concat_m(3, parts);
279	context->save_dir = save_dir;
280	if (info->save_type != SAVE_NONE) {
281		context->load_save(context);
282		if (!persist_save_registered) {
283			atexit(persist_save);
284			persist_save_registered = 1;
285		}
286	}
287}
288
289void apply_updated_config(void)
290{
291	render_config_updated();
292	if (current_system && current_system->config_updated) {
293		current_system->config_updated(current_system);
294	}
295}
296
297static system_media cart, lock_on;
298void reload_media(void)
299{
300	if (!current_system) {
301		return;
302	}
303	if (current_system->next_rom) {
304		free(current_system->next_rom);
305	}
306	char const *parts[] = {
307		cart.dir, PATH_SEP, cart.name, ".", cart.extension
308	};
309	char const **start = parts[0] ? parts : parts + 2;
310	int num_parts = parts[0] ? 5 : 3;
311	if (!parts[4]) {
312		num_parts--;
313	}
314	current_system->next_rom = alloc_concat_m(num_parts, start);
315	current_system->request_exit(current_system);
316}
317
318void lockon_media(char *lock_on_path)
319{
320	reload_media();
321	cart.chain = &lock_on;
322	free(lock_on.dir);
323	free(lock_on.name);
324	free(lock_on.extension);
325	lock_on.dir = path_dirname(lock_on_path);
326	lock_on.name = basename_no_extension(lock_on_path);
327	lock_on.extension = path_extension(lock_on_path);
328	lock_on.size = load_rom(lock_on_path, &lock_on.buffer, NULL);
329}
330
331static uint32_t opts = 0;
332static uint8_t force_region = 0;
333void init_system_with_media(const char *path, system_type force_stype)
334{
335	if (game_system) {
336		game_system->persist_save(game_system);
337		//swap to game context arena and mark all allocated pages in it free
338		if (current_system == menu_system) {
339			current_system->arena = set_current_arena(game_system->arena);
340		}
341		mark_all_free();
342		game_system->free_context(game_system);
343	} else if(current_system) {
344		//start a new arena and save old one in suspended system context
345		current_system->arena = start_new_arena();
346	}
347	system_type stype = SYSTEM_UNKNOWN;
348	if (!(cart.size = load_rom(path, &cart.buffer, &stype))) {
349		fatal_error("Failed to open %s for reading\n", path);
350	}
351	free(cart.dir);
352	free(cart.name);
353	free(cart.extension);
354	cart.dir = path_dirname(path);
355	cart.name = basename_no_extension(path);
356	cart.extension = path_extension(path);
357	if (force_stype != SYSTEM_UNKNOWN) {
358		stype = force_stype;
359	}
360	if (stype == SYSTEM_UNKNOWN) {
361		stype = detect_system_type(&cart);
362	}
363	if (stype == SYSTEM_UNKNOWN) {
364		fatal_error("Failed to detect system type for %s\n", path);
365	}
366	//allocate new system context
367	game_system = alloc_config_system(stype, &cart, opts, force_region);
368	if (!game_system) {
369		fatal_error("Failed to configure emulated machine for %s\n", path);
370	}
371	if (menu_system) {
372		menu_system->next_context = game_system;
373	}
374	game_system->next_context = menu_system;
375	setup_saves(&cart, game_system);
376	update_title(game_system->info.name);
377}
378
379int main(int argc, char ** argv)
380{
381	set_exe_str(argv[0]);
382	config = load_config();
383	int width = -1;
384	int height = -1;
385	int loaded = 0;
386	system_type stype = SYSTEM_UNKNOWN, force_stype = SYSTEM_UNKNOWN;
387	char * romfname = NULL;
388	char * statefile = NULL;
389	debugger_type dtype = DEBUGGER_NATIVE;
390	uint8_t start_in_debugger = 0;
391	uint8_t debug_target = 0;
392	for (int i = 1; i < argc; i++) {
393		if (argv[i][0] == '-') {
394			switch(argv[i][1]) {
395			case 'b':
396				i++;
397				if (i >= argc) {
398					fatal_error("-b must be followed by a frame count\n");
399				}
400				headless = 1;
401				exit_after = atoi(argv[i]);
402				break;
403			case 'd':
404				start_in_debugger = 1;
405				//allow debugging the menu
406				if (argv[i][2] == 'm') {
407					debug_target = 1;
408				}
409				break;
410			case 'D':
411				gdb_remote_init();
412				dtype = DEBUGGER_GDB;
413				start_in_debugger = 1;
414				break;
415			case 'l':
416				opts |= OPT_ADDRESS_LOG;
417				break;
418			case 'v':
419				info_message("blastem %s\n", BLASTEM_VERSION);
420				return 0;
421				break;
422			case 'n':
423				z80_enabled = 0;
424				break;
425			case 'r':
426				i++;
427				if (i >= argc) {
428					fatal_error("-r must be followed by region (J, U or E)\n");
429				}
430				force_region = translate_region_char(toupper(argv[i][0]));
431				if (!force_region) {
432					fatal_error("'%c' is not a valid region character for the -r option\n", argv[i][0]);
433				}
434				break;
435			case 'm':
436				i++;
437				if (i >= argc) {
438					fatal_error("-r must be followed by a machine type (sms, gen or jag)\n");
439				}
440				if (!strcmp("sms", argv[i])) {
441					stype = force_stype = SYSTEM_SMS;
442				} else if (!strcmp("gen", argv[i])) {
443					stype = force_stype = SYSTEM_GENESIS;
444				} else {
445					fatal_error("Unrecognized machine type %s\n", argv[i]);
446				}
447				break;
448			case 's':
449				i++;
450				if (i >= argc) {
451					fatal_error("-s must be followed by a savestate filename\n");
452				}
453				statefile = argv[i];
454				break;
455			case 't':
456				force_no_terminal();
457				break;
458			case 'y':
459				opts |= YM_OPT_WAVE_LOG;
460				break;
461			case 'o': {
462				i++;
463				if (i >= argc) {
464					fatal_error("-o must be followed by a lock on cartridge filename\n");
465				}
466				lock_on.size = load_rom(argv[i], &lock_on.buffer, NULL);
467				if (!lock_on.size) {
468					fatal_error("Failed to load lock on cartridge %s\n", argv[i]);
469				}
470				lock_on.name = basename_no_extension(argv[i]);
471				lock_on.extension = path_extension(argv[i]);
472				cart.chain = &lock_on;
473				break;
474			}
475			case 'h':
476				info_message(
477					"Usage: blastem [OPTIONS] ROMFILE [WIDTH] [HEIGHT]\n"
478					"Options:\n"
479					"	-h          Print this help text\n"
480					"	-r (J|U|E)  Force region to Japan, US or Europe respectively\n"
481					"	-m MACHINE  Force emulated machine type to MACHINE. Valid values are:\n"
482					"                   sms - Sega Master System/Mark III\n"
483					"                   gen - Sega Genesis/Megadrive\n"
484					"	-s FILE     Load a GST format savestate from FILE\n"
485					"	-o FILE     Load FILE as a lock-on cartridge\n"
486					"	-d          Enter debugger on startup\n"
487					"	-n          Disable Z80\n"
488					"	-v          Display version number and exit\n"
489					"	-l          Log 68K code addresses (useful for assemblers)\n"
490					"	-y          Log individual YM-2612 channels to WAVE files\n"
491				);
492				return 0;
493			default:
494				fatal_error("Unrecognized switch %s\n", argv[i]);
495			}
496		} else if (!loaded) {
497			if (!(cart.size = load_rom(argv[i], &cart.buffer, stype == SYSTEM_UNKNOWN ? &stype : NULL))) {
498				fatal_error("Failed to open %s for reading\n", argv[i]);
499			}
500			cart.dir = path_dirname(argv[i]);
501			cart.name = basename_no_extension(argv[i]);
502			cart.extension = path_extension(argv[i]);
503			romfname = argv[i];
504			loaded = 1;
505		} else if (width < 0) {
506			width = atoi(argv[i]);
507		} else if (height < 0) {
508			height = atoi(argv[i]);
509		}
510	}
511	
512	int def_width = 0, def_height = 0;
513	char *config_width = tern_find_path(config, "video\0width\0", TVAL_PTR).ptrval;
514	if (config_width) {
515		def_width = atoi(config_width);
516	}
517	if (!def_width) {
518		def_width = 640;
519	}
520	char *config_height = tern_find_path(config, "video\0height\0", TVAL_PTR).ptrval;
521	if (config_height) {
522		def_height = atoi(config_height);
523	}
524	if (!def_height) {
525		def_height = -1;
526	}
527	width = width < 1 ? def_width : width;
528	height = height < 1 ? def_height : height;
529
530	if (!headless) {
531		render_init(width, height, "BlastEm");
532	}
533	set_bindings();
534	
535	uint8_t menu = !loaded;
536	if (!loaded) {
537		//load menu
538		romfname = tern_find_path(config, "ui\0rom\0", TVAL_PTR).ptrval;
539		if (!romfname) {
540			romfname = "menu.bin";
541		}
542		if (is_absolute_path(romfname)) {
543			if (!(cart.size = load_rom(romfname, &cart.buffer, &stype))) {
544				fatal_error("Failed to open UI ROM %s for reading", romfname);
545			}
546		} else {
547			cart.buffer = (uint16_t *)read_bundled_file(romfname, &cart.size);
548			if (!cart.buffer) {
549				fatal_error("Failed to open UI ROM %s for reading", romfname);
550			}
551			uint32_t rom_size = nearest_pow2(cart.size);
552			if (rom_size > cart.size) {
553				cart.buffer = realloc(cart.buffer, rom_size);
554				cart.size = rom_size;
555			}
556		}
557		//force system detection, value on command line is only for games not the menu
558		stype = detect_system_type(&cart);
559		cart.dir = path_dirname(romfname);
560		cart.name = basename_no_extension(romfname);
561		cart.extension = path_extension(romfname);
562		loaded = 1;
563	}
564	char *state_format = tern_find_path(config, "ui\0state_format\0", TVAL_PTR).ptrval;
565	if (state_format && !strcmp(state_format, "gst")) {
566		use_native_states = 0;
567	} else if (state_format && strcmp(state_format, "native")) {
568		warning("%s is not a valid value for the ui.state_format setting. Valid values are gst and native\n", state_format);
569	}
570
571	if (loaded) {
572		if (stype == SYSTEM_UNKNOWN) {
573			stype = detect_system_type(&cart);
574		}
575		if (stype == SYSTEM_UNKNOWN) {
576			fatal_error("Failed to detect system type for %s\n", romfname);
577		}
578		current_system = alloc_config_system(stype, &cart, menu ? 0 : opts, force_region);
579		if (!current_system) {
580			fatal_error("Failed to configure emulated machine for %s\n", romfname);
581		}
582	
583		setup_saves(&cart, current_system);
584		update_title(current_system->info.name);
585		if (menu) {
586			menu_system = current_system;
587		} else {
588			game_system = current_system;
589		}
590	}
591	
592	current_system->debugger_type = dtype;
593	current_system->enter_debugger = start_in_debugger && menu == debug_target;
594	current_system->start_context(current_system,  menu ? NULL : statefile);
595	for(;;)
596	{
597		if (current_system->should_exit) {
598			break;
599		}
600		if (current_system->next_rom) {
601			char *next_rom = current_system->next_rom;
602			current_system->next_rom = NULL;
603			init_system_with_media(next_rom, force_stype);
604			free(next_rom);
605			menu = 0;
606			current_system = game_system;
607			current_system->debugger_type = dtype;
608			current_system->enter_debugger = start_in_debugger && menu == debug_target;
609			current_system->start_context(current_system, statefile);
610		} else if (menu && game_system) {
611			current_system->arena = set_current_arena(game_system->arena);
612			current_system = game_system;
613			menu = 0;
614			current_system->resume_context(current_system);
615		} else if (!menu && menu_system) {
616			current_system->arena = set_current_arena(menu_system->arena);
617			current_system = menu_system;
618			menu = 1;
619			if (!current_system->next_rom) {
620				current_system->resume_context(current_system);
621			}
622		} else {
623			break;
624		}
625	}
626
627	return 0;
628}