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