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