230 lines
5 KiB
C
230 lines
5 KiB
C
/*
|
|
* This software is licensed under the terms of the MIT License.
|
|
* See COPYING for further information.
|
|
* ---
|
|
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
|
|
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
|
|
*/
|
|
|
|
#include "taisei.h"
|
|
|
|
#include "rwops_zipfile.h"
|
|
#include "rwops_util.h"
|
|
#include "rwops_zstd.h"
|
|
|
|
#include "util.h"
|
|
#include "util/libzip_compat.h"
|
|
|
|
#define FORCE_MANUAL_DECOMPRESSION (0)
|
|
|
|
#if FORCE_MANUAL_DECOMPRESSION
|
|
#include "rwops_zlib.h"
|
|
#endif
|
|
|
|
typedef struct ZipRWData {
|
|
VFSNode *node;
|
|
zip_file_t *file;
|
|
int64_t pos;
|
|
int64_t size;
|
|
zip_flags_t open_flags;
|
|
} ZipRWData;
|
|
|
|
#define ZTLS(pdata) vfs_zipfile_get_tls((pdata)->zipnode, true)
|
|
|
|
static int ziprw_reopen(SDL_RWops *rw) {
|
|
ZipRWData *rwdata = rw->hidden.unknown.data1;
|
|
VFSZipPathData *pdata = rw->hidden.unknown.data2;
|
|
|
|
if(rwdata->file) {
|
|
zip_fclose(rwdata->file);
|
|
}
|
|
|
|
rwdata->file = zip_fopen_index(ZTLS(pdata)->zip, pdata->index, rwdata->open_flags);
|
|
rwdata->pos = 0;
|
|
|
|
return rwdata->file ? 0 : -1;
|
|
}
|
|
|
|
static zip_file_t *ziprw_get_zipfile(SDL_RWops *rw) {
|
|
ZipRWData *rwdata = rw->hidden.unknown.data1;
|
|
VFSZipPathData *pdata = rw->hidden.unknown.data2;
|
|
|
|
int64_t pos = rwdata->pos;
|
|
|
|
if(!vfs_zipfile_get_tls((pdata)->zipnode, false) && rwdata->file) {
|
|
zip_fclose(rwdata->file);
|
|
rwdata->file = NULL;
|
|
}
|
|
|
|
if(!rwdata->file && ziprw_reopen(rw) >= 0) {
|
|
SDL_RWseek(rw, pos, RW_SEEK_SET);
|
|
}
|
|
|
|
assert(rwdata->file);
|
|
return rwdata->file;
|
|
}
|
|
|
|
static int ziprw_close(SDL_RWops *rw) {
|
|
if(rw) {
|
|
ZipRWData *rwdata = rw->hidden.unknown.data1;
|
|
|
|
if(rwdata->file) {
|
|
zip_fclose(rwdata->file);
|
|
}
|
|
|
|
vfs_decref(rwdata->node);
|
|
free(rwdata);
|
|
SDL_FreeRW(rw);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int64_t ziprw_seek(SDL_RWops *rw, int64_t offset, int whence) {
|
|
ZipRWData *rwdata = rw->hidden.unknown.data1;
|
|
|
|
if(!ziprw_get_zipfile(rw)) {
|
|
return -1;
|
|
}
|
|
|
|
if(zip_fseek(rwdata->file, offset, whence) == 0) {
|
|
return rwdata->pos = zip_ftell(rwdata->file);
|
|
}
|
|
|
|
SDL_SetError("ZIP error: %s", zip_error_strerror(zip_file_get_error(rwdata->file)));
|
|
return -1;
|
|
}
|
|
|
|
static int64_t ziprw_seek_emulated(SDL_RWops *rw, int64_t offset, int whence) {
|
|
ZipRWData *rwdata = rw->hidden.unknown.data1;
|
|
|
|
if(!ziprw_get_zipfile(rw)) {
|
|
return -1;
|
|
}
|
|
|
|
char buf[1024];
|
|
|
|
return rwutil_seek_emulated(
|
|
rw, offset, whence,
|
|
&rwdata->pos, ziprw_reopen, sizeof(buf), buf
|
|
);
|
|
}
|
|
|
|
static int64_t ziprw_size(SDL_RWops *rw) {
|
|
ZipRWData *rwdata = rw->hidden.unknown.data1;
|
|
|
|
if(rwdata->size < 0) {
|
|
return SDL_SetError("zip_stat_index() failed");
|
|
}
|
|
|
|
return rwdata->size;
|
|
}
|
|
|
|
static size_t ziprw_read(SDL_RWops *rw, void *ptr, size_t size, size_t maxnum) {
|
|
ZipRWData *rwdata = rw->hidden.unknown.data1;
|
|
|
|
libzip_sucks:
|
|
|
|
if(UNLIKELY(!ziprw_get_zipfile(rw))) {
|
|
return 0;
|
|
}
|
|
|
|
size_t read_size = size * maxnum;
|
|
|
|
if(UNLIKELY(read_size == 0)) {
|
|
return 0;
|
|
}
|
|
|
|
if(UNLIKELY(read_size / size != maxnum)) {
|
|
SDL_SetError("Read size is too large");
|
|
return 0;
|
|
}
|
|
|
|
zip_int64_t bytes_read = zip_fread(rwdata->file, ptr, read_size);
|
|
|
|
if(UNLIKELY(bytes_read < 0)) {
|
|
SDL_SetError("ZIP error: %s", zip_error_strerror(zip_file_get_error(rwdata->file)));
|
|
log_debug("ZIP error: %s", zip_error_strerror(zip_file_get_error(rwdata->file)));
|
|
return 0;
|
|
}
|
|
|
|
if(read_size > 0 && bytes_read == 0 && rw->seek == ziprw_seek && zip_ftell(rwdata->file) < rwdata->size) {
|
|
log_debug("libzip BUG: EOF flag not cleared after seek, reopening file");
|
|
zip_fclose(rwdata->file);
|
|
rwdata->file = NULL;
|
|
goto libzip_sucks;
|
|
}
|
|
|
|
rwdata->pos += bytes_read;
|
|
return bytes_read / size;
|
|
}
|
|
|
|
static size_t ziprw_write(SDL_RWops *rw, const void *ptr, size_t size, size_t maxnum) {
|
|
SDL_SetError("Not implemented");
|
|
return -1;
|
|
}
|
|
|
|
SDL_RWops *SDL_RWFromZipFile(VFSNode *znode, VFSZipPathData *pdata) {
|
|
SDL_RWops *rw = SDL_AllocRW();
|
|
|
|
if(UNLIKELY(!rw)) {
|
|
return NULL;
|
|
}
|
|
|
|
memset(rw, 0, sizeof(SDL_RWops));
|
|
|
|
ZipRWData *rwdata = calloc(1, sizeof(*rwdata));
|
|
rwdata->node = znode;
|
|
rwdata->size = pdata->size;
|
|
|
|
vfs_incref(znode);
|
|
|
|
rw->hidden.unknown.data1 = rwdata;
|
|
rw->hidden.unknown.data2 = pdata;
|
|
rw->type = SDL_RWOPS_UNKNOWN;
|
|
|
|
rw->size = ziprw_size;
|
|
rw->close = ziprw_close;
|
|
rw->read = ziprw_read;
|
|
rw->write = ziprw_write;
|
|
|
|
DIAGNOSTIC(push)
|
|
DIAGNOSTIC(ignored "-Wunreachable-code")
|
|
|
|
if(pdata->compression == ZIP_CM_STORE) {
|
|
rw->seek = ziprw_seek;
|
|
} else if(
|
|
!FORCE_MANUAL_DECOMPRESSION &&
|
|
zip_compression_method_supported(pdata->compression, false)
|
|
) {
|
|
rw->seek = ziprw_seek_emulated;
|
|
} else {
|
|
rw->seek = ziprw_seek;
|
|
rwdata->size = pdata->compressed_size;
|
|
rwdata->open_flags = ZIP_FL_COMPRESSED;
|
|
|
|
switch(pdata->compression) {
|
|
#if FORCE_MANUAL_DECOMPRESSION
|
|
case ZIP_CM_DEFLATE:
|
|
rw = SDL_RWWrapInflateReaderSeekable(rw, pdata->size, imin(4096, pdata->size), true);
|
|
break;
|
|
#endif
|
|
|
|
case ZIP_CM_ZSTD: {
|
|
rw = SDL_RWWrapZstdReaderSeekable(rw, pdata->size, true);
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
char *fname = vfs_node_repr(znode, true);
|
|
SDL_SetError("%s: unsupported compression method: %i", fname, pdata->compression);
|
|
SDL_RWclose(rw);
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
DIAGNOSTIC(pop)
|
|
|
|
return rw;
|
|
}
|