added zipped rom support

This commit is contained in:
Shaun Inman 2023-03-03 22:24:21 -05:00
parent 127c31bcf4
commit f196202ce3
3 changed files with 194 additions and 5 deletions

View file

@ -13,6 +13,7 @@
#include <time.h>
#include <sys/stat.h>
#include <errno.h>
#include <zlib.h>
#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;

View file

@ -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

View file

@ -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