513d613387
I would've preferred to just go with 4-spaces for indent and no tabs, but lao is a bit conservative about it. :^) Still, this is a ton better than mixing different styles all over the place, especially within the same file.
205 lines
3.9 KiB
C
205 lines
3.9 KiB
C
/*
|
|
* This software is licensed under the terms of the MIT-License
|
|
* See COPYING for further information.
|
|
* ---
|
|
* Copyright (c) 2011-2018, Lukas Weber <laochailan@web.de>.
|
|
* Copyright (c) 2012-2018, Andrei Alexeyev <akari@alienslab.net>.
|
|
*/
|
|
|
|
#include "taisei.h"
|
|
|
|
#include <stdbool.h>
|
|
#include "rwops_segment.h"
|
|
#include "util.h"
|
|
|
|
typedef struct Segment {
|
|
SDL_RWops *wrapped;
|
|
size_t start;
|
|
size_t end;
|
|
int64_t pos; // fallback for non-seekable streams
|
|
bool autoclose;
|
|
} Segment;
|
|
|
|
#define SEGMENT(rw) ((Segment*)((rw)->hidden.unknown.data1))
|
|
|
|
static int64_t segment_seek(SDL_RWops *rw, int64_t offset, int whence) {
|
|
Segment *s = SEGMENT(rw);
|
|
|
|
switch(whence) {
|
|
case RW_SEEK_CUR: {
|
|
if(offset) {
|
|
int64_t pos = SDL_RWtell(s->wrapped);
|
|
|
|
if(pos < 0) {
|
|
return pos;
|
|
}
|
|
|
|
if(pos + offset > s->end) {
|
|
offset = s->end - pos;
|
|
} else if(pos + offset < s->start) {
|
|
offset = s->start - pos;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case RW_SEEK_SET: {
|
|
offset += s->start;
|
|
|
|
if(offset > s->end) {
|
|
offset = s->end;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case RW_SEEK_END: {
|
|
int64_t size = SDL_RWsize(s->wrapped);
|
|
|
|
if(size < 0) {
|
|
return size;
|
|
}
|
|
|
|
if(size > s->end) {
|
|
offset -= size - s->end;
|
|
}
|
|
|
|
if(size + offset < s->start) {
|
|
offset += s->start - (size + offset);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
SDL_SetError("Bad whence value %i", whence);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
int64_t result = SDL_RWseek(s->wrapped, offset, whence);
|
|
|
|
if(result > 0) {
|
|
if(s->start > result) {
|
|
result = 0;
|
|
} else {
|
|
result -= s->start;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static int64_t segment_size(SDL_RWops *rw) {
|
|
Segment *s = SEGMENT(rw);
|
|
int64_t size = SDL_RWsize(s->wrapped);
|
|
|
|
if(size < 0) {
|
|
return size;
|
|
}
|
|
|
|
if(size > s->end) {
|
|
size = s->end;
|
|
}
|
|
|
|
return size - s->start;
|
|
}
|
|
|
|
static size_t segment_readwrite(SDL_RWops *rw, void *ptr, size_t size, size_t maxnum, bool write) {
|
|
Segment *s = SEGMENT(rw);
|
|
int64_t pos = SDL_RWtell(s->wrapped);
|
|
size_t onum;
|
|
|
|
if(pos < 0) {
|
|
log_debug("SDL_RWtell failed (%i): %s", (int)pos, SDL_GetError());
|
|
SDL_SetError("segment_readwrite: SDL_RWtell failed (%i) %s", (int)pos, SDL_GetError());
|
|
|
|
// this could be a non-seekable stream, like /dev/stdin...
|
|
// let's assume nothing else uses the wrapped stream and try to guess the current position
|
|
// this only works if the actual positon in the stream at the time of segment creation matched s->start...
|
|
pos = s->pos;
|
|
} else {
|
|
s->pos = pos;
|
|
}
|
|
|
|
if(pos < s->start || pos > s->end) {
|
|
log_warn("Segment range violation");
|
|
SDL_SetError("segment_readwrite: segment range violation");
|
|
return 0;
|
|
}
|
|
|
|
int64_t maxsize = s->end - pos;
|
|
|
|
while(size * maxnum > maxsize) {
|
|
if(!--maxnum) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if(write) {
|
|
onum = SDL_RWwrite(s->wrapped, ptr, size, maxnum);
|
|
} else {
|
|
onum = SDL_RWread(s->wrapped, ptr, size, maxnum);
|
|
}
|
|
|
|
s->pos += onum / size;
|
|
assert(s->pos <= s->end);
|
|
|
|
return onum;
|
|
}
|
|
|
|
static size_t segment_read(SDL_RWops *rw, void *ptr, size_t size, size_t maxnum) {
|
|
return segment_readwrite(rw, ptr, size, maxnum, false);
|
|
}
|
|
|
|
static size_t segment_write(SDL_RWops *rw, const void *ptr, size_t size, size_t maxnum) {
|
|
return segment_readwrite(rw, (void*)ptr, size, maxnum, true);
|
|
}
|
|
|
|
static int segment_close(SDL_RWops *rw) {
|
|
if(rw) {
|
|
Segment *s = SEGMENT(rw);
|
|
|
|
if(s->autoclose) {
|
|
SDL_RWclose(s->wrapped);
|
|
}
|
|
|
|
free(s);
|
|
SDL_FreeRW(rw);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
SDL_RWops* SDL_RWWrapSegment(SDL_RWops *src, size_t start, size_t end, bool autoclose) {
|
|
assert(end > start);
|
|
|
|
SDL_RWops *rw = SDL_AllocRW();
|
|
|
|
if(!rw) {
|
|
return NULL;
|
|
}
|
|
|
|
memset(rw, 0, sizeof(SDL_RWops));
|
|
|
|
rw->type = SDL_RWOPS_UNKNOWN;
|
|
rw->seek = segment_seek;
|
|
rw->size = segment_size;
|
|
rw->read = segment_read;
|
|
rw->write = segment_write;
|
|
rw->close = segment_close;
|
|
|
|
Segment *s = malloc(sizeof(Segment));
|
|
s->wrapped = src;
|
|
s->start = start;
|
|
s->end = end;
|
|
s->autoclose = autoclose;
|
|
|
|
// fallback for non-seekable streams
|
|
s->pos = start;
|
|
|
|
rw->hidden.unknown.data1 = s;
|
|
|
|
return rw;
|
|
}
|