Commit | Line | Data |
---|---|---|
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 */ | |
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 */ | |
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 | 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 */ | |
143 | static 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 */ | |
193 | static 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 */ | |
332 | static 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 */ | |
381 | rzipstream_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 */ | |
436 | static 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 */ | |
524 | int64_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. */ | |
584 | int 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. */ | |
606 | char* 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 */ | |
650 | bool 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 | ||
700 | error: | |
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 */ | |
721 | static 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 */ | |
785 | int64_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 */ | |
831 | int 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 */ | |
847 | int 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 */ | |
864 | int 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 */ | |
884 | bool 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). */ | |
916 | void 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. */ | |
995 | int64_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. */ | |
1007 | int 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. */ | |
1021 | int64_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 */ | |
1033 | bool 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. */ | |
1043 | int 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 | ||
1065 | error: | |
1066 | /* Stream must be free()'d regardless */ | |
1067 | rzipstream_free_stream(stream); | |
1068 | return -1; | |
1069 | } |