1 /* Copyright  (C) 2010-2020 The RetroArch team
2  *
3  * ---------------------------------------------------------------------------------------
4  * The following license statement only applies to this file (rzip_stream.c).
5  * ---------------------------------------------------------------------------------------
6  *
7  * Permission is hereby granted, free of charge,
8  * to any person obtaining a copy of this software and associated documentation files (the "Software"),
9  * to deal in the Software without restriction, including without limitation the rights to
10  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
11  * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
16  * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21  */
22 
23 #include <string/stdstring.h>
24 #include <file/file_path.h>
25 
26 #include <streams/file_stream.h>
27 #include <streams/trans_stream.h>
28 
29 #include <streams/rzip_stream.h>
30 
31 /* Current RZIP file format version */
32 #define RZIP_VERSION 1
33 
34 /* Compression level
35  * > zlib default of 6 provides the best
36  *   balance between file size and
37  *   compression speed */
38 #define RZIP_COMPRESSION_LEVEL 6
39 
40 /* Default chunk size: 128kb */
41 #define RZIP_DEFAULT_CHUNK_SIZE 131072
42 
43 /* Header sizes (in bytes) */
44 #define RZIP_HEADER_SIZE 20
45 #define RZIP_CHUNK_HEADER_SIZE 4
46 
47 /* Holds all metadata for an RZIP file stream */
48 struct rzipstream
49 {
50    uint64_t size;
51    /* virtual_ptr: Used to track how much
52     * uncompressed data has been read */
53    uint64_t virtual_ptr;
54    RFILE* file;
55    const struct trans_stream_backend *deflate_backend;
56    void *deflate_stream;
57    const struct trans_stream_backend *inflate_backend;
58    void *inflate_stream;
59    uint8_t *in_buf;
60    uint8_t *out_buf;
61    uint32_t in_buf_size;
62    uint32_t in_buf_ptr;
63    uint32_t out_buf_size;
64    uint32_t out_buf_ptr;
65    uint32_t out_buf_occupancy;
66    uint32_t chunk_size;
67    bool is_compressed;
68    bool is_writing;
69 };
70 
71 /* Header Functions */
72 
73 /* Reads header information from RZIP file
74  * > Detects whether file is compressed or
75  *   uncompressed data
76  * > If compressed, extracts uncompressed
77  *   file/chunk sizes */
rzipstream_read_file_header(rzipstream_t * stream)78 static bool rzipstream_read_file_header(rzipstream_t *stream)
79 {
80    unsigned i;
81    int64_t length;
82    uint8_t header_bytes[RZIP_HEADER_SIZE];
83 
84    if (!stream)
85       return false;
86 
87    for (i = 0; i < RZIP_HEADER_SIZE; i++)
88       header_bytes[i] = 0;
89 
90    /* Attempt to read header bytes */
91    length = filestream_read(stream->file, header_bytes, sizeof(header_bytes));
92    if (length <= 0)
93       return false;
94 
95    /* If file length is less than header size
96     * then assume this is uncompressed data */
97    if (length < RZIP_HEADER_SIZE)
98       goto file_uncompressed;
99 
100    /* Check 'magic numbers' - first 8 bytes
101     * of header */
102    if ((header_bytes[0] !=           35) || /* # */
103        (header_bytes[1] !=           82) || /* R */
104        (header_bytes[2] !=           90) || /* Z */
105        (header_bytes[3] !=           73) || /* I */
106        (header_bytes[4] !=           80) || /* P */
107        (header_bytes[5] !=          118) || /* v */
108        (header_bytes[6] != RZIP_VERSION) || /* file format version number */
109        (header_bytes[7] !=           35))   /* # */
110       goto file_uncompressed;
111 
112    /* Get uncompressed chunk size - next 4 bytes */
113    stream->chunk_size = ((uint32_t)header_bytes[11] << 24) |
114                         ((uint32_t)header_bytes[10] << 16) |
115                         ((uint32_t)header_bytes[9]  <<  8) |
116                          (uint32_t)header_bytes[8];
117    if (stream->chunk_size == 0)
118       return false;
119 
120    /* Get total uncompressed data size - next 8 bytes */
121    stream->size = ((uint64_t)header_bytes[19] << 56) |
122                   ((uint64_t)header_bytes[18] << 48) |
123                   ((uint64_t)header_bytes[17] << 40) |
124                   ((uint64_t)header_bytes[16] << 32) |
125                   ((uint64_t)header_bytes[15] << 24) |
126                   ((uint64_t)header_bytes[14] << 16) |
127                   ((uint64_t)header_bytes[13] <<  8) |
128                    (uint64_t)header_bytes[12];
129    if (stream->size == 0)
130       return false;
131 
132    stream->is_compressed = true;
133    return true;
134 
135 file_uncompressed:
136 
137    /* Reset file to start */
138    filestream_seek(stream->file, 0, SEEK_SET);
139 
140    /* Get 'raw' file size */
141    stream->size = filestream_get_size(stream->file);
142 
143    stream->is_compressed = false;
144    return true;
145 }
146 
147 /* Writes header information to RZIP file
148  * > ID 'magic numbers' + uncompressed
149  *   file/chunk sizes */
rzipstream_write_file_header(rzipstream_t * stream)150 static bool rzipstream_write_file_header(rzipstream_t *stream)
151 {
152    unsigned i;
153    int64_t length;
154    uint8_t header_bytes[RZIP_HEADER_SIZE];
155 
156    if (!stream)
157       return false;
158 
159    /* Populate header array */
160    for (i = 0; i < RZIP_HEADER_SIZE; i++)
161       header_bytes[i] = 0;
162 
163    /* > 'Magic numbers' - first 8 bytes */
164    header_bytes[0]    =        35;    /* # */
165    header_bytes[1]    =        82;    /* R */
166    header_bytes[2]    =        90;    /* Z */
167    header_bytes[3]    =        73;    /* I */
168    header_bytes[4]    =        80;    /* P */
169    header_bytes[5]    =       118;    /* v */
170    header_bytes[6]    = RZIP_VERSION; /* file format version number */
171    header_bytes[7]    =        35;    /* # */
172 
173    /* > Uncompressed chunk size - next 4 bytes */
174    header_bytes[11]   = (stream->chunk_size >> 24) & 0xFF;
175    header_bytes[10]   = (stream->chunk_size >> 16) & 0xFF;
176    header_bytes[9]    = (stream->chunk_size >>  8) & 0xFF;
177    header_bytes[8]    =  stream->chunk_size        & 0xFF;
178 
179    /* > Total uncompressed data size - next 8 bytes */
180    header_bytes[19]   = (stream->size >> 56) & 0xFF;
181    header_bytes[18]   = (stream->size >> 48) & 0xFF;
182    header_bytes[17]   = (stream->size >> 40) & 0xFF;
183    header_bytes[16]   = (stream->size >> 32) & 0xFF;
184    header_bytes[15]   = (stream->size >> 24) & 0xFF;
185    header_bytes[14]   = (stream->size >> 16) & 0xFF;
186    header_bytes[13]   = (stream->size >>  8) & 0xFF;
187    header_bytes[12]   =  stream->size        & 0xFF;
188 
189    /* Reset file to start */
190    filestream_seek(stream->file, 0, SEEK_SET);
191 
192    /* Write header bytes */
193    length = filestream_write(stream->file,
194          header_bytes, sizeof(header_bytes));
195    if (length != RZIP_HEADER_SIZE)
196       return false;
197 
198    return true;
199 }
200 
201 /* Stream Initialisation/De-initialisation */
202 
203 /* Initialises all members of an rzipstream_t struct,
204  * reading config from existing file header if available */
rzipstream_init_stream(rzipstream_t * stream,const char * path,bool is_writing)205 static bool rzipstream_init_stream(
206       rzipstream_t *stream, const char *path, bool is_writing)
207 {
208    unsigned file_mode;
209 
210    if (!stream)
211       return false;
212 
213    /* Ensure stream has valid initial values */
214    stream->size              = 0;
215    stream->chunk_size        = RZIP_DEFAULT_CHUNK_SIZE;
216    stream->file              = NULL;
217    stream->deflate_backend   = NULL;
218    stream->deflate_stream    = NULL;
219    stream->inflate_backend   = NULL;
220    stream->inflate_stream    = NULL;
221    stream->in_buf            = NULL;
222    stream->in_buf_size       = 0;
223    stream->in_buf_ptr        = 0;
224    stream->out_buf           = NULL;
225    stream->out_buf_size      = 0;
226    stream->out_buf_ptr       = 0;
227    stream->out_buf_occupancy = 0;
228 
229    /* Check whether this is a read or write stream */
230    stream->is_writing = is_writing;
231    if (stream->is_writing)
232    {
233       /* Written files are always compressed */
234       stream->is_compressed = true;
235       file_mode             = RETRO_VFS_FILE_ACCESS_WRITE;
236    }
237    /* For read files, must get compression status
238     * from file itself... */
239    else
240       file_mode             = RETRO_VFS_FILE_ACCESS_READ;
241 
242    /* Open file */
243    stream->file = filestream_open(
244          path, file_mode, RETRO_VFS_FILE_ACCESS_HINT_NONE);
245    if (!stream->file)
246       return false;
247 
248    /* If file is open for writing, output header
249     * (Size component cannot be written until
250     * file is closed...) */
251    if (stream->is_writing)
252    {
253       /* Note: could just write zeros here, but
254        * still want to identify this as an RZIP
255        * file if writing fails partway through */
256       if (!rzipstream_write_file_header(stream))
257          return false;
258    }
259    /* If file is open for reading, parse any existing
260     * header */
261    else if (!rzipstream_read_file_header(stream))
262       return false;
263 
264    /* Initialise appropriate transform stream
265     * and determine associated buffer sizes */
266    if (stream->is_writing)
267    {
268       /* Compression */
269       stream->deflate_backend = trans_stream_get_zlib_deflate_backend();
270       if (!stream->deflate_backend)
271          return false;
272 
273       stream->deflate_stream = stream->deflate_backend->stream_new();
274       if (!stream->deflate_stream)
275          return false;
276 
277       /* Set compression level */
278       if (!stream->deflate_backend->define(
279             stream->deflate_stream, "level", RZIP_COMPRESSION_LEVEL))
280          return false;
281 
282       /* Buffers
283        * > Input: uncompressed
284        * > Output: compressed */
285       stream->in_buf_size  = stream->chunk_size;
286       stream->out_buf_size = stream->chunk_size * 2;
287       /* > Account for minimum zlib overhead
288        *   of 11 bytes... */
289       stream->out_buf_size =
290             (stream->out_buf_size < (stream->in_buf_size + 11)) ?
291                   stream->out_buf_size + 11 :
292                   stream->out_buf_size;
293 
294       /* Redundant safety check */
295       if ((stream->in_buf_size == 0) ||
296           (stream->out_buf_size == 0))
297          return false;
298    }
299    /* When reading, don't need an inflate transform
300     * stream (or buffers) if source file is uncompressed */
301    else if (stream->is_compressed)
302    {
303       /* Decompression */
304       stream->inflate_backend = trans_stream_get_zlib_inflate_backend();
305       if (!stream->inflate_backend)
306          return false;
307 
308       stream->inflate_stream = stream->inflate_backend->stream_new();
309       if (!stream->inflate_stream)
310          return false;
311 
312       /* Buffers
313        * > Input: compressed
314        * > Output: uncompressed
315        * Note 1: Actual compressed chunk sizes are read
316        *         from the file - just allocate a sensible
317        *         default to minimise memory reallocations
318        * Note 2: If file header is valid, output buffer
319        *         should have a size of exactly stream->chunk_size.
320        *         Allocate some additional space, just for
321        *         redundant safety... */
322       stream->in_buf_size  = stream->chunk_size * 2;
323       stream->out_buf_size = stream->chunk_size + (stream->chunk_size >> 2);
324 
325       /* Redundant safety check */
326       if ((stream->in_buf_size == 0) ||
327           (stream->out_buf_size == 0))
328          return false;
329    }
330 
331    /* Allocate buffers */
332    if (stream->in_buf_size > 0)
333    {
334       stream->in_buf = (uint8_t *)calloc(stream->in_buf_size, 1);
335       if (!stream->in_buf)
336          return false;
337    }
338 
339    if (stream->out_buf_size > 0)
340    {
341       stream->out_buf = (uint8_t *)calloc(stream->out_buf_size, 1);
342       if (!stream->out_buf)
343          return false;
344    }
345 
346    return true;
347 }
348 
349 /* free()'s all members of an rzipstream_t struct
350  * > Also closes associated file, if currently open */
rzipstream_free_stream(rzipstream_t * stream)351 static int rzipstream_free_stream(rzipstream_t *stream)
352 {
353    int ret = 0;
354 
355    if (!stream)
356       return -1;
357 
358    /* Free transform streams */
359    if (stream->deflate_stream && stream->deflate_backend)
360       stream->deflate_backend->stream_free(stream->deflate_stream);
361 
362    stream->deflate_stream  = NULL;
363    stream->deflate_backend = NULL;
364 
365    if (stream->inflate_stream && stream->inflate_backend)
366       stream->inflate_backend->stream_free(stream->inflate_stream);
367 
368    stream->inflate_stream  = NULL;
369    stream->inflate_backend = NULL;
370 
371    /* Free buffers */
372    if (stream->in_buf)
373       free(stream->in_buf);
374    stream->in_buf = NULL;
375 
376    if (stream->out_buf)
377       free(stream->out_buf);
378    stream->out_buf = NULL;
379 
380    /* Close file */
381    if (stream->file)
382       ret = filestream_close(stream->file);
383    stream->file = NULL;
384 
385    free(stream);
386 
387    return ret;
388 }
389 
390 /* File Open */
391 
392 /* Opens a new or existing RZIP file
393  * > Supported 'mode' values are:
394  *   - RETRO_VFS_FILE_ACCESS_READ
395  *   - RETRO_VFS_FILE_ACCESS_WRITE
396  * > When reading, 'path' may reference compressed
397  *   or uncompressed data
398  * Returns NULL if arguments are invalid, file
399  * is invalid or an IO error occurs */
rzipstream_open(const char * path,unsigned mode)400 rzipstream_t* rzipstream_open(const char *path, unsigned mode)
401 {
402    rzipstream_t *stream = NULL;
403 
404    /* Sanity check
405     * > Only RETRO_VFS_FILE_ACCESS_READ and
406     *   RETRO_VFS_FILE_ACCESS_WRITE are supported */
407    if (string_is_empty(path) ||
408        ((mode != RETRO_VFS_FILE_ACCESS_READ) &&
409         (mode != RETRO_VFS_FILE_ACCESS_WRITE)))
410       return NULL;
411 
412    /* If opening in read mode, ensure file exists */
413    if ((mode == RETRO_VFS_FILE_ACCESS_READ) &&
414        !path_is_valid(path))
415       return NULL;
416 
417    /* Allocate stream object */
418    stream = (rzipstream_t*)malloc(sizeof(*stream));
419    if (!stream)
420       return NULL;
421 
422    stream->is_compressed   = false;
423    stream->is_writing      = false;
424    stream->size            = 0;
425    stream->chunk_size      = 0;
426    stream->virtual_ptr     = 0;
427    stream->file            = NULL;
428    stream->deflate_backend = NULL;
429    stream->deflate_stream  = NULL;
430    stream->inflate_backend = NULL;
431    stream->inflate_stream  = NULL;
432    stream->in_buf          = NULL;
433    stream->in_buf_size     = 0;
434    stream->in_buf_ptr      = 0;
435    stream->out_buf         = NULL;
436    stream->out_buf_size    = 0;
437    stream->out_buf_ptr     = 0;
438    stream->out_buf_occupancy = 0;
439 
440    /* Initialise stream */
441    if (!rzipstream_init_stream(
442          stream, path,
443          (mode == RETRO_VFS_FILE_ACCESS_WRITE)))
444    {
445       rzipstream_free_stream(stream);
446       return NULL;
447    }
448 
449    return stream;
450 }
451 
452 /* File Read */
453 
454 /* Reads and decompresses the next chunk of data
455  * in the RZIP file */
rzipstream_read_chunk(rzipstream_t * stream)456 static bool rzipstream_read_chunk(rzipstream_t *stream)
457 {
458    unsigned i;
459    int64_t length;
460    uint8_t chunk_header_bytes[RZIP_CHUNK_HEADER_SIZE];
461    uint32_t compressed_chunk_size;
462    uint32_t inflate_read;
463    uint32_t inflate_written;
464 
465    if (!stream || !stream->inflate_backend || !stream->inflate_stream)
466       return false;
467 
468    for (i = 0; i < RZIP_CHUNK_HEADER_SIZE; i++)
469       chunk_header_bytes[i] = 0;
470 
471    /* Attempt to read chunk header bytes */
472    length = filestream_read(
473          stream->file, chunk_header_bytes, sizeof(chunk_header_bytes));
474    if (length != RZIP_CHUNK_HEADER_SIZE)
475       return false;
476 
477    /* Get size of next compressed chunk */
478    compressed_chunk_size = ((uint32_t)chunk_header_bytes[3] << 24) |
479                            ((uint32_t)chunk_header_bytes[2] << 16) |
480                            ((uint32_t)chunk_header_bytes[1] <<  8) |
481                             (uint32_t)chunk_header_bytes[0];
482    if (compressed_chunk_size == 0)
483       return false;
484 
485    /* Resize input buffer, if required */
486    if (compressed_chunk_size > stream->in_buf_size)
487    {
488       free(stream->in_buf);
489       stream->in_buf      = NULL;
490 
491       stream->in_buf_size = compressed_chunk_size;
492       stream->in_buf      = (uint8_t *)calloc(stream->in_buf_size, 1);
493       if (!stream->in_buf)
494          return false;
495 
496       /* Note: Uncompressed data size is fixed, and read
497        * from the file header - we therefore don't attempt
498        * to resize the output buffer (if it's too small, then
499        * that's an error condition) */
500    }
501 
502    /* Read compressed chunk from file */
503    length = filestream_read(
504          stream->file, stream->in_buf, compressed_chunk_size);
505    if (length != compressed_chunk_size)
506       return false;
507 
508    /* Decompress chunk data */
509    stream->inflate_backend->set_in(
510          stream->inflate_stream,
511          stream->in_buf, compressed_chunk_size);
512 
513    stream->inflate_backend->set_out(
514          stream->inflate_stream,
515          stream->out_buf, stream->out_buf_size);
516 
517    /* Note: We have to set 'flush == true' here, otherwise we
518     * can't guarantee that the entire chunk will be written
519     * to the output buffer - this is inefficient, but not
520     * much we can do... */
521    if (!stream->inflate_backend->trans(
522          stream->inflate_stream, true,
523          &inflate_read, &inflate_written, NULL))
524       return false;
525 
526    /* Error checking */
527    if (inflate_read != compressed_chunk_size)
528       return false;
529 
530    if ((inflate_written == 0) ||
531        (inflate_written > stream->out_buf_size))
532       return false;
533 
534    /* Record current output buffer occupancy
535     * and reset pointer */
536    stream->out_buf_occupancy = inflate_written;
537    stream->out_buf_ptr       = 0;
538 
539    return true;
540 }
541 
542 /* Reads (a maximum of) 'len' bytes from an RZIP file.
543  * Returns actual number of bytes read, or -1 in
544  * the event of an error */
rzipstream_read(rzipstream_t * stream,void * data,int64_t len)545 int64_t rzipstream_read(rzipstream_t *stream, void *data, int64_t len)
546 {
547    int64_t data_len  = len;
548    uint8_t *data_ptr = (uint8_t *)data;
549    int64_t data_read = 0;
550 
551    if (!stream || stream->is_writing || !data)
552       return -1;
553 
554    /* If we are reading uncompressed data, simply
555     * 'pass on' the direct file access request */
556    if (!stream->is_compressed)
557       return filestream_read(stream->file, data, len);
558 
559    /* Process input data */
560    while (data_len > 0)
561    {
562       uint32_t read_size = 0;
563 
564       /* Check whether we have reached the end
565        * of the file */
566       if (stream->virtual_ptr >= stream->size)
567          return data_read;
568 
569       /* If everything in the output buffer has already
570        * been read, grab and extract the next chunk
571        * from disk */
572       if (stream->out_buf_ptr >= stream->out_buf_occupancy)
573          if (!rzipstream_read_chunk(stream))
574             return -1;
575 
576       /* Get amount of data to 'read out' this loop
577        * > i.e. minimum of remaining output buffer
578        *   occupancy and remaining 'read data' size */
579       read_size = stream->out_buf_occupancy - stream->out_buf_ptr;
580       read_size = (read_size > data_len) ? data_len : read_size;
581 
582       /* Copy as much cached data as possible into
583        * the read buffer */
584       memcpy(data_ptr, stream->out_buf + stream->out_buf_ptr, read_size);
585 
586       /* Increment pointers and remaining length */
587       stream->out_buf_ptr += read_size;
588       data_ptr            += read_size;
589       data_len            -= read_size;
590 
591       stream->virtual_ptr += read_size;
592 
593       data_read           += read_size;
594    }
595 
596    return data_read;
597 }
598 
599 /* Reads next character from an RZIP file.
600  * Returns character value, or EOF if no data
601  * remains.
602  * Note: Always returns EOF if file is open
603  * for writing. */
rzipstream_getc(rzipstream_t * stream)604 int rzipstream_getc(rzipstream_t *stream)
605 {
606    char c = 0;
607 
608    if (!stream || stream->is_writing)
609       return EOF;
610 
611    /* Attempt to read a single character */
612    if (rzipstream_read(stream, &c, 1) == 1)
613       return (int)(unsigned char)c;
614 
615    return EOF;
616 }
617 
618 /* Reads one line from an RZIP file and stores it
619  * in the character array pointed to by 's'.
620  * It stops reading when either (len-1) characters
621  * are read, the newline character is read, or the
622  * end-of-file is reached, whichever comes first.
623  * On success, returns 's'. In the event of an error,
624  * or if end-of-file is reached and no characters
625  * have been read, returns NULL. */
rzipstream_gets(rzipstream_t * stream,char * s,size_t len)626 char* rzipstream_gets(rzipstream_t *stream, char *s, size_t len)
627 {
628    size_t str_len;
629    int c         = 0;
630    char *str_ptr = s;
631 
632    if (!stream || stream->is_writing || (len == 0))
633       return NULL;
634 
635    /* Read bytes until newline or EOF is reached,
636     * or string buffer is full */
637    for (str_len = (len - 1); str_len > 0; str_len--)
638    {
639       /* Get next character */
640       c = rzipstream_getc(stream);
641 
642       /* Check for newline and EOF */
643       if ((c == '\n') || (c == EOF))
644          break;
645 
646       /* Copy character to string buffer */
647       *str_ptr++ = c;
648    }
649 
650    /* Add NUL termination */
651    *str_ptr = '\0';
652 
653    /* Check whether EOF has been reached without
654     * reading any characters */
655    if ((str_ptr == s) && (c == EOF))
656       return NULL;
657 
658    return (s);
659 }
660 
661 /* Reads all data from file specified by 'path' and
662  * copies it to 'buf'.
663  * - 'buf' will be allocated and must be free()'d manually.
664  * - Allocated 'buf' size is equal to 'len'.
665  * Returns false in the event of an error */
rzipstream_read_file(const char * path,void ** buf,int64_t * len)666 bool rzipstream_read_file(const char *path, void **buf, int64_t *len)
667 {
668    int64_t bytes_read       = 0;
669    void *content_buf        = NULL;
670    int64_t content_buf_size = 0;
671    rzipstream_t *stream     = NULL;
672 
673    if (!buf)
674       return false;
675 
676    /* Attempt to open file */
677    stream = rzipstream_open(path, RETRO_VFS_FILE_ACCESS_READ);
678 
679    if (!stream)
680    {
681       *buf = NULL;
682       return false;
683    }
684 
685    /* Get file size */
686    content_buf_size = rzipstream_get_size(stream);
687 
688    if (content_buf_size < 0)
689       goto error;
690 
691    if ((int64_t)(uint64_t)(content_buf_size + 1) != (content_buf_size + 1))
692       goto error;
693 
694    /* Allocate buffer */
695    content_buf = malloc((size_t)(content_buf_size + 1));
696 
697    if (!content_buf)
698       goto error;
699 
700    /* Read file contents */
701    bytes_read = rzipstream_read(stream, content_buf, content_buf_size);
702 
703    if (bytes_read < 0)
704       goto error;
705 
706    /* Close file */
707    rzipstream_close(stream);
708    stream = NULL;
709 
710    /* Add NUL termination for easy/safe handling of strings.
711     * Will only work with sane character formatting (Unix). */
712    ((char*)content_buf)[bytes_read] = '\0';
713 
714    /* Assign buffer */
715    *buf = content_buf;
716 
717    /* Assign length value, if required */
718    if (len)
719       *len = bytes_read;
720 
721    return true;
722 
723 error:
724 
725    if (stream)
726       rzipstream_close(stream);
727    stream = NULL;
728 
729    if (content_buf)
730       free(content_buf);
731    content_buf = NULL;
732 
733    if (len)
734       *len = -1;
735 
736    *buf = NULL;
737 
738    return false;
739 }
740 
741 /* File Write */
742 
743 /* Compresses currently cached data and writes it
744  * as the next RZIP file chunk */
rzipstream_write_chunk(rzipstream_t * stream)745 static bool rzipstream_write_chunk(rzipstream_t *stream)
746 {
747    unsigned i;
748    int64_t length;
749    uint8_t chunk_header_bytes[RZIP_CHUNK_HEADER_SIZE];
750    uint32_t deflate_read;
751    uint32_t deflate_written;
752 
753    if (!stream || !stream->deflate_backend || !stream->deflate_stream)
754       return false;
755 
756    for (i = 0; i < RZIP_CHUNK_HEADER_SIZE; i++)
757       chunk_header_bytes[i] = 0;
758 
759    /* Compress data currently held in input buffer */
760    stream->deflate_backend->set_in(
761          stream->deflate_stream,
762          stream->in_buf, stream->in_buf_ptr);
763 
764    stream->deflate_backend->set_out(
765          stream->deflate_stream,
766          stream->out_buf, stream->out_buf_size);
767 
768    /* Note: We have to set 'flush == true' here, otherwise we
769     * can't guarantee that the entire chunk will be written
770     * to the output buffer - this is inefficient, but not
771     * much we can do... */
772    if (!stream->deflate_backend->trans(
773          stream->deflate_stream, true,
774          &deflate_read, &deflate_written, NULL))
775       return false;
776 
777    /* Error checking */
778    if (deflate_read != stream->in_buf_ptr)
779       return false;
780 
781    if ((deflate_written == 0) ||
782        (deflate_written > stream->out_buf_size))
783       return false;
784 
785    /* Write compressed chunk size to file */
786    chunk_header_bytes[3] = (deflate_written >> 24) & 0xFF;
787    chunk_header_bytes[2] = (deflate_written >> 16) & 0xFF;
788    chunk_header_bytes[1] = (deflate_written >>  8) & 0xFF;
789    chunk_header_bytes[0] =  deflate_written        & 0xFF;
790 
791    length = filestream_write(
792          stream->file, chunk_header_bytes, sizeof(chunk_header_bytes));
793    if (length != RZIP_CHUNK_HEADER_SIZE)
794       return false;
795 
796    /* Write compressed data to file */
797    length = filestream_write(
798          stream->file, stream->out_buf, deflate_written);
799 
800    if (length != deflate_written)
801       return false;
802 
803    /* Reset input buffer pointer */
804    stream->in_buf_ptr = 0;
805 
806    return true;
807 }
808 
809 /* Writes 'len' bytes to an RZIP file.
810  * Returns actual number of bytes written, or -1
811  * in the event of an error */
rzipstream_write(rzipstream_t * stream,const void * data,int64_t len)812 int64_t rzipstream_write(rzipstream_t *stream, const void *data, int64_t len)
813 {
814    int64_t data_len        = len;
815    const uint8_t *data_ptr = (const uint8_t *)data;
816 
817    if (!stream || !stream->is_writing || !data)
818       return -1;
819 
820    /* Process input data */
821    while (data_len > 0)
822    {
823       uint32_t cache_size = 0;
824 
825       /* If input buffer is full, compress and write to disk */
826       if (stream->in_buf_ptr >= stream->in_buf_size)
827          if (!rzipstream_write_chunk(stream))
828             return -1;
829 
830       /* Get amount of data to cache during this loop
831        * > i.e. minimum of space remaining in input buffer
832        *   and remaining 'write data' size */
833       cache_size = stream->in_buf_size - stream->in_buf_ptr;
834       cache_size = (cache_size > data_len) ? data_len : cache_size;
835 
836       /* Copy as much data as possible into
837        * the input buffer */
838       memcpy(stream->in_buf + stream->in_buf_ptr, data_ptr, cache_size);
839 
840       /* Increment pointers and remaining length */
841       stream->in_buf_ptr  += cache_size;
842       data_ptr            += cache_size;
843       data_len            -= cache_size;
844 
845       stream->size        += cache_size;
846       stream->virtual_ptr += cache_size;
847    }
848 
849    /* We always write the specified number of bytes
850     * (unless rzipstream_write_chunk() fails, in
851     * which we register a complete failure...) */
852    return len;
853 }
854 
855 /* Writes a single character to an RZIP file.
856  * Returns character written, or EOF in the event
857  * of an error */
rzipstream_putc(rzipstream_t * stream,int c)858 int rzipstream_putc(rzipstream_t *stream, int c)
859 {
860    char c_char = (char)c;
861 
862    if (!stream || !stream->is_writing)
863       return EOF;
864 
865    return (rzipstream_write(stream, &c_char, 1) == 1) ?
866          (int)(unsigned char)c : EOF;
867 }
868 
869 /* Writes a variable argument list to an RZIP file.
870  * Ugly 'internal' function, required to enable
871  * 'printf' support in the higher level 'interface_stream'.
872  * Returns actual number of bytes written, or -1
873  * in the event of an error */
rzipstream_vprintf(rzipstream_t * stream,const char * format,va_list args)874 int rzipstream_vprintf(rzipstream_t *stream, const char* format, va_list args)
875 {
876    static char buffer[8 * 1024] = {0};
877    int64_t num_chars            = vsnprintf(buffer,
878          sizeof(buffer), format, args);
879 
880    if (num_chars < 0)
881       return -1;
882    else if (num_chars == 0)
883       return 0;
884 
885    return (int)rzipstream_write(stream, buffer, num_chars);
886 }
887 
888 /* Writes formatted output to an RZIP file.
889  * Returns actual number of bytes written, or -1
890  * in the event of an error */
rzipstream_printf(rzipstream_t * stream,const char * format,...)891 int rzipstream_printf(rzipstream_t *stream, const char* format, ...)
892 {
893    va_list vl;
894    int result = 0;
895 
896    /* Initialise variable argument list */
897    va_start(vl, format);
898 
899    /* Write variable argument list to file */
900    result = rzipstream_vprintf(stream, format, vl);
901 
902    /* End using variable argument list */
903    va_end(vl);
904 
905    return result;
906 }
907 
908 /* Writes contents of 'data' buffer to file
909  * specified by 'path'.
910  * Returns false in the event of an error */
rzipstream_write_file(const char * path,const void * data,int64_t len)911 bool rzipstream_write_file(const char *path, const void *data, int64_t len)
912 {
913    int64_t bytes_written = 0;
914    rzipstream_t *stream  = NULL;
915 
916    if (!data)
917       return false;
918 
919    /* Attempt to open file */
920    stream = rzipstream_open(path, RETRO_VFS_FILE_ACCESS_WRITE);
921 
922    if (!stream)
923       return false;
924 
925    /* Write contents of data buffer to file */
926    bytes_written = rzipstream_write(stream, data, len);
927 
928    /* Close file */
929    if (rzipstream_close(stream) == -1)
930       return false;
931 
932    /* Check that the correct number of bytes
933     * were written */
934    if (bytes_written != len)
935       return false;
936 
937    return true;
938 }
939 
940 /* File Control */
941 
942 /* Sets file position to the beginning of the
943  * specified RZIP file.
944  * Note: It is not recommended to rewind a file
945  * that is open for writing, since the caller
946  * may end up with a file containing junk data
947  * at the end (harmless, but a waste of space). */
rzipstream_rewind(rzipstream_t * stream)948 void rzipstream_rewind(rzipstream_t *stream)
949 {
950    if (!stream)
951       return;
952 
953    /* Note: rzipstream_rewind() has no way of
954     * reporting errors (higher level interface
955     * requires a void return type) - so if anything
956     * goes wrong, all we can do is print to stderr
957     * and bail out... */
958 
959    /* If we are handling uncompressed data, simply
960     * 'pass on' the direct file access request */
961    if (!stream->is_compressed)
962    {
963       filestream_rewind(stream->file);
964       return;
965    }
966 
967    /* If no file access has yet occurred, file is
968     * already at the beginning -> do nothing */
969    if (stream->virtual_ptr == 0)
970       return;
971 
972    /* Check whether we are reading or writing */
973    if (stream->is_writing)
974    {
975       /* Reset file position to first chunk location */
976       filestream_seek(stream->file, RZIP_HEADER_SIZE, SEEK_SET);
977       if (filestream_error(stream->file))
978       {
979          fprintf(
980                stderr,
981                "rzipstream_rewind(): Failed to reset file position...\n");
982          return;
983       }
984 
985       /* Reset pointers */
986       stream->virtual_ptr = 0;
987       stream->in_buf_ptr  = 0;
988 
989       /* Reset file size */
990       stream->size        = 0;
991    }
992    else
993    {
994       /* Check whether first file chunk is currently
995        * buffered in memory */
996       if ((stream->virtual_ptr < stream->chunk_size) &&
997           (stream->out_buf_ptr < stream->out_buf_occupancy))
998       {
999          /* It is: No file access is therefore required
1000           * > Just reset pointers */
1001          stream->virtual_ptr = 0;
1002          stream->out_buf_ptr = 0;
1003       }
1004       else
1005       {
1006          /* It isn't: Have to re-read the first chunk
1007           * from disk... */
1008 
1009          /* Reset file position to first chunk location */
1010          filestream_seek(stream->file, RZIP_HEADER_SIZE, SEEK_SET);
1011          if (filestream_error(stream->file))
1012          {
1013             fprintf(
1014                   stderr,
1015                   "rzipstream_rewind(): Failed to reset file position...\n");
1016             return;
1017          }
1018 
1019          /* Read chunk */
1020          if (!rzipstream_read_chunk(stream))
1021          {
1022             fprintf(
1023                   stderr,
1024                   "rzipstream_rewind(): Failed to read first chunk of file...\n");
1025             return;
1026          }
1027 
1028          /* Reset pointers */
1029          stream->virtual_ptr = 0;
1030          stream->out_buf_ptr = 0;
1031       }
1032    }
1033 }
1034 
1035 /* File Status */
1036 
1037 /* Returns total size (in bytes) of the *uncompressed*
1038  * data in an RZIP file.
1039  * (If reading an uncompressed file, this corresponds
1040  * to the 'physical' file size in bytes)
1041  * Returns -1 in the event of a error. */
rzipstream_get_size(rzipstream_t * stream)1042 int64_t rzipstream_get_size(rzipstream_t *stream)
1043 {
1044    if (!stream)
1045       return -1;
1046 
1047    if (stream->is_compressed)
1048       return stream->size;
1049    return filestream_get_size(stream->file);
1050 }
1051 
1052 /* Returns EOF when no further *uncompressed* data
1053  * can be read from an RZIP file. */
rzipstream_eof(rzipstream_t * stream)1054 int rzipstream_eof(rzipstream_t *stream)
1055 {
1056    if (!stream)
1057       return -1;
1058 
1059    if (stream->is_compressed)
1060       return (stream->virtual_ptr >= stream->size) ?
1061             EOF : 0;
1062    return filestream_eof(stream->file);
1063 }
1064 
1065 /* Returns the offset of the current byte of *uncompressed*
1066  * data relative to the beginning of an RZIP file.
1067  * Returns -1 in the event of a error. */
rzipstream_tell(rzipstream_t * stream)1068 int64_t rzipstream_tell(rzipstream_t *stream)
1069 {
1070    if (!stream)
1071       return -1;
1072 
1073    return (int64_t)stream->virtual_ptr;
1074 }
1075 
1076 /* Returns true if specified RZIP file contains
1077  * compressed content */
rzipstream_is_compressed(rzipstream_t * stream)1078 bool rzipstream_is_compressed(rzipstream_t *stream)
1079 {
1080    if (!stream)
1081       return false;
1082 
1083    return stream->is_compressed;
1084 }
1085 
1086 /* File Close */
1087 
1088 /* Closes RZIP file. If file is open for writing,
1089  * flushes any remaining buffered data to disk.
1090  * Returns -1 in the event of a error. */
rzipstream_close(rzipstream_t * stream)1091 int rzipstream_close(rzipstream_t *stream)
1092 {
1093    if (!stream)
1094       return -1;
1095 
1096    /* If we are writing, ensure that any
1097     * remaining uncompressed data is flushed to
1098     * disk and update file header */
1099    if (stream->is_writing)
1100    {
1101       if (stream->in_buf_ptr > 0)
1102          if (!rzipstream_write_chunk(stream))
1103             goto error;
1104 
1105       if (!rzipstream_write_file_header(stream))
1106          goto error;
1107    }
1108 
1109    /* Free stream
1110     * > This also closes the file */
1111    return rzipstream_free_stream(stream);
1112 
1113 error:
1114    /* Stream must be free()'d regardless */
1115    rzipstream_free_stream(stream);
1116    return -1;
1117 }
1118