diff --git a/Makefile b/Makefile index d5f2b4b9b7..344a4cb2d8 100644 --- a/Makefile +++ b/Makefile @@ -266,16 +266,12 @@ endif ifeq ($(HAVE_SDL_IMAGE), 1) LIBS += $(SDL_IMAGE_LIBS) DEFINES += $(SDL_IMAGE_CFLAGS) -else - ifeq ($(HAVE_ZLIB), 1) - OBJ += gfx/rpng/rpng.o - DEFINES += -DHAVE_ZLIB_DEFLATE - endif endif ifeq ($(HAVE_ZLIB), 1) + OBJ += gfx/rpng/rpng.o file_extract.o LIBS += $(ZLIB_LIBS) - DEFINES += $(ZLIB_CFLAGS) + DEFINES += $(ZLIB_CFLAGS) -DHAVE_ZLIB_DEFLATE endif ifeq ($(HAVE_FFMPEG), 1) diff --git a/config.features.h b/config.features.h index daf00f3575..dbad8369b2 100644 --- a/config.features.h +++ b/config.features.h @@ -104,6 +104,12 @@ static const bool _xaudio_supp = true; static const bool _xaudio_supp = false; #endif +#ifdef HAVE_ZLIB +static const bool _zlib_supp = true; +#else +static const bool _zlib_supp = false; +#endif + #ifdef HAVE_DYLIB static const bool _dylib_supp = true; #else diff --git a/console/griffin/griffin.c b/console/griffin/griffin.c index b5ea1f6966..73ec326152 100644 --- a/console/griffin/griffin.c +++ b/console/griffin/griffin.c @@ -48,6 +48,7 @@ CONSOLE EXTENSIONS #ifdef HAVE_ZLIB #include "../rarch_zlib.c" +#include "../../file_extract.c" #endif /*============================================================ diff --git a/file.c b/file.c index 55a940d6b0..8ccbc1c41c 100644 --- a/file.c +++ b/file.c @@ -25,6 +25,7 @@ #include "patch.h" #include "compat/strl.h" #include "hash.h" +#include "file_extract.h" #if defined(_WIN32) && !defined(_XBOX) #include @@ -36,6 +37,20 @@ #define INVALID_FILE_ATTRIBUTES -1 #endif +// Dump stuff to file. +bool write_file(const char *path, const void *data, size_t size) +{ + FILE *file = fopen(path, "wb"); + if (!file) + return false; + else + { + bool ret = fwrite(data, 1, size, file) == size; + fclose(file); + return ret; + } +} + // Generic file loader. ssize_t read_file(const char *path, void **buf) { @@ -278,26 +293,13 @@ static ssize_t read_rom_file(FILE *file, void **buf) g_extern.cart_crc = crc32_calculate(ret_buf, ret); sha256_hash(g_extern.sha256, ret_buf, ret); - RARCH_LOG("SHA256 sum: %s\n", g_extern.sha256); + RARCH_LOG("CRC32: 0x%x, SHA256: %s\n", + (unsigned)g_extern.cart_crc, g_extern.sha256); *buf = ret_buf; return ret; } -// Dump stuff to file. -static bool dump_to_file(const char *path, const void *data, size_t size) -{ - FILE *file = fopen(path, "wb"); - if (!file) - return false; - else - { - bool ret = fwrite(data, 1, size, file) == size; - fclose(file); - return ret; - } -} - static const char *ramtype2str(int type) { switch (type) @@ -348,7 +350,7 @@ static void dump_to_file_desperate(const void *data, size_t size, int type) strlcat(path, timebuf, sizeof(path)); strlcat(path, ramtype2str(type), sizeof(path)); - if (dump_to_file(path, data, size)) + if (write_file(path, data, size)) RARCH_WARN("Succeeded in saving RAM data to \"%s\".\n", path); else goto error; @@ -376,7 +378,7 @@ bool save_state(const char *path) RARCH_LOG("State size: %d bytes.\n", (int)size); bool ret = pretro_serialize(data, size); if (ret) - ret = dump_to_file(path, data, size); + ret = write_file(path, data, size); if (!ret) RARCH_ERR("Failed to save state to \"%s\".\n", path); @@ -495,7 +497,7 @@ void save_ram_file(const char *path, int type) if (data && size > 0) { - if (!dump_to_file(path, data, size)) + if (!write_file(path, data, size)) { RARCH_ERR("Failed to save SRAM.\n"); RARCH_WARN("Attempting to recover ...\n"); @@ -643,6 +645,23 @@ static bool load_sufami_rom(void) bool init_rom_file(enum rarch_game_type type) { +#ifdef HAVE_ZLIB + if (*g_extern.fullpath && !g_extern.system.block_extract) + { + const char *ext = path_get_extension(g_extern.fullpath); + if (ext && !strcasecmp(ext, "zip")) + { + g_extern.rom_file_temporary = true; + if (!zlib_extract_first_rom(g_extern.fullpath, sizeof(g_extern.fullpath), g_extern.system.valid_extensions)) + { + RARCH_ERR("Failed to extract ROM from zipped file: %s.\n", g_extern.fullpath); + g_extern.rom_file_temporary = false; + return false; + } + } + } +#endif + switch (type) { case RARCH_CART_SGB: diff --git a/file.h b/file.h index dac862eb0d..d5ea111109 100644 --- a/file.h +++ b/file.h @@ -31,6 +31,7 @@ extern "C" { // Generic file, path and directory handling. ssize_t read_file(const char *path, void **buf); +bool write_file(const char *path, const void *buf, size_t size); bool load_state(const char *path); bool save_state(const char *path); diff --git a/file_extract.c b/file_extract.c new file mode 100644 index 0000000000..e9ee39b1fa --- /dev/null +++ b/file_extract.c @@ -0,0 +1,189 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2013 - Hans-Kristian Arntzen + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#include "file_extract.h" +#include "file.h" +#include "compat/strl.h" +#include +#include + +#ifdef WANT_RZLIB +#include "deps/rzlib/zlib.h" +#else +#include +#endif + +#include "hash.h" + +// Modified from nall::unzip (higan). + +#define GOTO_END_ERROR() do { \ + RARCH_ERR("ZIP extraction failed at line: %d.\n", __LINE__); \ + ret = false; \ + goto end; \ +} while(0) + +static uint32_t read_le(const uint8_t *data, unsigned size) +{ + uint32_t val = 0; + size *= 8; + for (unsigned i = 0; i < size; i += 8) + val |= *data++ << i; + + return val; +} + +static bool inflate_data_to_file(const char *path, uint8_t *cdata, + uint32_t csize, uint32_t size, uint32_t crc32) +{ + bool ret = true; + uint8_t *out_data = (uint8_t*)malloc(size); + if (!out_data) + return false; + + z_stream stream = {0}; + + if (inflateInit2(&stream, -MAX_WBITS) != Z_OK) + GOTO_END_ERROR(); + + stream.next_in = cdata; + stream.avail_in = csize; + stream.next_out = out_data; + stream.avail_out = size; + + if (inflate(&stream, Z_FINISH) != Z_STREAM_END) + { + inflateEnd(&stream); + GOTO_END_ERROR(); + } + inflateEnd(&stream); + + uint32_t real_crc32 = crc32_calculate(out_data, size); + if (real_crc32 != crc32) + RARCH_WARN("File CRC differs from ZIP CRC. File: 0x%x, ZIP: 0x%x.\n", + (unsigned)real_crc32, (unsigned)crc32); + + if (!write_file(path, out_data, size)) + GOTO_END_ERROR(); + +end: + free(out_data); + return ret; +} + +bool zlib_extract_first_rom(char *zip_path, size_t zip_path_size, const char *valid_exts) +{ + bool ret = true; + if (!valid_exts) + { + RARCH_ERR("Libretro implementation does not have any valid extensions. Cannot unzip without knowing this.\n"); + return false; + } + + struct string_list *list = string_split(valid_exts, "|"); + if (!list) + return false; + + uint8_t *data = NULL; + ssize_t zip_size = read_file(zip_path, (void**)&data); + if (zip_size < 22) + GOTO_END_ERROR(); + + const uint8_t *footer = data + zip_size - 22; + for (;; footer--) + { + if (footer <= data + 22) + GOTO_END_ERROR(); + if (read_le(footer, 4) == 0x06054b50) + { + unsigned comment_len = read_le(footer + 20, 2); + if (footer + 22 + comment_len == data + zip_size) + break; + } + } + + const uint8_t *directory = data + read_le(footer + 16, 4); + + for (;;) + { + uint32_t signature = read_le(directory + 0, 4); + if (signature != 0x02014b50) + break; + + unsigned cmode = read_le(directory + 10, 2); + uint32_t crc32 = read_le(directory + 16, 4); + uint32_t csize = read_le(directory + 20, 4); + uint32_t size = read_le(directory + 24, 4); + + unsigned namelength = read_le(directory + 28, 2); + unsigned extralength = read_le(directory + 30, 2); + unsigned commentlength = read_le(directory + 32, 2); + + char filename[PATH_MAX] = {0}; + if (namelength >= PATH_MAX) + GOTO_END_ERROR(); + + memcpy(filename, directory + 46, namelength); + + uint32_t offset = read_le(directory + 42, 4); + unsigned offsetNL = read_le(data + offset + 26, 2); + unsigned offsetEL = read_le(data + offset + 28, 2); + + uint8_t *cdata = data + offset + 30 + offsetNL + offsetEL; + + RARCH_LOG("OFFSET: %u, CSIZE: %u, SIZE: %u.\n", offset + 30 + offsetNL + offsetEL, csize, size); + + // Extract first ROM that matches our list. + const char *ext = path_get_extension(filename); + if (ext && string_list_find_elem(list, ext)) + { + char new_path[PATH_MAX]; + fill_pathname_resolve_relative(new_path, zip_path, + path_basename(filename), sizeof(new_path)); + + switch (cmode) + { + case 0: // Uncompressed + if (!write_file(new_path, cdata, size)) + GOTO_END_ERROR(); + goto end; + + case 8: // Deflate + if (inflate_data_to_file(new_path, cdata, csize, size, crc32)) + { + strlcpy(zip_path, new_path, zip_path_size); + goto end; + } + else + GOTO_END_ERROR(); + break; + + default: + GOTO_END_ERROR(); + } + } + + directory += 46 + namelength + extralength + commentlength; + } + + RARCH_ERR("Didn't find any ROMS that matched valid extensions for libretro implementation.\n"); + GOTO_END_ERROR(); + +end: + free(data); + string_list_free(list); + return ret; +} + diff --git a/file_extract.h b/file_extract.h new file mode 100644 index 0000000000..8c07e14604 --- /dev/null +++ b/file_extract.h @@ -0,0 +1,25 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2013 - Hans-Kristian Arntzen + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#ifndef FILE_EXTRACT_H__ +#define FILE_EXTRACT_H__ + +#include "boolean.h" +#include + +bool zlib_extract_first_rom(char *zip_path, size_t zip_path_size, const char *valid_exts); + +#endif + diff --git a/general.h b/general.h index 301971ddce..2ca8ceb05e 100644 --- a/general.h +++ b/general.h @@ -309,6 +309,7 @@ struct global bool has_justifiers; bool has_multitap; + bool rom_file_temporary; enum rarch_game_type game_type; uint32_t cart_crc; diff --git a/gfx/rpng/rpng.c b/gfx/rpng/rpng.c index f0d652f57c..7fa1e2fa9f 100644 --- a/gfx/rpng/rpng.c +++ b/gfx/rpng/rpng.c @@ -402,7 +402,7 @@ bool rpng_load_image_argb(const char *path, uint32_t **data, unsigned *width, un stream.avail_out = inflate_buf_size; stream.next_out = inflate_buf; - if (inflate(&stream, Z_SYNC_FLUSH) != Z_STREAM_END) + if (inflate(&stream, Z_FINISH) != Z_STREAM_END) { inflateEnd(&stream); GOTO_END_ERROR(); @@ -697,6 +697,7 @@ static bool rpng_save_image(const char *path, const uint8_t *data, deflateEnd(&stream); GOTO_END_ERROR(); } + deflateEnd(&stream); memcpy(deflate_buf + 4, "IDAT", 4); dword_write_be(deflate_buf + 0, stream.total_out); diff --git a/retroarch.c b/retroarch.c index 761159c10b..e1420ae603 100644 --- a/retroarch.c +++ b/retroarch.c @@ -597,6 +597,7 @@ static void print_features(void) _PSUPP(pulse, "PulseAudio", "audio driver"); _PSUPP(dsound, "DirectSound", "audio driver"); _PSUPP(xaudio, "XAudio2", "audio driver"); + _PSUPP(zlib, "zlib", "PNG encode/decode and .zip extraction"); _PSUPP(al, "OpenAL", "audio driver"); _PSUPP(dylib, "External", "External filter and plugin support"); _PSUPP(cg, "Cg", "Cg pixel shaders"); @@ -2903,6 +2904,14 @@ void rarch_main_deinit(void) uninit_drivers(); uninit_libretro_sym(); + if (g_extern.rom_file_temporary) + { + RARCH_LOG("Removing tempoary ROM file: %s.\n", g_extern.fullpath); + if (remove(g_extern.fullpath) < 0) + RARCH_ERR("Failed to remove temporary file: %s.\n", g_extern.fullpath); + g_extern.rom_file_temporary = false; + } + g_extern.main_is_init = false; }