libretro: adjust psxclock description
[pcsx_rearmed.git] / deps / libretro-common / file / archive_file_zlib.c
CommitLineData
3719602c
PC
1/* Copyright (C) 2010-2020 The RetroArch team
2 *
3 * ---------------------------------------------------------------------------------------
4 * The following license statement only applies to this file (archive_file_zlib.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 <stdlib.h>
24#include <string.h>
25
26#include <file/archive_file.h>
27#include <streams/file_stream.h>
28#include <retro_inline.h>
29#include <retro_miscellaneous.h>
30#include <encodings/crc32.h>
31
32#include <zlib.h>
33
34#ifndef CENTRAL_FILE_HEADER_SIGNATURE
35#define CENTRAL_FILE_HEADER_SIGNATURE 0x02014b50
36#endif
37
38#ifndef END_OF_CENTRAL_DIR_SIGNATURE
39#define END_OF_CENTRAL_DIR_SIGNATURE 0x06054b50
40#endif
41
42#define _READ_CHUNK_SIZE (128*1024) /* Read 128KiB compressed chunks */
43
44enum file_archive_compression_mode
45{
46 ZIP_MODE_STORED = 0,
47 ZIP_MODE_DEFLATED = 8
48};
49
50typedef struct
51{
52 struct file_archive_transfer *state;
53 uint8_t *directory;
54 uint8_t *directory_entry;
55 uint8_t *directory_end;
56 uint64_t fdoffset;
57 uint32_t boffset, csize, usize;
58 unsigned cmode;
59 z_stream *zstream;
60 uint8_t *tmpbuf;
61 uint8_t *decompressed_data;
62} zip_context_t;
63
64static INLINE uint32_t read_le(const uint8_t *data, unsigned size)
65{
66 unsigned i;
67 uint32_t val = 0;
68
69 size *= 8;
70 for (i = 0; i < size; i += 8)
71 val |= (uint32_t)*data++ << i;
72
73 return val;
74}
75
76static void zip_context_free_stream(
77 zip_context_t *zip_context, bool keep_decompressed)
78{
79 if (zip_context->zstream)
80 {
81 inflateEnd(zip_context->zstream);
82 free(zip_context->zstream);
83 zip_context->fdoffset = 0;
84 zip_context->csize = 0;
85 zip_context->usize = 0;
86 zip_context->zstream = NULL;
87 }
88 if (zip_context->tmpbuf)
89 {
90 free(zip_context->tmpbuf);
91 zip_context->tmpbuf = NULL;
92 }
93 if (zip_context->decompressed_data && !keep_decompressed)
94 {
95 free(zip_context->decompressed_data);
96 zip_context->decompressed_data = NULL;
97 }
98}
99
100static bool zlib_stream_decompress_data_to_file_init(
101 void *context, file_archive_file_handle_t *handle,
102 const uint8_t *cdata, unsigned cmode, uint32_t csize, uint32_t size)
103{
104 zip_context_t *zip_context = (zip_context_t *)context;
105 struct file_archive_transfer *state = zip_context->state;
106 uint8_t local_header_buf[4];
107 uint8_t *local_header;
108 uint32_t offsetNL, offsetEL;
109 int64_t offsetData;
110
111 /* free previous data and stream if left unfinished */
112 zip_context_free_stream(zip_context, false);
113
114 /* seek past most of the local directory header */
115#ifdef HAVE_MMAP
116 if (state->archive_mmap_data)
117 {
118 local_header = state->archive_mmap_data + (size_t)cdata + 26;
119 }
120 else
121#endif
122 {
123 filestream_seek(state->archive_file, (int64_t)(size_t)cdata + 26, RETRO_VFS_SEEK_POSITION_START);
124 if (filestream_read(state->archive_file, local_header_buf, 4) != 4)
125 goto error;
126 local_header = local_header_buf;
127 }
128
129 offsetNL = read_le(local_header, 2); /* file name length */
130 offsetEL = read_le(local_header + 2, 2); /* extra field length */
131 offsetData = (int64_t)(size_t)cdata + 26 + 4 + offsetNL + offsetEL;
132
133 zip_context->fdoffset = offsetData;
134 zip_context->usize = size;
135 zip_context->csize = csize;
136 zip_context->boffset = 0;
137 zip_context->cmode = cmode;
138 zip_context->decompressed_data = (uint8_t*)malloc(size);
139 zip_context->zstream = NULL;
140 zip_context->tmpbuf = NULL;
141
142 if (cmode == ZIP_MODE_DEFLATED)
143 {
144 /* Initialize the zlib inflate machinery */
145 zip_context->zstream = (z_stream*)malloc(sizeof(z_stream));
146 zip_context->tmpbuf = malloc(_READ_CHUNK_SIZE);
147
148 zip_context->zstream->next_in = NULL;
149 zip_context->zstream->avail_in = 0;
150 zip_context->zstream->total_in = 0;
151 zip_context->zstream->next_out = zip_context->decompressed_data;
152 zip_context->zstream->avail_out = size;
153 zip_context->zstream->total_out = 0;
154
155 zip_context->zstream->zalloc = NULL;
156 zip_context->zstream->zfree = NULL;
157 zip_context->zstream->opaque = NULL;
158
159 if (inflateInit2(zip_context->zstream, -MAX_WBITS) != Z_OK) {
160 free(zip_context->zstream);
161 zip_context->zstream = NULL;
162 goto error;
163 }
164 }
165
166 return true;
167
168error:
169 zip_context_free_stream(zip_context, false);
170 return false;
171}
172
173static int zlib_stream_decompress_data_to_file_iterate(
174 void *context, file_archive_file_handle_t *handle)
175{
176 zip_context_t *zip_context = (zip_context_t *)context;
177 struct file_archive_transfer *state = zip_context->state;
178 int64_t rd;
179
180 if (zip_context->cmode == ZIP_MODE_STORED)
181 {
182 #ifdef HAVE_MMAP
183 if (zip_context->state->archive_mmap_data)
184 {
185 /* Simply copy the data to the output buffer */
186 memcpy(zip_context->decompressed_data,
187 zip_context->state->archive_mmap_data + (size_t)zip_context->fdoffset,
188 zip_context->usize);
189 }
190 else
191 #endif
192 {
193 /* Read the entire file to memory */
194 filestream_seek(state->archive_file, zip_context->fdoffset, RETRO_VFS_SEEK_POSITION_START);
195 if (filestream_read(state->archive_file,
196 zip_context->decompressed_data,
197 zip_context->usize) < 0)
198 return -1;
199 }
200
201 handle->data = zip_context->decompressed_data;
202 return 1;
203 }
204 else if (zip_context->cmode == ZIP_MODE_DEFLATED)
205 {
206 int to_read = MIN(zip_context->csize - zip_context->boffset, _READ_CHUNK_SIZE);
207 uint8_t *dptr;
208 if (!zip_context->zstream)
209 {
210 /* file was uncompressed or decompression finished before */
211 return 1;
212 }
213
214 #ifdef HAVE_MMAP
215 if (state->archive_mmap_data)
216 {
217 /* Decompress from the mapped file */
218 dptr = state->archive_mmap_data + (size_t)zip_context->fdoffset + zip_context->boffset;
219 rd = to_read;
220 }
221 else
222 #endif
223 {
224 /* Read some compressed data from file to the temp buffer */
225 filestream_seek(state->archive_file, zip_context->fdoffset + zip_context->boffset,
226 RETRO_VFS_SEEK_POSITION_START);
227 rd = filestream_read(state->archive_file, zip_context->tmpbuf, to_read);
228 if (rd < 0)
229 return -1;
230 dptr = zip_context->tmpbuf;
231 }
232
233 zip_context->boffset += rd;
234 zip_context->zstream->next_in = dptr;
235 zip_context->zstream->avail_in = (uInt)rd;
236
237 if (inflate(zip_context->zstream, 0) < 0)
238 return -1;
239
240 if (zip_context->boffset >= zip_context->csize)
241 {
242 inflateEnd(zip_context->zstream);
243 free(zip_context->zstream);
244 zip_context->zstream = NULL;
245
246 handle->data = zip_context->decompressed_data;
247 return 1;
248 }
249
250 return 0; /* still more data to process */
251 }
252
253 /* No idea what kind of compression this is */
254 return -1;
255}
256
257static uint32_t zlib_stream_crc32_calculate(uint32_t crc,
258 const uint8_t *data, size_t length)
259{
260 return encoding_crc32(crc, data, length);
261}
262
263static bool zip_file_decompressed_handle(
264 file_archive_transfer_t *transfer,
265 file_archive_file_handle_t* handle,
266 const uint8_t *cdata, unsigned cmode, uint32_t csize,
267 uint32_t size, uint32_t crc32)
268{
269 int ret = 0;
270
271 transfer->backend = &zlib_backend;
272
273 if (!transfer->backend->stream_decompress_data_to_file_init(
274 transfer->context, handle, cdata, cmode, csize, size))
275 return false;
276
277 do
278 {
279 ret = transfer->backend->stream_decompress_data_to_file_iterate(
280 transfer->context, handle);
281 if (ret < 0)
282 return false;
283 }while (ret == 0);
284
285 return true;
286}
287
288typedef struct
289{
290 char *opt_file;
291 char *needle;
292 void **buf;
293 size_t size;
294 bool found;
295} decomp_state_t;
296
297/* Extract the relative path (needle) from a
298 * ZIP archive (path) and allocate a buffer for it to write it in.
299 *
300 * optional_outfile if not NULL will be used to extract the file to.
301 * buf will be 0 then.
302 */
303
304static int zip_file_decompressed(
305 const char *name, const char *valid_exts,
306 const uint8_t *cdata, unsigned cmode,
307 uint32_t csize, uint32_t size,
308 uint32_t crc32, struct archive_extract_userdata *userdata)
309{
310 decomp_state_t* decomp_state = (decomp_state_t*)userdata->cb_data;
311 char last_char = name[strlen(name) - 1];
312 /* Ignore directories. */
313 if (last_char == '/' || last_char == '\\')
314 return 1;
315
316 if (strstr(name, decomp_state->needle))
317 {
318 file_archive_file_handle_t handle = {0};
319
320 if (zip_file_decompressed_handle(userdata->transfer,
321 &handle, cdata, cmode, csize, size, crc32))
322 {
323 if (decomp_state->opt_file != 0)
324 {
325 /* Called in case core has need_fullpath enabled. */
326 bool success = filestream_write_file(decomp_state->opt_file, handle.data, size);
327
328 /* Note: Do not free handle.data here - this
329 * will be done when stream is deinitialised */
330 handle.data = NULL;
331
332 decomp_state->size = 0;
333
334 if (!success)
335 return -1;
336 }
337 else
338 {
339 /* Called in case core has need_fullpath disabled.
340 * Will move decompressed content directly into
341 * RetroArch's ROM buffer. */
342 zip_context_t *zip_context = (zip_context_t *)userdata->transfer->context;
343
344 decomp_state->size = 0;
345 *decomp_state->buf = handle.data;
346 decomp_state->size = size;
347 /* We keep the data, prevent its deallocation during free */
348 zip_context->decompressed_data = NULL;
349 handle.data = NULL;
350 }
351 }
352
353 decomp_state->found = true;
354 }
355
356 return 1;
357}
358
359static int64_t zip_file_read(
360 const char *path,
361 const char *needle, void **buf,
362 const char *optional_outfile)
363{
364 file_archive_transfer_t state = {0};
365 decomp_state_t decomp = {0};
366 struct archive_extract_userdata userdata = {0};
367 bool returnerr = true;
368 int ret = 0;
369
370 if (needle)
371 decomp.needle = strdup(needle);
372 if (optional_outfile)
373 decomp.opt_file = strdup(optional_outfile);
374
375 state.type = ARCHIVE_TRANSFER_INIT;
376 userdata.transfer = &state;
377 userdata.cb_data = &decomp;
378 decomp.buf = buf;
379
380 do
381 {
382 ret = file_archive_parse_file_iterate(&state, &returnerr, path,
383 "", zip_file_decompressed, &userdata);
384 if (!returnerr)
385 break;
386 }while (ret == 0 && !decomp.found);
387
388 file_archive_parse_file_iterate_stop(&state);
389
390 if (decomp.opt_file)
391 free(decomp.opt_file);
392 if (decomp.needle)
393 free(decomp.needle);
394
395 if (!decomp.found)
396 return -1;
397
398 return (int64_t)decomp.size;
399}
400
401static int zip_parse_file_init(file_archive_transfer_t *state,
402 const char *file)
403{
404 uint8_t footer_buf[1024];
405 uint8_t *footer = footer_buf;
406 int64_t read_pos = state->archive_size;
407 int64_t read_block = MIN(read_pos, (ssize_t)sizeof(footer_buf));
408 int64_t directory_size, directory_offset;
409 zip_context_t *zip_context = NULL;
410
411 /* Minimal ZIP file size is 22 bytes */
412 if (read_block < 22)
413 return -1;
414
415 /* Find the end of central directory record by scanning
416 * the file from the end towards the beginning.
417 */
418 for (;;)
419 {
420 if (--footer < footer_buf)
421 {
422 if (read_pos <= 0)
423 return -1; /* reached beginning of file */
424
425 /* Read 21 bytes of overlaps except on the first block. */
426 if (read_pos == state->archive_size)
427 read_pos = read_pos - read_block;
428 else
429 read_pos = MAX(read_pos - read_block + 21, 0);
430
431 /* Seek to read_pos and read read_block bytes. */
432 filestream_seek(state->archive_file, read_pos, RETRO_VFS_SEEK_POSITION_START);
433 if (filestream_read(state->archive_file, footer_buf, read_block) != read_block)
434 return -1;
435
436 footer = footer_buf + read_block - 22;
437 }
438 if (read_le(footer, 4) == END_OF_CENTRAL_DIR_SIGNATURE)
439 {
440 unsigned comment_len = read_le(footer + 20, 2);
441 if (read_pos + (footer - footer_buf) + 22 + comment_len == state->archive_size)
442 break; /* found it! */
443 }
444 }
445
446 /* Read directory info and do basic sanity checks. */
447 directory_size = read_le(footer + 12, 4);
448 directory_offset = read_le(footer + 16, 4);
449 if (directory_size > state->archive_size
450 || directory_offset > state->archive_size)
451 return -1;
452
453 /* This is a ZIP file, allocate one block of memory for both the
454 * context and the entire directory, then read the directory.
455 */
456 zip_context = (zip_context_t*)malloc(sizeof(zip_context_t) + (size_t)directory_size);
457 zip_context->state = state;
458 zip_context->directory = (uint8_t*)(zip_context + 1);
459 zip_context->directory_entry = zip_context->directory;
460 zip_context->directory_end = zip_context->directory + (size_t)directory_size;
461 zip_context->zstream = NULL;
462 zip_context->tmpbuf = NULL;
463 zip_context->decompressed_data = NULL;
464
465 filestream_seek(state->archive_file, directory_offset, RETRO_VFS_SEEK_POSITION_START);
466 if (filestream_read(state->archive_file, zip_context->directory, directory_size) != directory_size)
467 {
468 free(zip_context);
469 return -1;
470 }
471
472 state->context = zip_context;
473 state->step_total = read_le(footer + 10, 2); /* total entries */;
474
475 return 0;
476}
477
478static int zip_parse_file_iterate_step_internal(
479 zip_context_t * zip_context, char *filename,
480 const uint8_t **cdata,
481 unsigned *cmode, uint32_t *size, uint32_t *csize,
482 uint32_t *checksum, unsigned *payback)
483{
484 uint8_t *entry = zip_context->directory_entry;
485 uint32_t signature, namelength, extralength, commentlength, offset;
486
487 if (entry < zip_context->directory || entry >= zip_context->directory_end)
488 return 0;
489
490 signature = read_le(zip_context->directory_entry + 0, 4);
491
492 if (signature != CENTRAL_FILE_HEADER_SIGNATURE)
493 return 0;
494
495 *cmode = read_le(zip_context->directory_entry + 10, 2); /* compression mode, 0 = store, 8 = deflate */
496 *checksum = read_le(zip_context->directory_entry + 16, 4); /* CRC32 */
497 *csize = read_le(zip_context->directory_entry + 20, 4); /* compressed size */
498 *size = read_le(zip_context->directory_entry + 24, 4); /* uncompressed size */
499
500 namelength = read_le(zip_context->directory_entry + 28, 2); /* file name length */
501 extralength = read_le(zip_context->directory_entry + 30, 2); /* extra field length */
502 commentlength = read_le(zip_context->directory_entry + 32, 2); /* file comment length */
503
504 if (namelength >= PATH_MAX_LENGTH)
505 return -1;
506
507 memcpy(filename, zip_context->directory_entry + 46, namelength); /* file name */
508 filename[namelength] = '\0';
509
510 offset = read_le(zip_context->directory_entry + 42, 4); /* relative offset of local file header */
511
512 *cdata = (uint8_t*)(size_t)offset; /* store file offset in data pointer */
513
514 *payback = 46 + namelength + extralength + commentlength;
515
516 return 1;
517}
518
519static int zip_parse_file_iterate_step(void *context,
520 const char *valid_exts, struct archive_extract_userdata *userdata,
521 file_archive_file_cb file_cb)
522{
523 zip_context_t *zip_context = (zip_context_t *)context;
524 const uint8_t *cdata = NULL;
525 uint32_t checksum = 0;
526 uint32_t size = 0;
527 uint32_t csize = 0;
528 unsigned cmode = 0;
529 unsigned payload = 0;
530 int ret = zip_parse_file_iterate_step_internal(zip_context,
531 userdata->current_file_path, &cdata, &cmode, &size, &csize, &checksum, &payload);
532
533 if (ret != 1)
534 return ret;
535
536 userdata->crc = checksum;
537
538 if (file_cb && !file_cb(userdata->current_file_path, valid_exts, cdata, cmode,
539 csize, size, checksum, userdata))
540 return 0;
541
542 zip_context->directory_entry += payload;
543
544 return 1;
545}
546
547static void zip_parse_file_free(void *context)
548{
549 zip_context_t *zip_context = (zip_context_t *)context;
550 zip_context_free_stream(zip_context, false);
551 free(zip_context);
552}
553
554const struct file_archive_file_backend zlib_backend = {
555 zip_parse_file_init,
556 zip_parse_file_iterate_step,
557 zip_parse_file_free,
558 zlib_stream_decompress_data_to_file_init,
559 zlib_stream_decompress_data_to_file_iterate,
560 zlib_stream_crc32_calculate,
561 zip_file_read,
562 "zlib"
563};