changeset 1531:092675db4f37

Add support for loading ROMs from zip files
author Michael Pavone <pavone@retrodev.com>
date Sat, 24 Mar 2018 15:33:44 -0700
parents 00d788dac91a
children b505083dcd87
files Makefile blastem.c zip.c zip.h
diffstat 4 files changed, 266 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Fri Mar 23 22:30:02 2018 -0700
+++ b/Makefile	Sat Mar 24 15:33:44 2018 -0700
@@ -131,7 +131,7 @@
 
 MAINOBJS=blastem.o system.o genesis.o debug.o gdb_remote.o vdp.o render_sdl.o ppm.o io.o romdb.o hash.o menu.o xband.o\
 	realtec.o i2c.o nor.o sega_mapper.o multi_game.o megawifi.o net.o serialize.o $(TERMINAL) $(CONFIGOBJS) gst.o\
-	$(M68KOBJS) $(TRANSOBJS) $(AUDIOOBJS)
+	zip.o $(M68KOBJS) $(TRANSOBJS) $(AUDIOOBJS)
 	
 ifdef NOZLIB
 CFLAGS+= -DDISABLE_ZLIB
--- a/blastem.c	Fri Mar 23 22:30:02 2018 -0700
+++ b/blastem.c	Sat Mar 24 15:33:44 2018 -0700
@@ -24,6 +24,7 @@
 #include "arena.h"
 #include "config.h"
 #include "menu.h"
+#include "zip.h"
 
 #define BLASTEM_VERSION "0.5.2-pre"
 
@@ -95,9 +96,48 @@
 	return readsize;
 }
 
+uint32_t load_rom_zip(char *filename, void **dst)
+{
+	static const char *valid_exts[] = {"bin", "md", "gen", "sms", "rom"};
+	const uint32_t num_exts = sizeof(valid_exts)/sizeof(*valid_exts);
+	zip_file *z = zip_open(filename);
+	if (!z) {
+		return 0;
+	}
+	
+	for (uint32_t i = 0; i < z->num_entries; i++)
+	{
+		char *ext = path_extension(z->entries[i].name);
+		if (!ext) {
+			continue;
+		}
+		for (uint32_t j = 0; j < num_exts; j++)
+		{
+			if (!strcasecmp(ext, valid_exts[j])) {
+				size_t out_size = nearest_pow2(z->entries[i].size);
+				*dst = zip_read(z, i, &out_size);
+				if (*dst) {
+					free(ext);
+					zip_close(z);
+					return out_size;
+				}
+			}
+		}
+		free(ext);
+	}
+	zip_close(z);
+	return 0;
+}
+
 uint32_t load_rom(char * filename, void **dst, system_type *stype)
 {
 	uint8_t header[10];
+	char *ext = path_extension(filename);
+	if (!strcasecmp(ext, "zip")) {
+		free(ext);
+		return load_rom_zip(filename, dst);
+	}
+	free(ext);
 	ROMFILE f = romopen(filename, "rb");
 	if (!f) {
 		return 0;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zip.c	Sat Mar 24 15:33:44 2018 -0700
@@ -0,0 +1,200 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include "util.h"
+#include "zip.h"
+#ifndef DISABLE_ZLIB
+#include "zlib/zlib.h"
+#endif
+
+static const char cdfd_magic[4] = {'P', 'K', 1, 2};
+static const char eocd_magic[4] = {'P', 'K', 5, 6};
+#define MIN_EOCD_SIZE 22
+#define MIN_CDFD_SIZE 46
+#define ZIP_MAX_EOCD_OFFSET (64*1024+MIN_EOCD_SIZE)
+
+enum {
+	ZIP_STORE = 0,
+	ZIP_DEFLATE = 8
+};
+
+zip_file *zip_open(char *filename)
+{
+	FILE *f = fopen(filename, "rb");
+	if (!f) {
+		return NULL;
+	}
+	long fsize = file_size(f);
+	if (fsize < MIN_EOCD_SIZE) {
+		//too small to be a zip file
+		goto fail;
+	}
+	
+	long max_offset = fsize > ZIP_MAX_EOCD_OFFSET ? ZIP_MAX_EOCD_OFFSET : fsize;
+	fseek(f, -max_offset, SEEK_END);
+	uint8_t *buf = malloc(max_offset);
+	if (max_offset != fread(buf, 1, max_offset, f)) {
+		goto fail;
+	}
+	
+	long current_offset;
+	uint32_t cd_start, cd_size;
+	uint16_t cd_count;
+	for (current_offset = max_offset - MIN_EOCD_SIZE; current_offset >= 0; current_offset--)
+	{
+		if (memcmp(eocd_magic, buf + current_offset, sizeof(eocd_magic))) {
+			continue;
+		}
+		uint16_t comment_size = buf[current_offset + 20] | buf[current_offset + 21] << 8;
+		if (comment_size != (max_offset - current_offset - MIN_EOCD_SIZE)) {
+			continue;
+		}
+		cd_start = buf[current_offset + 16] | buf[current_offset + 17] << 8
+			| buf[current_offset + 18] << 16 | buf[current_offset + 19] << 24;
+		if (cd_start > (fsize - (max_offset - current_offset))) {
+			continue;
+		}
+		cd_size = buf[current_offset + 12] | buf[current_offset + 13] << 8
+			| buf[current_offset + 14] << 16 | buf[current_offset + 15] << 24;
+		if ((cd_start + cd_size) > (fsize - (max_offset - current_offset))) {
+			continue;
+		}
+		cd_count = buf[current_offset + 10] | buf[current_offset + 11] << 8;
+		break;
+	}
+	free(buf);
+	if (current_offset < 0) {
+		//failed to find EOCD
+		goto fail;
+	}
+	buf = malloc(cd_size);
+	fseek(f, cd_start, SEEK_SET);
+	if (cd_size != fread(buf, 1, cd_size, f)) {
+		goto fail_free;
+	}
+	zip_entry *entries = calloc(cd_count, sizeof(zip_entry));
+	uint32_t cd_max_last = cd_size - MIN_CDFD_SIZE;
+	zip_entry *cur_entry = entries;
+	for (uint32_t off = 0; cd_count && off <= cd_max_last; cur_entry++, cd_count--)
+	{
+		if (memcmp(buf + off, cdfd_magic, sizeof(cdfd_magic))) {
+			goto fail_entries;
+		}
+		uint32_t name_length = buf[off + 28] | buf[off + 29] << 8;
+		uint32_t extra_length = buf[off + 30] | buf[off + 31] << 8;
+		//TODO: verify name length doesn't go past end of CD
+		
+		cur_entry->name = malloc(name_length + 1);
+		memcpy(cur_entry->name, buf + off + MIN_CDFD_SIZE, name_length);
+		cur_entry->name[name_length] = 0;
+		
+		cur_entry->compressed_size = buf[off + 20] | buf[off + 21] << 8 
+			| buf[off + 22] << 16 | buf[off + 23] << 24;
+		cur_entry->size = buf[off + 24] | buf[off + 25] << 8 
+			| buf[off + 26] << 16 | buf[off + 27] << 24;
+			
+		cur_entry->local_header_off = buf[off + 42] | buf[off + 43] << 8 
+			| buf[off + 44] << 16 | buf[off + 45] << 24;
+			
+		cur_entry->compression_method = buf[off + 10] | buf[off + 11] << 8;
+		
+		off += name_length + extra_length + MIN_CDFD_SIZE;
+	}
+	
+	zip_file *z = malloc(sizeof(zip_file));
+	z->entries = entries;
+	z->file = f;
+	z->num_entries = cur_entry - entries;
+	return z;
+	
+fail_entries:
+	for (cur_entry--; cur_entry >= entries; cur_entry--)
+	{
+		free(cur_entry->name);
+	}
+	free(entries);
+fail_free:
+	free(buf);
+fail:
+	fclose(f);
+	return NULL;
+}
+
+uint8_t *zip_read(zip_file *f, uint32_t index, size_t *out_size)
+{
+	
+	fseek(f->file, f->entries[index].local_header_off + 26, SEEK_SET);
+	uint8_t tmp[4];
+	if (sizeof(tmp) != fread(tmp, 1, sizeof(tmp), f->file)) {
+		return NULL;
+	}
+	uint32_t local_variable = (tmp[0] | tmp[1] << 8) + (tmp[2] | tmp[3] << 8);
+	fseek(f->file, f->entries[index].local_header_off + local_variable + 30, SEEK_SET);
+	
+	size_t int_size;
+	if (!out_size) {
+		out_size = &int_size;
+		int_size = f->entries[index].size;
+	}
+	
+	uint8_t *buf = malloc(*out_size);
+	if (*out_size > f->entries[index].size) {
+		*out_size = f->entries[index].size;
+	}
+	switch(f->entries[index].compression_method)
+	{
+	case ZIP_STORE:
+		if (*out_size != fread(buf, 1, *out_size, f->file)) {
+			free(buf);
+			return NULL;
+		}
+		break;
+#ifndef DISABLE_ZLIB
+	case ZIP_DEFLATE: {
+		//note in unzip.c in zlib/contrib suggests a dummy byte is needed, so we allocate an extra byte here
+		uint8_t *src_buf = malloc(f->entries[index].compressed_size + 1);
+		if (f->entries[index].compressed_size != fread(src_buf, 1, f->entries[index].compressed_size, f->file)) {
+			free(src_buf);
+			return NULL;
+		}
+		uLongf destLen = *out_size;
+		z_stream stream;
+		memset(&stream, 0, sizeof(stream));
+		stream.avail_in = f->entries[index].compressed_size + 1;
+		stream.next_in = src_buf;
+		stream.next_out = buf;
+		stream.avail_out = *out_size;
+		if (Z_OK == inflateInit2(&stream, -15)) {
+			int result = inflate(&stream, Z_FINISH);
+			*out_size = stream.total_out;
+			free(src_buf);
+			inflateEnd(&stream);
+			if (result != Z_OK && result != Z_STREAM_END && result != Z_BUF_ERROR) {
+				free(buf);
+				return NULL;
+			}
+		}
+		break;
+#endif
+	}
+	default:
+		free(buf);
+		return NULL;
+	}
+	
+	return buf;
+}
+
+void zip_close(zip_file *f)
+{
+	fclose(f->file);
+	for (uint32_t i = 0; i < f->num_entries; i++)
+	{
+		free(f->entries[i].name);
+	}
+	free(f->entries);
+	free(f);
+}
+
+ 
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zip.h	Sat Mar 24 15:33:44 2018 -0700
@@ -0,0 +1,25 @@
+#ifndef ZIP_H_
+#define ZIP_H_
+
+#include <stdint.h>
+#include <stdio.h>
+
+typedef struct {
+	uint64_t compressed_size;
+	uint64_t size;
+	uint64_t local_header_off;
+	char     *name;
+	uint16_t compression_method;
+} zip_entry;
+
+typedef struct {
+	zip_entry *entries;
+	FILE      *file;
+	uint32_t  num_entries;
+} zip_file;
+
+zip_file *zip_open(char *filename);
+uint8_t *zip_read(zip_file *f, uint32_t index, size_t *out_size);
+void zip_close(zip_file *f);
+
+#endif //ZIP_H_
\ No newline at end of file