From c1f4a7536a8587613d7a11c4126ec9a719e1c10d Mon Sep 17 00:00:00 2001 From: jdgleaver Date: Fri, 17 Apr 2020 16:16:38 +0100 Subject: [PATCH] Expand functionality of 'rzip_stream' interface --- .../include/streams/interface_stream.h | 5 + libretro-common/include/streams/rzip_stream.h | 66 ++++ libretro-common/streams/interface_stream.c | 82 +++- libretro-common/streams/rzip_stream.c | 358 +++++++++++++++++- 4 files changed, 504 insertions(+), 7 deletions(-) diff --git a/libretro-common/include/streams/interface_stream.h b/libretro-common/include/streams/interface_stream.h index 813b3551cf..4cfc982e12 100644 --- a/libretro-common/include/streams/interface_stream.h +++ b/libretro-common/include/streams/interface_stream.h @@ -75,6 +75,9 @@ int64_t intfstream_read(intfstream_internal_t *intf, int64_t intfstream_write(intfstream_internal_t *intf, const void *s, uint64_t len); +int intfstream_printf(intfstream_internal_t *intf, + const char* format, ...); + int64_t intfstream_get_ptr(intfstream_internal_t *intf); char *intfstream_gets(intfstream_internal_t *intf, @@ -101,6 +104,8 @@ uint32_t intfstream_get_offset_to_start(intfstream_internal_t *intf); uint32_t intfstream_get_frame_size(intfstream_internal_t *intf); +bool intfstream_is_compressed(intfstream_internal_t *intf); + intfstream_t* intfstream_open_file(const char *path, unsigned mode, unsigned hints); diff --git a/libretro-common/include/streams/rzip_stream.h b/libretro-common/include/streams/rzip_stream.h index 8b53033516..0dcc7f2594 100644 --- a/libretro-common/include/streams/rzip_stream.h +++ b/libretro-common/include/streams/rzip_stream.h @@ -26,6 +26,7 @@ #include #include #include +#include #include @@ -90,6 +91,30 @@ rzipstream_t* rzipstream_open(const char *path, unsigned mode); * the event of an error */ int64_t rzipstream_read(rzipstream_t *stream, void *data, int64_t len); +/* 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); + +/* 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); + +/* 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); + /* File Write */ /* Writes 'len' bytes to an RZIP file. @@ -97,6 +122,38 @@ int64_t rzipstream_read(rzipstream_t *stream, void *data, int64_t len); * in the event of an error */ int64_t rzipstream_write(rzipstream_t *stream, const void *data, int64_t 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); + +/* 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); + +/* 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, ...); + +/* 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); + +/* 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); + /* File Status */ /* Returns total size (in bytes) of the *uncompressed* @@ -110,6 +167,15 @@ int64_t rzipstream_get_size(rzipstream_t *stream); * can be read from an RZIP file. */ int rzipstream_eof(rzipstream_t *stream); +/* 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); + +/* Returns true if specified RZIP file contains + * compressed content */ +bool rzipstream_is_compressed(rzipstream_t *stream); + /* File Close */ /* Closes RZIP file. If file is open for writing, diff --git a/libretro-common/streams/interface_stream.c b/libretro-common/streams/interface_stream.c index c2ad9edba8..67957bd169 100644 --- a/libretro-common/streams/interface_stream.c +++ b/libretro-common/streams/interface_stream.c @@ -349,6 +349,40 @@ int64_t intfstream_write(intfstream_internal_t *intf, return 0; } +int intfstream_printf(intfstream_internal_t *intf, + const char* format, ...) +{ + va_list vl; + int result; + + if (!intf) + return 0; + + switch (intf->type) + { + case INTFSTREAM_FILE: + va_start(vl, format); + result = filestream_vprintf(intf->file.fp, format, vl); + va_end(vl); + return result; + case INTFSTREAM_MEMORY: + return -1; + case INTFSTREAM_CHD: + return -1; + case INTFSTREAM_RZIP: +#if defined(HAVE_ZLIB) + va_start(vl, format); + result = rzipstream_vprintf(intf->rzip.fp, format, vl); + va_end(vl); + return result; +#else + return -1; +#endif + } + + return 0; +} + int64_t intfstream_get_ptr(intfstream_internal_t* intf) { if (!intf) @@ -390,8 +424,11 @@ char *intfstream_gets(intfstream_internal_t *intf, break; #endif case INTFSTREAM_RZIP: - /* Unsupported */ +#if defined(HAVE_ZLIB) + return rzipstream_gets(intf->rzip.fp, buffer, len); +#else break; +#endif } return NULL; @@ -415,8 +452,11 @@ int intfstream_getc(intfstream_internal_t *intf) break; #endif case INTFSTREAM_RZIP: - /* Unsupported */ +#if defined(HAVE_ZLIB) + return rzipstream_getc(intf->rzip.fp); +#else break; +#endif } return -1; @@ -440,8 +480,11 @@ int64_t intfstream_tell(intfstream_internal_t *intf) break; #endif case INTFSTREAM_RZIP: - /* Unsupported */ +#if defined(HAVE_ZLIB) + return (int64_t)rzipstream_tell(intf->rzip.fp); +#else break; +#endif } return -1; @@ -463,7 +506,9 @@ void intfstream_rewind(intfstream_internal_t *intf) #endif break; case INTFSTREAM_RZIP: - /* Unsupported */ +#if defined(HAVE_ZLIB) + rzipstream_rewind(intf->rzip.fp); +#endif break; } } @@ -484,8 +529,11 @@ void intfstream_putc(intfstream_internal_t *intf, int c) case INTFSTREAM_CHD: break; case INTFSTREAM_RZIP: - /* Unsupported */ +#if defined(HAVE_ZLIB) + rzipstream_putc(intf->rzip.fp, c); +#else break; +#endif } } @@ -515,6 +563,30 @@ uint32_t intfstream_get_frame_size(intfstream_internal_t *intf) return 0; } +bool intfstream_is_compressed(intfstream_internal_t *intf) +{ + if (!intf) + return false; + + switch (intf->type) + { + case INTFSTREAM_FILE: + return false; + case INTFSTREAM_MEMORY: + return false; + case INTFSTREAM_CHD: + return true; + case INTFSTREAM_RZIP: +#if defined(HAVE_ZLIB) + return rzipstream_is_compressed(intf->rzip.fp); +#else + break; +#endif + } + + return false; +} + intfstream_t* intfstream_open_file(const char *path, unsigned mode, unsigned hints) { diff --git a/libretro-common/streams/rzip_stream.c b/libretro-common/streams/rzip_stream.c index 94fa121caf..5614939a8a 100644 --- a/libretro-common/streams/rzip_stream.c +++ b/libretro-common/streams/rzip_stream.c @@ -518,7 +518,7 @@ int64_t rzipstream_read(rzipstream_t *stream, void *data, int64_t len) uint8_t *data_ptr = (uint8_t *)data; int64_t data_read = 0; - if (!stream || stream->is_writing) + if (!stream || stream->is_writing || !data) return -1; /* If we are reading uncompressed data, simply @@ -566,6 +566,151 @@ int64_t rzipstream_read(rzipstream_t *stream, void *data, int64_t len) 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) +{ + int c = 0; + char *str_ptr = s; + size_t str_len; + + 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) + { + fprintf(stderr, "[rzipstream] Failed to open file: %s\n", path ? path : ""); + goto error; + } + + /* 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) + { + fprintf(stderr, "[rzipstream] Failed to read file: %s\n", path); + 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 @@ -638,7 +783,7 @@ 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) + if (!stream || !stream->is_writing || !data) return -1; /* Process input data */ @@ -676,6 +821,194 @@ int64_t rzipstream_write(rzipstream_t *stream, const void *data, int64_t len) 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 = vsprintf(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) + { + fprintf(stderr, "[rzipstream] Failed to open file: %s\n", path ? path : ""); + return false; + } + + /* Write contents of data buffer to file */ + bytes_written = rzipstream_write(stream, data, len); + + /* Close file */ + if (rzipstream_close(stream) == -1) + { + fprintf(stderr, "[rzipstream] Failed to close file: %s\nData will be lost...\n", path); + return false; + } + + /* Check that the correct number of bytes + * were written */ + if (bytes_written != len) + { + fprintf(stderr, "[rzipstream] Wrote incorrect number of bytes to file: %s\n", path); + 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* @@ -708,6 +1041,27 @@ int rzipstream_eof(rzipstream_t *stream) 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,