summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Pavone <pavone@retrodev.com>2018-03-24 15:33:44 -0700
committerMichael Pavone <pavone@retrodev.com>2018-03-24 15:33:44 -0700
commit6e0a7307a3e97579a30655750aea7372115db367 (patch)
treeae61bfdaef54df7c2141b6b04d144713c8aa0a4b
parentd30df86b9188841fb04ccb6842af4bdb634badbe (diff)
Add support for loading ROMs from zip files
-rw-r--r--Makefile2
-rw-r--r--blastem.c40
-rw-r--r--zip.c200
-rw-r--r--zip.h25
4 files changed, 266 insertions, 1 deletions
diff --git a/Makefile b/Makefile
index f1fbc2c..587ba1c 100644
--- a/Makefile
+++ b/Makefile
@@ -131,7 +131,7 @@ LIBZOBJS=zlib/adler32.o zlib/compress.o zlib/crc32.o zlib/deflate.o zlib/gzclose
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
diff --git a/blastem.c b/blastem.c
index eeb201c..88a916e 100644
--- a/blastem.c
+++ b/blastem.c
@@ -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 @@ int load_smd_rom(ROMFILE f, void **buffer)
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;
diff --git a/zip.c b/zip.c
new file mode 100644
index 0000000..c978b60
--- /dev/null
+++ b/zip.c
@@ -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
diff --git a/zip.h b/zip.h
new file mode 100644
index 0000000..9e56084
--- /dev/null
+++ b/zip.h
@@ -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