RetroArch/libretro-common/streams/rzip_stream.c
2021-01-16 21:03:57 +01:00

1120 lines
32 KiB
C

/* Copyright (C) 2010-2020 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (rzip_stream.c).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <string/stdstring.h>
#include <file/file_path.h>
#include <streams/file_stream.h>
#include <streams/trans_stream.h>
#include <streams/rzip_stream.h>
/* Current RZIP file format version */
#define RZIP_VERSION 1
/* Compression level
* > zlib default of 6 provides the best
* balance between file size and
* compression speed */
#define RZIP_COMPRESSION_LEVEL 6
/* Default chunk size: 128kb */
#define RZIP_DEFAULT_CHUNK_SIZE 131072
/* Header sizes (in bytes) */
#define RZIP_HEADER_SIZE 20
#define RZIP_CHUNK_HEADER_SIZE 4
/* Holds all metadata for an RZIP file stream */
struct rzipstream
{
uint64_t size;
/* virtual_ptr: Used to track how much
* uncompressed data has been read */
uint64_t virtual_ptr;
RFILE* file;
const struct trans_stream_backend *deflate_backend;
void *deflate_stream;
const struct trans_stream_backend *inflate_backend;
void *inflate_stream;
uint8_t *in_buf;
uint8_t *out_buf;
uint32_t in_buf_size;
uint32_t in_buf_ptr;
uint32_t out_buf_size;
uint32_t out_buf_ptr;
uint32_t out_buf_occupancy;
uint32_t chunk_size;
bool is_compressed;
bool is_writing;
};
/* Header Functions */
/* Reads header information from RZIP file
* > Detects whether file is compressed or
* uncompressed data
* > If compressed, extracts uncompressed
* file/chunk sizes */
static bool rzipstream_read_file_header(rzipstream_t *stream)
{
unsigned i;
int64_t length;
uint8_t header_bytes[RZIP_HEADER_SIZE];
if (!stream)
return false;
for (i = 0; i < RZIP_HEADER_SIZE; i++)
header_bytes[i] = 0;
/* Attempt to read header bytes */
length = filestream_read(stream->file, header_bytes, sizeof(header_bytes));
if (length <= 0)
return false;
/* If file length is less than header size
* then assume this is uncompressed data */
if (length < RZIP_HEADER_SIZE)
goto file_uncompressed;
/* Check 'magic numbers' - first 8 bytes
* of header */
if ((header_bytes[0] != 35) || /* # */
(header_bytes[1] != 82) || /* R */
(header_bytes[2] != 90) || /* Z */
(header_bytes[3] != 73) || /* I */
(header_bytes[4] != 80) || /* P */
(header_bytes[5] != 118) || /* v */
(header_bytes[6] != RZIP_VERSION) || /* file format version number */
(header_bytes[7] != 35)) /* # */
goto file_uncompressed;
/* Get uncompressed chunk size - next 4 bytes */
stream->chunk_size = ((uint32_t)header_bytes[11] << 24) |
((uint32_t)header_bytes[10] << 16) |
((uint32_t)header_bytes[9] << 8) |
(uint32_t)header_bytes[8];
if (stream->chunk_size == 0)
return false;
/* Get total uncompressed data size - next 8 bytes */
stream->size = ((uint64_t)header_bytes[19] << 56) |
((uint64_t)header_bytes[18] << 48) |
((uint64_t)header_bytes[17] << 40) |
((uint64_t)header_bytes[16] << 32) |
((uint64_t)header_bytes[15] << 24) |
((uint64_t)header_bytes[14] << 16) |
((uint64_t)header_bytes[13] << 8) |
(uint64_t)header_bytes[12];
if (stream->size == 0)
return false;
stream->is_compressed = true;
return true;
file_uncompressed:
/* Reset file to start */
filestream_seek(stream->file, 0, SEEK_SET);
/* Get 'raw' file size */
stream->size = filestream_get_size(stream->file);
stream->is_compressed = false;
return true;
}
/* Writes header information to RZIP file
* > ID 'magic numbers' + uncompressed
* file/chunk sizes */
static bool rzipstream_write_file_header(rzipstream_t *stream)
{
unsigned i;
int64_t length;
uint8_t header_bytes[RZIP_HEADER_SIZE];
if (!stream)
return false;
/* Populate header array */
for (i = 0; i < RZIP_HEADER_SIZE; i++)
header_bytes[i] = 0;
/* > 'Magic numbers' - first 8 bytes */
header_bytes[0] = 35; /* # */
header_bytes[1] = 82; /* R */
header_bytes[2] = 90; /* Z */
header_bytes[3] = 73; /* I */
header_bytes[4] = 80; /* P */
header_bytes[5] = 118; /* v */
header_bytes[6] = RZIP_VERSION; /* file format version number */
header_bytes[7] = 35; /* # */
/* > Uncompressed chunk size - next 4 bytes */
header_bytes[11] = (stream->chunk_size >> 24) & 0xFF;
header_bytes[10] = (stream->chunk_size >> 16) & 0xFF;
header_bytes[9] = (stream->chunk_size >> 8) & 0xFF;
header_bytes[8] = stream->chunk_size & 0xFF;
/* > Total uncompressed data size - next 8 bytes */
header_bytes[19] = (stream->size >> 56) & 0xFF;
header_bytes[18] = (stream->size >> 48) & 0xFF;
header_bytes[17] = (stream->size >> 40) & 0xFF;
header_bytes[16] = (stream->size >> 32) & 0xFF;
header_bytes[15] = (stream->size >> 24) & 0xFF;
header_bytes[14] = (stream->size >> 16) & 0xFF;
header_bytes[13] = (stream->size >> 8) & 0xFF;
header_bytes[12] = stream->size & 0xFF;
/* Reset file to start */
filestream_seek(stream->file, 0, SEEK_SET);
/* Write header bytes */
length = filestream_write(stream->file,
header_bytes, sizeof(header_bytes));
if (length != RZIP_HEADER_SIZE)
return false;
return true;
}
/* Stream Initialisation/De-initialisation */
/* Initialises all members of an rzipstream_t struct,
* reading config from existing file header if available */
static bool rzipstream_init_stream(
rzipstream_t *stream, const char *path, bool is_writing)
{
unsigned file_mode;
if (!stream)
return false;
/* Ensure stream has valid initial values */
stream->size = 0;
stream->chunk_size = RZIP_DEFAULT_CHUNK_SIZE;
stream->file = NULL;
stream->deflate_backend = NULL;
stream->deflate_stream = NULL;
stream->inflate_backend = NULL;
stream->inflate_stream = NULL;
stream->in_buf = NULL;
stream->in_buf_size = 0;
stream->in_buf_ptr = 0;
stream->out_buf = NULL;
stream->out_buf_size = 0;
stream->out_buf_ptr = 0;
stream->out_buf_occupancy = 0;
/* Check whether this is a read or write stream */
stream->is_writing = is_writing;
if (stream->is_writing)
{
/* Written files are always compressed */
stream->is_compressed = true;
file_mode = RETRO_VFS_FILE_ACCESS_WRITE;
}
/* For read files, must get compression status
* from file itself... */
else
file_mode = RETRO_VFS_FILE_ACCESS_READ;
/* Open file */
stream->file = filestream_open(
path, file_mode, RETRO_VFS_FILE_ACCESS_HINT_NONE);
if (!stream->file)
return false;
/* If file is open for writing, output header
* (Size component cannot be written until
* file is closed...) */
if (stream->is_writing)
{
/* Note: could just write zeros here, but
* still want to identify this as an RZIP
* file if writing fails partway through */
if (!rzipstream_write_file_header(stream))
return false;
}
/* If file is open for reading, parse any existing
* header */
else if (!rzipstream_read_file_header(stream))
return false;
/* Initialise appropriate transform stream
* and determine associated buffer sizes */
if (stream->is_writing)
{
/* Compression */
stream->deflate_backend = trans_stream_get_zlib_deflate_backend();
if (!stream->deflate_backend)
return false;
stream->deflate_stream = stream->deflate_backend->stream_new();
if (!stream->deflate_stream)
return false;
/* Set compression level */
if (!stream->deflate_backend->define(
stream->deflate_stream, "level", RZIP_COMPRESSION_LEVEL))
return false;
/* Buffers
* > Input: uncompressed
* > Output: compressed */
stream->in_buf_size = stream->chunk_size;
stream->out_buf_size = stream->chunk_size * 2;
/* > Account for minimum zlib overhead
* of 11 bytes... */
stream->out_buf_size =
(stream->out_buf_size < (stream->in_buf_size + 11)) ?
stream->out_buf_size + 11 :
stream->out_buf_size;
/* Redundant safety check */
if ((stream->in_buf_size == 0) ||
(stream->out_buf_size == 0))
return false;
}
/* When reading, don't need an inflate transform
* stream (or buffers) if source file is uncompressed */
else if (stream->is_compressed)
{
/* Decompression */
stream->inflate_backend = trans_stream_get_zlib_inflate_backend();
if (!stream->inflate_backend)
return false;
stream->inflate_stream = stream->inflate_backend->stream_new();
if (!stream->inflate_stream)
return false;
/* Buffers
* > Input: compressed
* > Output: uncompressed
* Note 1: Actual compressed chunk sizes are read
* from the file - just allocate a sensible
* default to minimise memory reallocations
* Note 2: If file header is valid, output buffer
* should have a size of exactly stream->chunk_size.
* Allocate some additional space, just for
* redundant safety... */
stream->in_buf_size = stream->chunk_size * 2;
stream->out_buf_size = stream->chunk_size + (stream->chunk_size >> 2);
/* Redundant safety check */
if ((stream->in_buf_size == 0) ||
(stream->out_buf_size == 0))
return false;
}
/* Allocate buffers */
if (stream->in_buf_size > 0)
{
stream->in_buf = (uint8_t *)calloc(stream->in_buf_size, 1);
if (!stream->in_buf)
return false;
}
if (stream->out_buf_size > 0)
{
stream->out_buf = (uint8_t *)calloc(stream->out_buf_size, 1);
if (!stream->out_buf)
return false;
}
return true;
}
/* free()'s all members of an rzipstream_t struct
* > Also closes associated file, if currently open */
static int rzipstream_free_stream(rzipstream_t *stream)
{
int ret = 0;
if (!stream)
return -1;
/* Free transform streams */
if (stream->deflate_stream && stream->deflate_backend)
stream->deflate_backend->stream_free(stream->deflate_stream);
stream->deflate_stream = NULL;
stream->deflate_backend = NULL;
if (stream->inflate_stream && stream->inflate_backend)
stream->inflate_backend->stream_free(stream->inflate_stream);
stream->inflate_stream = NULL;
stream->inflate_backend = NULL;
/* Free buffers */
if (stream->in_buf)
free(stream->in_buf);
stream->in_buf = NULL;
if (stream->out_buf)
free(stream->out_buf);
stream->out_buf = NULL;
/* Close file */
if (stream->file)
ret = filestream_close(stream->file);
stream->file = NULL;
free(stream);
return ret;
}
/* File Open */
/* Opens a new or existing RZIP file
* > Supported 'mode' values are:
* - RETRO_VFS_FILE_ACCESS_READ
* - RETRO_VFS_FILE_ACCESS_WRITE
* > When reading, 'path' may reference compressed
* or uncompressed data
* Returns NULL if arguments are invalid, file
* is invalid or an IO error occurs */
rzipstream_t* rzipstream_open(const char *path, unsigned mode)
{
rzipstream_t *stream = NULL;
/* Sanity check
* > Only RETRO_VFS_FILE_ACCESS_READ and
* RETRO_VFS_FILE_ACCESS_WRITE are supported */
if (string_is_empty(path) ||
((mode != RETRO_VFS_FILE_ACCESS_READ) &&
(mode != RETRO_VFS_FILE_ACCESS_WRITE)))
return NULL;
/* If opening in read mode, ensure file exists */
if ((mode == RETRO_VFS_FILE_ACCESS_READ) &&
!path_is_valid(path))
return NULL;
/* Allocate stream object */
stream = (rzipstream_t*)malloc(sizeof(*stream));
if (!stream)
return NULL;
stream->is_compressed = false;
stream->is_writing = false;
stream->size = 0;
stream->chunk_size = 0;
stream->virtual_ptr = 0;
stream->file = NULL;
stream->deflate_backend = NULL;
stream->deflate_stream = NULL;
stream->inflate_backend = NULL;
stream->inflate_stream = NULL;
stream->in_buf = NULL;
stream->in_buf_size = 0;
stream->in_buf_ptr = 0;
stream->out_buf = NULL;
stream->out_buf_size = 0;
stream->out_buf_ptr = 0;
stream->out_buf_occupancy = 0;
/* Initialise stream */
if (!rzipstream_init_stream(
stream, path,
(mode == RETRO_VFS_FILE_ACCESS_WRITE)))
{
rzipstream_free_stream(stream);
return NULL;
}
return stream;
}
/* File Read */
/* Reads and decompresses the next chunk of data
* in the RZIP file */
static bool rzipstream_read_chunk(rzipstream_t *stream)
{
unsigned i;
int64_t length;
uint8_t chunk_header_bytes[RZIP_CHUNK_HEADER_SIZE];
uint32_t compressed_chunk_size;
uint32_t inflate_read;
uint32_t inflate_written;
if (!stream || !stream->inflate_backend || !stream->inflate_stream)
return false;
for (i = 0; i < RZIP_CHUNK_HEADER_SIZE; i++)
chunk_header_bytes[i] = 0;
/* Attempt to read chunk header bytes */
length = filestream_read(
stream->file, chunk_header_bytes, sizeof(chunk_header_bytes));
if (length != RZIP_CHUNK_HEADER_SIZE)
return false;
/* Get size of next compressed chunk */
compressed_chunk_size = ((uint32_t)chunk_header_bytes[3] << 24) |
((uint32_t)chunk_header_bytes[2] << 16) |
((uint32_t)chunk_header_bytes[1] << 8) |
(uint32_t)chunk_header_bytes[0];
if (compressed_chunk_size == 0)
return false;
/* Resize input buffer, if required */
if (compressed_chunk_size > stream->in_buf_size)
{
free(stream->in_buf);
stream->in_buf = NULL;
stream->in_buf_size = compressed_chunk_size;
stream->in_buf = (uint8_t *)calloc(stream->in_buf_size, 1);
if (!stream->in_buf)
return false;
/* Note: Uncompressed data size is fixed, and read
* from the file header - we therefore don't attempt
* to resize the output buffer (if it's too small, then
* that's an error condition) */
}
/* Read compressed chunk from file */
length = filestream_read(
stream->file, stream->in_buf, compressed_chunk_size);
if (length != compressed_chunk_size)
return false;
/* Decompress chunk data */
stream->inflate_backend->set_in(
stream->inflate_stream,
stream->in_buf, compressed_chunk_size);
stream->inflate_backend->set_out(
stream->inflate_stream,
stream->out_buf, stream->out_buf_size);
/* Note: We have to set 'flush == true' here, otherwise we
* can't guarantee that the entire chunk will be written
* to the output buffer - this is inefficient, but not
* much we can do... */
if (!stream->inflate_backend->trans(
stream->inflate_stream, true,
&inflate_read, &inflate_written, NULL))
return false;
/* Error checking */
if (inflate_read != compressed_chunk_size)
return false;
if ((inflate_written == 0) ||
(inflate_written > stream->out_buf_size))
return false;
/* Record current output buffer occupancy
* and reset pointer */
stream->out_buf_occupancy = inflate_written;
stream->out_buf_ptr = 0;
return true;
}
/* Reads (a maximum of) 'len' bytes from an RZIP file.
* Returns actual number of bytes read, or -1 in
* the event of an error */
int64_t rzipstream_read(rzipstream_t *stream, void *data, int64_t len)
{
int64_t data_len = len;
uint8_t *data_ptr = (uint8_t *)data;
int64_t data_read = 0;
if (!stream || stream->is_writing || !data)
return -1;
/* If we are reading uncompressed data, simply
* 'pass on' the direct file access request */
if (!stream->is_compressed)
return filestream_read(stream->file, data, len);
/* Process input data */
while (data_len > 0)
{
uint32_t read_size = 0;
/* Check whether we have reached the end
* of the file */
if (stream->virtual_ptr >= stream->size)
return data_read;
/* If everything in the output buffer has already
* been read, grab and extract the next chunk
* from disk */
if (stream->out_buf_ptr >= stream->out_buf_occupancy)
if (!rzipstream_read_chunk(stream))
return -1;
/* Get amount of data to 'read out' this loop
* > i.e. minimum of remaining output buffer
* occupancy and remaining 'read data' size */
read_size = stream->out_buf_occupancy - stream->out_buf_ptr;
if (read_size > data_len)
read_size = data_len;
/* Copy as much cached data as possible into
* the read buffer */
memcpy(data_ptr, stream->out_buf + stream->out_buf_ptr, read_size);
/* Increment pointers and remaining length */
stream->out_buf_ptr += read_size;
data_ptr += read_size;
data_len -= read_size;
stream->virtual_ptr += read_size;
data_read += read_size;
}
return data_read;
}
/* Reads next character from an RZIP file.
* Returns character value, or EOF if no data
* remains.
* Note: Always returns EOF if file is open
* for writing. */
int rzipstream_getc(rzipstream_t *stream)
{
char c = 0;
if (!stream || stream->is_writing)
return EOF;
/* Attempt to read a single character */
if (rzipstream_read(stream, &c, 1) == 1)
return (int)(unsigned char)c;
return EOF;
}
/* Reads one line from an RZIP file and stores it
* in the character array pointed to by 's'.
* It stops reading when either (len-1) characters
* are read, the newline character is read, or the
* end-of-file is reached, whichever comes first.
* On success, returns 's'. In the event of an error,
* or if end-of-file is reached and no characters
* have been read, returns NULL. */
char* rzipstream_gets(rzipstream_t *stream, char *s, size_t len)
{
size_t str_len;
int c = 0;
char *str_ptr = s;
if (!stream || stream->is_writing || (len == 0))
return NULL;
/* Read bytes until newline or EOF is reached,
* or string buffer is full */
for (str_len = (len - 1); str_len > 0; str_len--)
{
/* Get next character */
c = rzipstream_getc(stream);
/* Check for newline and EOF */
if ((c == '\n') || (c == EOF))
break;
/* Copy character to string buffer */
*str_ptr++ = c;
}
/* Add NUL termination */
*str_ptr = '\0';
/* Check whether EOF has been reached without
* reading any characters */
if ((str_ptr == s) && (c == EOF))
return NULL;
return (s);
}
/* Reads all data from file specified by 'path' and
* copies it to 'buf'.
* - 'buf' will be allocated and must be free()'d manually.
* - Allocated 'buf' size is equal to 'len'.
* Returns false in the event of an error */
bool rzipstream_read_file(const char *path, void **buf, int64_t *len)
{
int64_t bytes_read = 0;
void *content_buf = NULL;
int64_t content_buf_size = 0;
rzipstream_t *stream = NULL;
if (!buf)
return false;
/* Attempt to open file */
stream = rzipstream_open(path, RETRO_VFS_FILE_ACCESS_READ);
if (!stream)
{
*buf = NULL;
return false;
}
/* Get file size */
content_buf_size = rzipstream_get_size(stream);
if (content_buf_size < 0)
goto error;
if ((int64_t)(uint64_t)(content_buf_size + 1) != (content_buf_size + 1))
goto error;
/* Allocate buffer */
content_buf = malloc((size_t)(content_buf_size + 1));
if (!content_buf)
goto error;
/* Read file contents */
bytes_read = rzipstream_read(stream, content_buf, content_buf_size);
if (bytes_read < 0)
goto error;
/* Close file */
rzipstream_close(stream);
stream = NULL;
/* Add NUL termination for easy/safe handling of strings.
* Will only work with sane character formatting (Unix). */
((char*)content_buf)[bytes_read] = '\0';
/* Assign buffer */
*buf = content_buf;
/* Assign length value, if required */
if (len)
*len = bytes_read;
return true;
error:
if (stream)
rzipstream_close(stream);
stream = NULL;
if (content_buf)
free(content_buf);
content_buf = NULL;
if (len)
*len = -1;
*buf = NULL;
return false;
}
/* File Write */
/* Compresses currently cached data and writes it
* as the next RZIP file chunk */
static bool rzipstream_write_chunk(rzipstream_t *stream)
{
unsigned i;
int64_t length;
uint8_t chunk_header_bytes[RZIP_CHUNK_HEADER_SIZE];
uint32_t deflate_read;
uint32_t deflate_written;
if (!stream || !stream->deflate_backend || !stream->deflate_stream)
return false;
for (i = 0; i < RZIP_CHUNK_HEADER_SIZE; i++)
chunk_header_bytes[i] = 0;
/* Compress data currently held in input buffer */
stream->deflate_backend->set_in(
stream->deflate_stream,
stream->in_buf, stream->in_buf_ptr);
stream->deflate_backend->set_out(
stream->deflate_stream,
stream->out_buf, stream->out_buf_size);
/* Note: We have to set 'flush == true' here, otherwise we
* can't guarantee that the entire chunk will be written
* to the output buffer - this is inefficient, but not
* much we can do... */
if (!stream->deflate_backend->trans(
stream->deflate_stream, true,
&deflate_read, &deflate_written, NULL))
return false;
/* Error checking */
if (deflate_read != stream->in_buf_ptr)
return false;
if ((deflate_written == 0) ||
(deflate_written > stream->out_buf_size))
return false;
/* Write compressed chunk size to file */
chunk_header_bytes[3] = (deflate_written >> 24) & 0xFF;
chunk_header_bytes[2] = (deflate_written >> 16) & 0xFF;
chunk_header_bytes[1] = (deflate_written >> 8) & 0xFF;
chunk_header_bytes[0] = deflate_written & 0xFF;
length = filestream_write(
stream->file, chunk_header_bytes, sizeof(chunk_header_bytes));
if (length != RZIP_CHUNK_HEADER_SIZE)
return false;
/* Write compressed data to file */
length = filestream_write(
stream->file, stream->out_buf, deflate_written);
if (length != deflate_written)
return false;
/* Reset input buffer pointer */
stream->in_buf_ptr = 0;
return true;
}
/* Writes 'len' bytes to an RZIP file.
* Returns actual number of bytes written, or -1
* in the event of an error */
int64_t rzipstream_write(rzipstream_t *stream, const void *data, int64_t len)
{
int64_t data_len = len;
const uint8_t *data_ptr = (const uint8_t *)data;
if (!stream || !stream->is_writing || !data)
return -1;
/* Process input data */
while (data_len > 0)
{
uint32_t cache_size = 0;
/* If input buffer is full, compress and write to disk */
if (stream->in_buf_ptr >= stream->in_buf_size)
if (!rzipstream_write_chunk(stream))
return -1;
/* Get amount of data to cache during this loop
* > i.e. minimum of space remaining in input buffer
* and remaining 'write data' size */
cache_size = stream->in_buf_size - stream->in_buf_ptr;
if (cache_size > data_len)
cache_size = data_len;
/* Copy as much data as possible into
* the input buffer */
memcpy(stream->in_buf + stream->in_buf_ptr, data_ptr, cache_size);
/* Increment pointers and remaining length */
stream->in_buf_ptr += cache_size;
data_ptr += cache_size;
data_len -= cache_size;
stream->size += cache_size;
stream->virtual_ptr += cache_size;
}
/* We always write the specified number of bytes
* (unless rzipstream_write_chunk() fails, in
* which we register a complete failure...) */
return len;
}
/* Writes a single character to an RZIP file.
* Returns character written, or EOF in the event
* of an error */
int rzipstream_putc(rzipstream_t *stream, int c)
{
char c_char = (char)c;
if (!stream || !stream->is_writing)
return EOF;
return (rzipstream_write(stream, &c_char, 1) == 1) ?
(int)(unsigned char)c : EOF;
}
/* Writes a variable argument list to an RZIP file.
* Ugly 'internal' function, required to enable
* 'printf' support in the higher level 'interface_stream'.
* Returns actual number of bytes written, or -1
* in the event of an error */
int rzipstream_vprintf(rzipstream_t *stream, const char* format, va_list args)
{
static char buffer[8 * 1024] = {0};
int64_t num_chars = vsnprintf(buffer,
sizeof(buffer), format, args);
if (num_chars < 0)
return -1;
else if (num_chars == 0)
return 0;
return (int)rzipstream_write(stream, buffer, num_chars);
}
/* Writes formatted output to an RZIP file.
* Returns actual number of bytes written, or -1
* in the event of an error */
int rzipstream_printf(rzipstream_t *stream, const char* format, ...)
{
va_list vl;
int result = 0;
/* Initialise variable argument list */
va_start(vl, format);
/* Write variable argument list to file */
result = rzipstream_vprintf(stream, format, vl);
/* End using variable argument list */
va_end(vl);
return result;
}
/* Writes contents of 'data' buffer to file
* specified by 'path'.
* Returns false in the event of an error */
bool rzipstream_write_file(const char *path, const void *data, int64_t len)
{
int64_t bytes_written = 0;
rzipstream_t *stream = NULL;
if (!data)
return false;
/* Attempt to open file */
stream = rzipstream_open(path, RETRO_VFS_FILE_ACCESS_WRITE);
if (!stream)
return false;
/* Write contents of data buffer to file */
bytes_written = rzipstream_write(stream, data, len);
/* Close file */
if (rzipstream_close(stream) == -1)
return false;
/* Check that the correct number of bytes
* were written */
if (bytes_written != len)
return false;
return true;
}
/* File Control */
/* Sets file position to the beginning of the
* specified RZIP file.
* Note: It is not recommended to rewind a file
* that is open for writing, since the caller
* may end up with a file containing junk data
* at the end (harmless, but a waste of space). */
void rzipstream_rewind(rzipstream_t *stream)
{
if (!stream)
return;
/* Note: rzipstream_rewind() has no way of
* reporting errors (higher level interface
* requires a void return type) - so if anything
* goes wrong, all we can do is print to stderr
* and bail out... */
/* If we are handling uncompressed data, simply
* 'pass on' the direct file access request */
if (!stream->is_compressed)
{
filestream_rewind(stream->file);
return;
}
/* If no file access has yet occurred, file is
* already at the beginning -> do nothing */
if (stream->virtual_ptr == 0)
return;
/* Check whether we are reading or writing */
if (stream->is_writing)
{
/* Reset file position to first chunk location */
filestream_seek(stream->file, RZIP_HEADER_SIZE, SEEK_SET);
if (filestream_error(stream->file))
{
fprintf(
stderr,
"rzipstream_rewind(): Failed to reset file position...\n");
return;
}
/* Reset pointers */
stream->virtual_ptr = 0;
stream->in_buf_ptr = 0;
/* Reset file size */
stream->size = 0;
}
else
{
/* Check whether first file chunk is currently
* buffered in memory */
if ((stream->virtual_ptr < stream->chunk_size) &&
(stream->out_buf_ptr < stream->out_buf_occupancy))
{
/* It is: No file access is therefore required
* > Just reset pointers */
stream->virtual_ptr = 0;
stream->out_buf_ptr = 0;
}
else
{
/* It isn't: Have to re-read the first chunk
* from disk... */
/* Reset file position to first chunk location */
filestream_seek(stream->file, RZIP_HEADER_SIZE, SEEK_SET);
if (filestream_error(stream->file))
{
fprintf(
stderr,
"rzipstream_rewind(): Failed to reset file position...\n");
return;
}
/* Read chunk */
if (!rzipstream_read_chunk(stream))
{
fprintf(
stderr,
"rzipstream_rewind(): Failed to read first chunk of file...\n");
return;
}
/* Reset pointers */
stream->virtual_ptr = 0;
stream->out_buf_ptr = 0;
}
}
}
/* File Status */
/* Returns total size (in bytes) of the *uncompressed*
* data in an RZIP file.
* (If reading an uncompressed file, this corresponds
* to the 'physical' file size in bytes)
* Returns -1 in the event of a error. */
int64_t rzipstream_get_size(rzipstream_t *stream)
{
if (!stream)
return -1;
if (stream->is_compressed)
return stream->size;
return filestream_get_size(stream->file);
}
/* Returns EOF when no further *uncompressed* data
* can be read from an RZIP file. */
int rzipstream_eof(rzipstream_t *stream)
{
if (!stream)
return -1;
if (stream->is_compressed)
return (stream->virtual_ptr >= stream->size) ?
EOF : 0;
return filestream_eof(stream->file);
}
/* Returns the offset of the current byte of *uncompressed*
* data relative to the beginning of an RZIP file.
* Returns -1 in the event of a error. */
int64_t rzipstream_tell(rzipstream_t *stream)
{
if (!stream)
return -1;
return (int64_t)stream->virtual_ptr;
}
/* Returns true if specified RZIP file contains
* compressed content */
bool rzipstream_is_compressed(rzipstream_t *stream)
{
if (!stream)
return false;
return stream->is_compressed;
}
/* File Close */
/* Closes RZIP file. If file is open for writing,
* flushes any remaining buffered data to disk.
* Returns -1 in the event of a error. */
int rzipstream_close(rzipstream_t *stream)
{
if (!stream)
return -1;
/* If we are writing, ensure that any
* remaining uncompressed data is flushed to
* disk and update file header */
if (stream->is_writing)
{
if (stream->in_buf_ptr > 0)
if (!rzipstream_write_chunk(stream))
goto error;
if (!rzipstream_write_file_header(stream))
goto error;
}
/* Free stream
* > This also closes the file */
return rzipstream_free_stream(stream);
error:
/* Stream must be free()'d regardless */
rzipstream_free_stream(stream);
return -1;
}