diff --git a/src/minarch/main.c b/src/minarch/main.c index 552395e..48488cf 100644 --- a/src/minarch/main.c +++ b/src/minarch/main.c @@ -13,6 +13,7 @@ #include #include #include +#include #include "libretro.h" #include "defines.h" @@ -96,6 +97,7 @@ static struct Core { const char tag[8]; // eg. GBC const char name[128]; // eg. gambatte const char version[128]; // eg. Gambatte (v0.5.0-netlink 7e02df6) + const char extensions[128]; // eg. gb|gbc|dmg CoreOverrides* overrides; @@ -130,12 +132,89 @@ static struct Core { // retro_audio_buffer_status_callback_t audio_buffer_status; } core; +/////////////////////////////////////// +// based on picoarch/unzip.c + +#define ZIP_HEADER_SIZE 30 +#define ZIP_CHUNK_SIZE 65536 +#define ZIP_LE_READ16(buf) ((uint16_t)(((uint8_t *)(buf))[1] << 8 | ((uint8_t *)(buf))[0])) +#define ZIP_LE_READ32(buf) ((uint32_t)(((uint8_t *)(buf))[3] << 24 | ((uint8_t *)(buf))[2] << 16 | ((uint8_t *)(buf))[1] << 8 | ((uint8_t *)(buf))[0])) +typedef int (*Zip_extract_t)(FILE* zip, FILE* dst, size_t size); + +static int Zip_copy(FILE* zip, FILE* dst, size_t size) { // uncompressed + uint8_t buffer[ZIP_CHUNK_SIZE]; + while (size) { + size_t sz = MIN(size, ZIP_CHUNK_SIZE); + if (sz!= fread(buffer, 1, sz, zip)) return -1; + if (sz!=fwrite(buffer, 1, sz, dst)) return -1; + size -= sz; + } + return 0; +} +static int Zip_inflate(FILE* zip, FILE* dst, size_t size) { // compressed + z_stream stream = {0}; + size_t have = 0; + uint8_t in[ZIP_CHUNK_SIZE]; + uint8_t out[ZIP_CHUNK_SIZE]; + int ret = -1; + + ret = inflateInit2(&stream, -MAX_WBITS); + if (ret != Z_OK) + return ret; + + do { + size_t insize = MIN(size, ZIP_CHUNK_SIZE); + + stream.avail_in = fread(in, 1, insize, zip); + if (ferror(zip)) { + (void)inflateEnd(&stream); + return Z_ERRNO; + } + + if (!stream.avail_in) + break; + stream.next_in = in; + + do { + stream.avail_out = ZIP_CHUNK_SIZE; + stream.next_out = out; + + ret = inflate(&stream, Z_NO_FLUSH); + switch(ret) { + case Z_NEED_DICT: + ret = Z_DATA_ERROR; + case Z_DATA_ERROR: + case Z_MEM_ERROR: + (void)inflateEnd(&stream); + return ret; + } + + have = ZIP_CHUNK_SIZE - stream.avail_out; + if (fwrite(out, 1, have, dst) != have || ferror(dst)) { + (void)inflateEnd(&stream); + return Z_ERRNO; + } + } while (stream.avail_out == 0); + + size -= insize; + } while (size && ret != Z_STREAM_END); + + (void)inflateEnd(&stream); + + if (!size || ret == Z_STREAM_END) { + return Z_OK; + } else { + return Z_DATA_ERROR; + } +} + /////////////////////////////////////// static struct Game { char path[MAX_PATH]; char name[MAX_PATH]; // TODO: rename to basename? char m3u_path[MAX_PATH]; + char tmp_path[MAX_PATH]; // location of unzipped file void* data; size_t size; int is_open; @@ -146,12 +225,113 @@ static void Game_open(char* path) { strcpy((char*)game.path, path); strcpy((char*)game.name, strrchr(path, '/')+1); + // if we have a zip file + if (suffixMatch(".zip", game.path)) { + LOG_info("is zip file\n"); + int supports_zip = 0; + int i = 0; + char* ext; + char exts[128]; + char* extensions[32]; + strcpy(exts,core.extensions); + while ((ext=strtok(i?NULL:exts,"|"))) { + extensions[i++] = ext; + if (!strcmp("zip", ext)) { + supports_zip = 1; + break; + } + } + extensions[i] = NULL; + + // if the core doesn't support zip files natively + if (!supports_zip) { + FILE *zip = fopen(game.path, "r"); + if (zip==NULL) { + LOG_error("Error opening archive: %s\n\t%s\n", game.path, strerror(errno)); + return; + } + + // extract a known file format + uint8_t header[ZIP_HEADER_SIZE]; + uint32_t next = 0; + uint16_t len = 0; + char filename[MAX_PATH]; + uint32_t compressed_size = 0; + char extension[8]; + while (1) { + if (next) fseek(zip, next, SEEK_CUR); + + if (ZIP_HEADER_SIZE!=fread(header, 1, ZIP_HEADER_SIZE, zip)) break; + + if ((uint16_t)(header[6]) & 0x0008) break; + + len = ZIP_LE_READ16(&header[26]); + if (len>=MAX_PATH) break; + + if (len!=fread(filename,1,len,zip)) break; + filename[len] = '\0'; + LOG_info("filename: %s\n", filename); + + compressed_size = ZIP_LE_READ32(&header[18]); + + fseek(zip, ZIP_LE_READ16(&header[28]), SEEK_CUR); + next = compressed_size; + + int found = 0; + for (i=0; extensions[i]; i++) { + sprintf(extension, ".%s", extensions[i]); + if (suffixMatch(extension, filename)) { + + found = 1; + break; + } + } + if (!found) continue; + + char tmp_template[MAX_PATH]; + strcpy(tmp_template, "/tmp/minarch-XXXXXX"); + char* tmp_dirname = mkdtemp(tmp_template); + LOG_info("tmp_dirname: %s\n", tmp_dirname); + sprintf(game.tmp_path, "%s/%s", tmp_dirname, basename(filename)); + + // TODO: we need to clear game.tmp_path if anything below this point fails! + + FILE* dst = fopen(game.tmp_path, "w"); + if (dst==NULL) { + game.tmp_path[0] = '\0'; + LOG_error("Error extracting file: %s\n\t%s\n", filename, strerror(errno)); + return; + } + + Zip_extract_t extract = NULL; + switch (ZIP_LE_READ16(&header[8])) { + case 0: extract = Zip_copy; break; + case 8: extract = Zip_inflate; break; + } + + if (!extract || extract(zip,dst,compressed_size)) { + game.tmp_path[0] = '\0'; + LOG_error("Error extracting file: %s\n\t%s\n", filename, strerror(errno)); + return; + } + + fclose(dst); + + break; + } + + fclose(zip); + } + } + // some cores handle opening files themselves, eg. pcsx_rearmed // if the frontend tries to load a 500MB file itself bad things happen if (!core.need_fullpath) { - FILE *file = fopen(game.path, "r"); + path = game.tmp_path[0]=='\0'?game.path:game.tmp_path; + + FILE *file = fopen(path, "r"); if (file==NULL) { - LOG_error("Error opening game: %s\n\t%s\n", game.path, strerror(errno)); + LOG_error("Error opening game: %s\n\t%s\n", path, strerror(errno)); return; } @@ -161,7 +341,7 @@ static void Game_open(char* path) { rewind(file); game.data = malloc(game.size); if (game.data==NULL) { - LOG_error("Couldn't allocate memory for file: %s\n", game.path); + LOG_error("Couldn't allocate memory for file: %s\n", path); return; } @@ -203,6 +383,7 @@ static void Game_open(char* path) { } static void Game_close(void) { if (game.data) free(game.data); + if (game.tmp_path) remove(game.tmp_path); game.is_open = 0; VIB_setStrength(0); // just in case } @@ -2600,6 +2781,7 @@ void Core_open(const char* core_path, const char* tag_name) { Core_getName((char*)core_path, (char*)core.name); sprintf((char*)core.version, "%s (%s)", info.library_name, info.library_version); strcpy((char*)core.tag, tag_name); + strcpy((char*)core.extensions, info.valid_extensions); core.need_fullpath = info.need_fullpath; @@ -2772,7 +2954,7 @@ enum { MENU_CALLBACK_EXIT, MENU_CALLBACK_NEXT_ITEM, }; -typedef int(*MenuList_callback_t)(MenuList* list, int i); +typedef int (*MenuList_callback_t)(MenuList* list, int i); typedef struct MenuItem { char* name; char* desc; diff --git a/src/minarch/makefile b/src/minarch/makefile index 78e27da..b8ecb1c 100644 --- a/src/minarch/makefile +++ b/src/minarch/makefile @@ -7,7 +7,7 @@ TARGET = minarch.elf CC = $(CROSS_COMPILE)gcc CFLAGS = -marm -mtune=cortex-a9 -mfpu=neon-fp16 -mfloat-abi=hard -march=armv7-a -fomit-frame-pointer CFLAGS += -I. -I../common -I./libretro-common/include -DPLATFORM=\"$(UNION_PLATFORM)\" -Ofast -LDFLAGS = -ldl -lSDL -lSDL_image -lSDL_ttf -lmsettings -lpthread +LDFLAGS = -ldl -lSDL -lSDL_image -lSDL_ttf -lmsettings -lpthread -lz # CFLAGS += -Wall -Wno-unused-variable -Wno-unused-function # CFLAGS += -fsanitize=address -fno-common # LDFLAGS += -lasan diff --git a/todo.txt b/todo.txt index d2037ff..1638ed3 100644 --- a/todo.txt +++ b/todo.txt @@ -18,6 +18,11 @@ Please see the README.txt in the zip file for installation and update instructio ------------------------------- +add additional info to installation readme + TF1 should have stock installed (Garlic will work too) + it will have 4 partitions, 2 hidden Linux partitions plus MISC and ROMS + TF2 should be formatted FAT32 + create a clean image to flash and install over advanced users seem to be confused about how simple it is to install :sweat_smile: @@ -31,6 +36,7 @@ hdmi audio minarch + zip support figure out how to handle different button mappings for cores with different modes eg. GG or SMS with picodrive move overrides to a text file that lives in the pak? @@ -50,6 +56,7 @@ minui misc port say, show, blank, and confirm make my own simple file browser? :sweat_smile: + port Random Game.pak + daemon checkout eggs tools I wonder if I could patch in Commander-11, BPreplayBold is too bold at that size