# HG changeset patch # User Michael Pavone # Date 1521930824 25200 # Node ID 092675db4f372e2f6dcbdab6b07ecc277d4104b7 # Parent 00d788dac91af784e0373499564f8d3beb79d054 Add support for loading ROMs from zip files diff -r 00d788dac91a -r 092675db4f37 Makefile --- 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 diff -r 00d788dac91a -r 092675db4f37 blastem.c --- 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; diff -r 00d788dac91a -r 092675db4f37 zip.c --- /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 +#include +#include +#include +#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 -r 00d788dac91a -r 092675db4f37 zip.h --- /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 +#include + +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