taisei/src/rwops/rwops_zlib.c
2024-05-17 14:11:48 +02:00

434 lines
9.4 KiB
C

/*
* This software is licensed under the terms of the MIT License.
* See COPYING for further information.
* ---
* Copyright (c) 2011-2024, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2024, Andrei Alexeyev <akari@taisei-project.org>.
*/
#include "rwops_zlib.h"
#include "log.h"
#include "rwops_util.h"
#include "util/miscmath.h"
#include <SDL.h>
#include <zlib.h>
#define MIN_CHUNK_SIZE 1024
#define ZDATA(rw) ((ZData*)((rw)->hidden.unknown.data1))
#define TYPENAME(rw) (ZDATA(rw)->type == TYPE_DEFLATE ? "a deflate" : "an inflate")
// #define TEST_ZRWOPS
// #define DEBUG_ZRWOPS
#ifdef TEST_ZRWOPS
#define DEBUG_ZRWOPS
#endif
#ifdef DEBUG_ZRWOPS
#define PRINT(...) tsfprintf(stdout, __VA_ARGS__)
#else
#define PRINT(...)
#endif
typedef struct ZData {
uint8_t *buffer;
uint8_t *buffer_ptr;
size_t buffer_size;
union {
// deflate
struct {
uint8_t *buffer_aux;
size_t buffer_aux_size;
};
// inflate
struct {
size_t buffer_fillsize;
int64_t uncompressed_size;
};
};
enum {
TYPE_DEFLATE,
TYPE_INFLATE,
} type;
int window_bits;
int64_t pos;
z_stream *stream;
SDL_RWops *wrapped;
bool autoclose;
} ZData;
static int64_t common_seek(SDL_RWops *rw, int64_t offset, int whence) {
if(!offset && whence == RW_SEEK_CUR) {
return ZDATA(rw)->pos;
}
return SDL_SetError("Can't seek in %s stream", TYPENAME(rw));
}
static int64_t common_size(SDL_RWops *rw) {
ZData *z = ZDATA(rw);
if(z->type == TYPE_INFLATE && z->uncompressed_size >= 0) {
return z->uncompressed_size;
}
return SDL_SetError("Can't get size of %s stream", TYPENAME(rw));
}
static void deflate_flush(ZData *z);
static int common_close(SDL_RWops *rw) {
PRINT("common_close\n");
if(rw) {
ZData *z = ZDATA(rw);
PRINT("T = %i\n", z->type);
if(z->type == TYPE_DEFLATE) {
deflate_flush(z);
deflateEnd(z->stream);
mem_free(z->buffer_aux);
} else {
inflateEnd(z->stream);
}
mem_free(z->buffer);
mem_free(z->stream);
if(z->autoclose) {
SDL_RWclose(z->wrapped);
}
mem_free(z);
SDL_FreeRW(rw);
}
return 0;
}
static void *zlib_alloc(void *opaque, unsigned int items, unsigned int size) {
return mem_alloc_array(items, size);
}
static void zlib_free(void *opaque, void *address) {
return mem_free(address);
}
static SDL_RWops *common_alloc(SDL_RWops *wrapped, size_t bufsize, bool autoclose) {
SDL_RWops *rw = SDL_AllocRW();
if(!rw) {
return NULL;
}
if(bufsize < MIN_CHUNK_SIZE) {
bufsize = MIN_CHUNK_SIZE;
}
memset(rw, 0, sizeof(SDL_RWops));
rw->type = SDL_RWOPS_UNKNOWN;
rw->size = common_size;
rw->seek = common_seek;
rw->close = common_close;
auto z = ALLOC(ZData, {
.buffer_size = bufsize,
.wrapped = wrapped,
.autoclose = autoclose,
});
z->stream = ALLOC(z_stream, {
.zalloc = zlib_alloc,
.zfree = zlib_free,
});
z->buffer = z->buffer_ptr = mem_alloc(bufsize);
rw->hidden.unknown.data1 = z;
return rw;
}
#ifdef DEBUG_ZRWOPS
static void printbuf(void *buf, size_t size) {
tsfprintf(stdout, "[ ");
for(uint i = 0; i < size; ++i) {
tsfprintf(stdout, "%02x ", ((uint8_t*)buf)[i]);
}
tsfprintf(stdout, "]\n");
}
#else
#define printbuf(buf,size)
#endif
static size_t deflate_read(SDL_RWops *rw, void *ptr, size_t size, size_t maxnum) {
SDL_SetError("Can't read from a deflate stream");
return 0;
}
static void deflate_flush(ZData *z) {
size_t totalsize = z->buffer_ptr - z->buffer;
PRINT("deflate_flush: %lu\n", totalsize);
if(!totalsize) {
return;
}
z->stream->next_in = z->buffer;
z->stream->avail_in = totalsize;
z->stream->next_out = z->buffer_aux;
z->stream->avail_out = z->buffer_aux_size;
int ret = Z_OK;
while(ret >= 0) {
PRINT("deflate start: (%p %i %p %i %lu)\n", (void*)z->stream->next_in, z->stream->avail_in, (void*)z->stream->next_out, z->stream->avail_out, totalsize);
ret = deflate(z->stream, Z_SYNC_FLUSH);
PRINT("deflate end: %i (%p %i %p %i %lu)\n", ret, (void*)z->stream->next_in, z->stream->avail_in, (void*)z->stream->next_out, z->stream->avail_out, totalsize);
if(!z->stream->avail_out) {
SDL_RWwrite(z->wrapped, z->buffer_aux, 1, z->buffer_aux_size);
z->stream->next_out = z->buffer_aux;
z->stream->avail_out = z->buffer_aux_size;
}
}
SDL_RWwrite(z->wrapped, z->buffer_aux, 1, z->buffer_aux_size - z->stream->avail_out);
z->buffer_ptr = z->buffer;
PRINT("---\n");
}
static size_t deflate_write(SDL_RWops *rw, const void *ptr, size_t size, size_t maxnum) {
ZData *z = ZDATA(rw);
size_t totalsize = size * maxnum;
size_t available;
size_t remaining = totalsize;
size_t offset = 0;
while(remaining) {
available = z->buffer_size - (z->buffer_ptr - z->buffer);
size_t copysize = remaining > available ? available : remaining;
PRINT("avail = %lu; copysize = %lu\n", available, copysize);
if(available) {
remaining -= copysize;
memcpy(z->buffer_ptr, (uint8_t*)ptr + offset, copysize);
printbuf(z->buffer_ptr, copysize);
offset += copysize;
z->buffer_ptr += copysize;
z->pos += copysize;
} else {
deflate_flush(z);
}
}
return maxnum;
}
static size_t inflate_read(SDL_RWops *rw, void *ptr, size_t size, size_t maxnum) {
ZData *z = ZDATA(rw);
size_t totalsize = size * maxnum;
int ret = Z_OK;
if(!totalsize) {
return 0;
}
z->stream->avail_out = totalsize;
z->stream->next_out = ptr;
PRINT("inflate_read()\n");
while(z->stream->avail_out && ret != Z_STREAM_END) {
z->stream->avail_in = z->buffer_fillsize - (z->buffer_ptr - z->buffer);
if(!z->stream->avail_in) {
z->buffer_fillsize = SDL_RWread(z->wrapped, z->buffer, 1, z->buffer_size);
z->buffer_ptr = z->buffer;
z->stream->avail_in = z->buffer_fillsize - (z->buffer_ptr - z->buffer);
}
z->stream->next_in = z->buffer_ptr;
PRINT(" -- begin read %i --- \n", z->stream->avail_in);
printbuf(z->stream->next_in, z->stream->avail_in);
PRINT(" -- end read --- \n");
switch(ret = inflate(z->stream, Z_NO_FLUSH)) {
case Z_OK:
case Z_STREAM_END:
PRINT("read ok\n");
break;
default:
PRINT("inflate error: %i\n", ret);
SDL_SetError("inflate error: %i", ret);
log_debug("%s", SDL_GetError());
ret = Z_STREAM_END;
break;
}
z->buffer_ptr = z->stream->next_in;
}
z->pos += (totalsize - z->stream->avail_out);
return (totalsize - z->stream->avail_out) / size;
}
static size_t inflate_write(SDL_RWops *rw, const void *ptr, size_t size, size_t maxnum) {
SDL_SetError("Can't write to an inflate stream");
return 0;
}
static SDL_RWops *wrap_writer(
SDL_RWops *src, size_t bufsize, bool autoclose,
int clevel, int window_bits
) {
if(UNLIKELY(!src)) {
return NULL;
}
SDL_RWops *rw = common_alloc(src, bufsize, autoclose);
if(UNLIKELY(!rw)) {
return NULL;
}
rw->read = deflate_read;
rw->write = deflate_write;
ZData *z = ZDATA(rw);
z->type = TYPE_DEFLATE;
z->buffer_aux_size = z->buffer_size;
z->buffer_aux = mem_alloc(z->buffer_aux_size);
z->window_bits = window_bits;
if(clevel >= 0) {
clevel = clamp(clevel, Z_BEST_SPEED, Z_BEST_COMPRESSION);
}
int status = deflateInit2(
z->stream,
clevel,
Z_DEFLATED,
window_bits,
8,
Z_DEFAULT_STRATEGY
);
if(status != Z_OK) {
SDL_SetError("deflateInit2() failed: %i", status);
SDL_RWclose(rw);
return NULL;
}
return rw;
}
static int inflate_reopen(SDL_RWops *rw) {
ZData *z = ZDATA(rw);
int64_t srcpos = SDL_RWseek(z->wrapped, 0, RW_SEEK_SET);
if(srcpos < 0) {
return srcpos;
}
assert(srcpos == 0);
z->pos = 0;
z->buffer_ptr = z->buffer;
z->buffer_fillsize = 0;
int status = inflateReset2(z->stream, z->window_bits);
if(status != Z_OK) {
return SDL_SetError("inflateReset2() failed: %i", status);
}
return 0;
}
static int64_t inflate_seek_emulated(SDL_RWops *rw, int64_t offset, int whence) {
ZData *z = ZDATA(rw);
char buf[1024];
return rwutil_seek_emulated(
rw, offset, whence,
&z->pos, inflate_reopen, sizeof(buf), buf
);
}
static SDL_RWops *wrap_reader(
SDL_RWops *src, size_t bufsize, bool autoclose,
int64_t uncompressed_size, int window_bits, bool emulate_seek
) {
if(UNLIKELY(!src)) {
return NULL;
}
SDL_RWops *rw = common_alloc(src, bufsize, autoclose);
if(UNLIKELY(!rw)) {
return NULL;
}
rw->read = inflate_read;
rw->write = inflate_write;
ZData *z = ZDATA(rw);
z->type = TYPE_INFLATE;
z->window_bits = window_bits;
z->uncompressed_size = uncompressed_size;
int status = inflateInit2(z->stream, window_bits);
if(UNLIKELY(status != Z_OK)) {
SDL_SetError("inflateInit2() failed: %i", status);
SDL_RWclose(rw);
return NULL;
}
if(emulate_seek) {
rw->seek = inflate_seek_emulated;
}
return rw;
}
SDL_RWops *SDL_RWWrapZlibReader(SDL_RWops *src, size_t bufsize, bool autoclose) {
return wrap_reader(src, bufsize, autoclose, -1, 15, false);
}
SDL_RWops *SDL_RWWrapZlibReaderSeekable(SDL_RWops *src, int64_t uncompressed_size, size_t bufsize, bool autoclose) {
return wrap_reader(src, bufsize, autoclose, uncompressed_size, 15, true);
}
SDL_RWops *SDL_RWWrapInflateReader(SDL_RWops *src, size_t bufsize, bool autoclose) {
return wrap_reader(src, bufsize, autoclose, -1, 15, false);
}
SDL_RWops *SDL_RWWrapInflateReaderSeekable(SDL_RWops *src, int64_t uncompressed_size, size_t bufsize, bool autoclose) {
return wrap_reader(src, bufsize, autoclose, uncompressed_size, -15, true);
}
SDL_RWops *SDL_RWWrapZlibWriter(SDL_RWops *src, int clevel, size_t bufsize, bool autoclose) {
return wrap_writer(src, bufsize, autoclose, clevel, 15);
}
SDL_RWops *SDL_RWWrapDeflateWriter(SDL_RWops *src, int clevel, size_t bufsize, bool autoclose) {
return wrap_writer(src, bufsize, autoclose, clevel, -15);
}