rwops: Add zstd compression stream wrappers

This commit is contained in:
Andrei Alexeyev 2021-03-22 17:44:07 +02:00
parent 144bcfe647
commit f2b012aed1
No known key found for this signature in database
GPG key ID: 72D26128040B9690
4 changed files with 357 additions and 0 deletions

View file

@ -17,6 +17,7 @@
#include "rwops_segment.h"
#include "rwops_sha256.h"
#include "rwops_zlib.h"
#include "rwops_zstd.h"
#ifdef TAISEI_BUILDCONF_USE_ZIP
#include "rwops_zipfile.h"

View file

@ -7,6 +7,7 @@ rwops_src = files(
'rwops_segment.c',
'rwops_sha256.c',
'rwops_zlib.c',
'rwops_zstd.c',
)
if taisei_deps.contains(dep_zip)

334
src/rwops/rwops_zstd.c Normal file
View file

@ -0,0 +1,334 @@
/*
* 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_zstd.h"
#include "util.h"
#include <zstd.h>
typedef struct ZstdData {
SDL_RWops *wrapped;
size_t pos;
union {
struct {
ZSTD_CStream *stream;
ZSTD_inBuffer in_buffer;
size_t in_buffer_alloc_size;
ZSTD_outBuffer out_buffer;
} writer;
struct {
ZSTD_DStream *stream;
ZSTD_inBuffer in_buffer;
size_t in_buffer_alloc_size;
size_t next_read_size;
} reader;
};
bool autoclose;
} ZstdData;
#define ZDATA(rw) NOT_NULL((ZstdData*)(NOT_NULL(rw)->hidden.unknown.data1))
static int64_t rwzstd_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 a zstd stream");
}
static int64_t rwzstd_size(SDL_RWops *rw) {
return SDL_SetError("Can't get size of a zstd stream");
}
static int rwzstd_close(SDL_RWops *rw) {
ZstdData *z = ZDATA(rw);
if(z->autoclose) {
SDL_RWclose(z->wrapped);
}
free(z);
SDL_FreeRW(rw);
return 0;
}
static size_t rwzstd_read_invalid(SDL_RWops *rw, void *ptr, size_t size, size_t maxnum) {
SDL_SetError("Can't read from a zstd writer stream");
return 0;
}
static size_t rwzstd_write_invalid(SDL_RWops *rw, const void *ptr, size_t size, size_t maxnum) {
SDL_SetError("Can't write to a zstd reader stream");
return 0;
}
static SDL_RWops *rwzstd_alloc(SDL_RWops *wrapped, bool autoclose) {
SDL_RWops *rw = SDL_AllocRW();
if(!rw) {
return NULL;
}
memset(rw, 0, sizeof(SDL_RWops));
rw->type = SDL_RWOPS_UNKNOWN;
rw->seek = rwzstd_seek;
rw->size = rwzstd_size;
ZstdData *z = calloc(1, sizeof(ZstdData));
z->wrapped = wrapped;
z->autoclose = autoclose;
rw->hidden.unknown.data1 = z;
return rw;
}
static size_t rwzstd_reader_fill_in_buffer(ZstdData *z, size_t request_size) {
ZSTD_inBuffer *in = &z->reader.in_buffer;
if(request_size > z->reader.in_buffer_alloc_size) {
z->reader.in_buffer_alloc_size = request_size;
in->src = realloc((void*)in->src, request_size);
}
if(request_size == 0) {
return 0;
}
size_t leftover_size = in->size - in->pos;
in->size = leftover_size;
assert(leftover_size <= request_size);
uint8_t *root = (uint8_t*)in->src;
if(UNLIKELY(leftover_size)) {
// some input has not been consumed; we must present it again
uint8_t *leftover = root + in->pos;
memmove(root, leftover, leftover_size);
}
in->pos = 0;
uint8_t *start = root + in->size;
uint8_t *end = root + request_size;
uint8_t *pos = start;
while(in->size < request_size) {
size_t read = SDL_RWread(z->wrapped, pos, 1, end - pos);
if(read == 0) {
break;
}
assert(read <= end - pos);
pos += read;
in->size += read;
}
assert(in->size <= z->reader.in_buffer_alloc_size);
return pos - start;
}
static size_t rwzstd_read(SDL_RWops *rw, void *ptr, size_t size, size_t maxnum) {
ZstdData *z = ZDATA(rw);
ZSTD_inBuffer *in = &z->reader.in_buffer;
ZSTD_outBuffer out = {
.dst = ptr,
.size = size * maxnum,
};
ZSTD_DStream *stream = NOT_NULL(z->reader.stream);
while(out.pos < out.size) {
if(in->size - in->pos < z->reader.next_read_size) {
rwzstd_reader_fill_in_buffer(z, z->reader.next_read_size);
if(in->size - in->pos < z->reader.next_read_size) {
// end of stream
break;
}
}
size_t status = ZSTD_decompressStream(stream, &out, in);
if(UNLIKELY(ZSTD_isError(status))) {
SDL_SetError("ZSTD_decompressStream() failed: %s", ZSTD_getErrorName(status));
log_debug("%s", SDL_GetError());
return 0;
}
z->reader.next_read_size = status;
}
z->pos += out.pos;
return out.pos / size;
}
static int rwzstd_reader_close(SDL_RWops *rw) {
ZstdData *z = ZDATA(rw);
ZSTD_freeDStream(z->reader.stream);
free((void*)z->reader.in_buffer.src);
return rwzstd_close(rw);
}
SDL_RWops *SDL_RWWrapZstdReader(SDL_RWops *src, bool autoclose) {
if(!src) {
return NULL;
}
SDL_RWops *rw = rwzstd_alloc(src, autoclose);
if(!rw) {
return NULL;
}
rw->write = rwzstd_write_invalid;
rw->read = rwzstd_read;
rw->close = rwzstd_reader_close;
ZstdData *z = ZDATA(rw);
z->reader.stream = NOT_NULL(ZSTD_createDStream());
z->reader.next_read_size = ZSTD_initDStream(z->reader.stream);
z->reader.in_buffer_alloc_size = imax(z->reader.next_read_size, 16384);
z->reader.in_buffer.src = calloc(1, z->reader.in_buffer_alloc_size);
z->reader.next_read_size = z->reader.in_buffer_alloc_size;
return rw;
}
static bool rwzstd_compress(SDL_RWops *rw, ZSTD_EndDirective edir, size_t *status) {
ZstdData *z = ZDATA(rw);
ZSTD_outBuffer *out = &z->writer.out_buffer;
ZSTD_inBuffer *in = &z->reader.in_buffer;
*status = ZSTD_compressStream2(z->writer.stream, out, in, edir);
if(UNLIKELY(ZSTD_isError(*status))) {
SDL_SetError("ZSTD_compressStream2() failed: %s", ZSTD_getErrorName(*status));
log_debug("%s", SDL_GetError());
return false;
}
if(out->pos > 0) {
size_t written = SDL_RWwrite(z->wrapped, out->dst, 1, out->pos);
if(UNLIKELY(written != out->pos)) {
SDL_SetError("SDL_RWwrite() returned %zi; expected %zi. Error: %s", written, out->pos, SDL_GetError());
log_debug("%s", SDL_GetError());
return false;
}
out->pos = 0;
}
return true;
}
static size_t rwzstd_write(SDL_RWops *rw, const void *data_in, size_t size, size_t maxnum) {
ZstdData *z = ZDATA(rw);
size_t wsize = size * maxnum;
ZSTD_inBuffer *in = &z->writer.in_buffer;
uint8_t *in_root = (uint8_t*)in->src;
assert(in->pos <= in->size);
if(in->pos > 0) {
in->size -= in->pos;
memmove(in_root, in_root + in->pos, in->size);
in->pos = 0;
}
size_t in_free = z->writer.in_buffer_alloc_size - in->size;
if(UNLIKELY(in_free < wsize)) {
in->src = realloc(in_root, z->writer.in_buffer_alloc_size + (wsize - in_free));
}
memcpy(in_root + in->size, data_in, wsize);
in->size += wsize;
z->pos += wsize;
size_t status;
if(LIKELY(rwzstd_compress(rw, ZSTD_e_continue, &status))) {
return maxnum;
}
return 0;
}
static bool rwzstd_writer_flush(SDL_RWops *rw, ZSTD_EndDirective edir) {
size_t status;
do {
if(UNLIKELY(!rwzstd_compress(rw, edir, &status))) {
return false;
}
} while(status != 0);
return true;
}
static int rwzstd_writer_close(SDL_RWops *rw) {
ZstdData *z = ZDATA(rw);
bool flush_ok = rwzstd_writer_flush(rw, ZSTD_e_end);
ZSTD_freeCStream(z->writer.stream);
free(z->writer.out_buffer.dst);
free((void*)z->writer.in_buffer.src);
int r = rwzstd_close(rw);
if(!flush_ok && r >= 0) {
return -1;
}
return r;
}
SDL_RWops *SDL_RWWrapZstdWriter(SDL_RWops *src, int clevel, bool autoclose) {
if(!src) {
return NULL;
}
SDL_RWops *rw = rwzstd_alloc(src, autoclose);
if(!rw) {
return NULL;
}
rw->write = rwzstd_write;
rw->read = rwzstd_read_invalid;
rw->close = rwzstd_writer_close;
ZstdData *z = ZDATA(rw);
z->writer.stream = NOT_NULL(ZSTD_createCStream());
z->writer.out_buffer.size = ZSTD_CStreamOutSize();
z->writer.out_buffer.dst = calloc(1, z->writer.out_buffer.size);
z->writer.in_buffer_alloc_size = ZSTD_CStreamInSize();
z->writer.in_buffer.src = calloc(1, z->writer.in_buffer_alloc_size);
if(clevel < ZSTD_minCLevel() || clevel > ZSTD_maxCLevel()) {
log_warn("Invalid compression level %i", clevel);
clevel = RW_ZSTD_LEVEL_DEFAULT;
assert(0);
}
ZSTD_CCtx_setParameter(z->writer.stream, ZSTD_c_compressionLevel, clevel);
return rw;
}

21
src/rwops/rwops_zstd.h Normal file
View file

@ -0,0 +1,21 @@
/*
* 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>.
*/
#ifndef IGUARD_rwops_rwops_zstd_h
#define IGUARD_rwops_rwops_zstd_h
#include "taisei.h"
#include <SDL.h>
#define RW_ZSTD_LEVEL_DEFAULT 0
SDL_RWops *SDL_RWWrapZstdReader(SDL_RWops *src, bool autoclose);
SDL_RWops *SDL_RWWrapZstdWriter(SDL_RWops *src, int clevel, bool autoclose);
#endif // IGUARD_rwops_rwops_zstd_h