added zipped rom support
This commit is contained in:
parent
127c31bcf4
commit
f196202ce3
3 changed files with 194 additions and 5 deletions
|
|
@ -13,6 +13,7 @@
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <zlib.h>
|
||||||
|
|
||||||
#include "libretro.h"
|
#include "libretro.h"
|
||||||
#include "defines.h"
|
#include "defines.h"
|
||||||
|
|
@ -96,6 +97,7 @@ static struct Core {
|
||||||
const char tag[8]; // eg. GBC
|
const char tag[8]; // eg. GBC
|
||||||
const char name[128]; // eg. gambatte
|
const char name[128]; // eg. gambatte
|
||||||
const char version[128]; // eg. Gambatte (v0.5.0-netlink 7e02df6)
|
const char version[128]; // eg. Gambatte (v0.5.0-netlink 7e02df6)
|
||||||
|
const char extensions[128]; // eg. gb|gbc|dmg
|
||||||
|
|
||||||
CoreOverrides* overrides;
|
CoreOverrides* overrides;
|
||||||
|
|
||||||
|
|
@ -130,12 +132,89 @@ static struct Core {
|
||||||
// retro_audio_buffer_status_callback_t audio_buffer_status;
|
// retro_audio_buffer_status_callback_t audio_buffer_status;
|
||||||
} core;
|
} 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 {
|
static struct Game {
|
||||||
char path[MAX_PATH];
|
char path[MAX_PATH];
|
||||||
char name[MAX_PATH]; // TODO: rename to basename?
|
char name[MAX_PATH]; // TODO: rename to basename?
|
||||||
char m3u_path[MAX_PATH];
|
char m3u_path[MAX_PATH];
|
||||||
|
char tmp_path[MAX_PATH]; // location of unzipped file
|
||||||
void* data;
|
void* data;
|
||||||
size_t size;
|
size_t size;
|
||||||
int is_open;
|
int is_open;
|
||||||
|
|
@ -146,12 +225,113 @@ static void Game_open(char* path) {
|
||||||
strcpy((char*)game.path, path);
|
strcpy((char*)game.path, path);
|
||||||
strcpy((char*)game.name, strrchr(path, '/')+1);
|
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
|
// some cores handle opening files themselves, eg. pcsx_rearmed
|
||||||
// if the frontend tries to load a 500MB file itself bad things happen
|
// if the frontend tries to load a 500MB file itself bad things happen
|
||||||
if (!core.need_fullpath) {
|
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) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -161,7 +341,7 @@ static void Game_open(char* path) {
|
||||||
rewind(file);
|
rewind(file);
|
||||||
game.data = malloc(game.size);
|
game.data = malloc(game.size);
|
||||||
if (game.data==NULL) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -203,6 +383,7 @@ static void Game_open(char* path) {
|
||||||
}
|
}
|
||||||
static void Game_close(void) {
|
static void Game_close(void) {
|
||||||
if (game.data) free(game.data);
|
if (game.data) free(game.data);
|
||||||
|
if (game.tmp_path) remove(game.tmp_path);
|
||||||
game.is_open = 0;
|
game.is_open = 0;
|
||||||
VIB_setStrength(0); // just in case
|
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);
|
Core_getName((char*)core_path, (char*)core.name);
|
||||||
sprintf((char*)core.version, "%s (%s)", info.library_name, info.library_version);
|
sprintf((char*)core.version, "%s (%s)", info.library_name, info.library_version);
|
||||||
strcpy((char*)core.tag, tag_name);
|
strcpy((char*)core.tag, tag_name);
|
||||||
|
strcpy((char*)core.extensions, info.valid_extensions);
|
||||||
|
|
||||||
core.need_fullpath = info.need_fullpath;
|
core.need_fullpath = info.need_fullpath;
|
||||||
|
|
||||||
|
|
@ -2772,7 +2954,7 @@ enum {
|
||||||
MENU_CALLBACK_EXIT,
|
MENU_CALLBACK_EXIT,
|
||||||
MENU_CALLBACK_NEXT_ITEM,
|
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 {
|
typedef struct MenuItem {
|
||||||
char* name;
|
char* name;
|
||||||
char* desc;
|
char* desc;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ TARGET = minarch.elf
|
||||||
CC = $(CROSS_COMPILE)gcc
|
CC = $(CROSS_COMPILE)gcc
|
||||||
CFLAGS = -marm -mtune=cortex-a9 -mfpu=neon-fp16 -mfloat-abi=hard -march=armv7-a -fomit-frame-pointer
|
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
|
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 += -Wall -Wno-unused-variable -Wno-unused-function
|
||||||
# CFLAGS += -fsanitize=address -fno-common
|
# CFLAGS += -fsanitize=address -fno-common
|
||||||
# LDFLAGS += -lasan
|
# LDFLAGS += -lasan
|
||||||
|
|
|
||||||
7
todo.txt
7
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
|
create a clean image to flash and install over
|
||||||
advanced users seem to be confused about how simple it is to install :sweat_smile:
|
advanced users seem to be confused about how simple it is to install :sweat_smile:
|
||||||
|
|
||||||
|
|
@ -31,6 +36,7 @@ hdmi
|
||||||
audio
|
audio
|
||||||
|
|
||||||
minarch
|
minarch
|
||||||
|
zip support
|
||||||
figure out how to handle different button mappings for cores with different modes
|
figure out how to handle different button mappings for cores with different modes
|
||||||
eg. GG or SMS with picodrive
|
eg. GG or SMS with picodrive
|
||||||
move overrides to a text file that lives in the pak?
|
move overrides to a text file that lives in the pak?
|
||||||
|
|
@ -50,6 +56,7 @@ minui
|
||||||
misc
|
misc
|
||||||
port say, show, blank, and confirm
|
port say, show, blank, and confirm
|
||||||
make my own simple file browser? :sweat_smile:
|
make my own simple file browser? :sweat_smile:
|
||||||
|
port Random Game.pak + daemon
|
||||||
|
|
||||||
checkout eggs tools
|
checkout eggs tools
|
||||||
I wonder if I could patch in Commander-11, BPreplayBold is too bold at that size
|
I wonder if I could patch in Commander-11, BPreplayBold is too bold at that size
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue